DEV Community

Alexey Timin
Alexey Timin

Posted on

How to include external resources into your executables in C++

Very often we need to include into our programs some resources like textures, sounds, static data etc. Usually, we distribute them with the programs as files. However, if you want to distribute the application as a single executable, it may become a bit tricky.

Classical approach for GCC

The most elegant way to do it, was described here. It works perfectly but only on Linux with GCC.

Portable approach

If you want a portable approach for including your resources,
you can use cmake and generate a header file with the resources as strings.

Let's create a template with name resource.h.in:

#ifndef RESOURCE_H
#define RESOURCE_H

#include <string_view>
namespace app {

static std::string_view kResource = "@RESOURCE_DATA@";

}
#endif  // RESOURCE_H
Enter fullscreen mode Exit fullscreen mode

In your CMakeLists.txt you should read the resource from its file and use configure_file to generate a header:

file(READ ${CMAKE_CURRENT_BINARY_DIR}/path/to/resource.data RESOURCE_DATA HEX)
configure_file(resource.h.in ${CMAKE_BINARY_DIR}/app/resource.h @ONLY)
Enter fullscreen mode Exit fullscreen mode

We need to represent our data as hex-digits, overwise your binary data breaks the C++ strings. It isn't a big deal, you can later convert it into a normal string in your C++ code:

  auto hex_str_to_str = [](std::string hex) {
    auto len = hex.length();
    std::transform(hex.begin(), hex.end(), hex.begin(), toupper);
    std::string new_string;
    for (int i = 0; i < len; i += 2) {
      auto byte = hex.substr(i, 2);
      char chr = ((byte[0] - (byte[0] < 'A' ? 0x30 : 0x37)) << 4) + (byte[1] - (byte[1] < 'A' ? 0x30 : 0x37));
      new_string.push_back(chr);
    }

    return new_string;
  };

auto data = hex_str_to_str(std::move(hex));
Enter fullscreen mode Exit fullscreen mode

Unfortunately, if your resources are big, you may meet this error on Windows:

C2026:  string too big, trailing characters truncated
Enter fullscreen mode Exit fullscreen mode

It happens if your string is bigger than 16380 chars.
Annoying… but it's possible to work around the problem.
We can reformat the strings in Python:

"""Script to reformat long string for Windows"""
import sys

STEP = 16000
if __name__ == "__main__":
    filename = sys.argv[1]
    split = []
    with open(filename, "r") as file:
        while True:
            chunk = file.read(STEP)
            split.append(chunk)
            if len(chunk) < STEP:
                break

    with open(filename, "w") as file:
        file.write('"\n"'.join(split))
Enter fullscreen mode Exit fullscreen mode

and run it in our CMakeLists.txt:

file(READ ${CMAKE_CURRENT_BINARY_DIR}/path/to/resource.data RESOURCE_DATA HEX)
configure_file(resource.h.in ${CMAKE_BINARY_DIR}/app/resource.h @ONLY)
execute_process(COMMAND python3 ${CMAKE_SOURCE_DIR}/cmake/split_string.py ${CMAKE_BINARY_DIR}/app/resource.h)
Enter fullscreen mode Exit fullscreen mode

Of course, you need Python on your machine now. If it is an issue, you can find out how to do it in pure-CMake as well.

Top comments (0)