Generate and Embed Resource Files with GCC Toolchain
Introduction
As we all know, resource files play an indispensable role in program execution. However, to achieve cross-platform resource file utilization, we must abandon Win32 resource files. This article introduces a method for generating and embedding resource files using the (MinGW) GCC toolchain.
Step 1: Generate Resource Object Files Using ld
In this step, we will use the linker (ld) to generate the resource object file (.o) we need.
Prepare Resource Files
Resource files can be in any format, including but not limited to images and text files. Here we use two text documents as examples:
File: a.txt
dawdawwdwkjagcbsfgbcfgfjbkajkaadgadFile: b.txt
Hello,txt1!Generate Resource Object File
Open CMD in the folder containing the resource files and execute:
ld -r -b binary a.txt b.txt -o res.oTip
The command template is: ld -r -b binary {file1} {file2} -o {output file path}; wildcards are available.
Thus, we obtain the resource object file res.o.
Check Resource Object File Symbols (Optional)
To ensure successful compilation, we can first check the symbols exposed by the resource file to understand the naming pattern.
Execute in the CMD:
objdump -x res.oWe get a large amount of output. At the end of the output, there is a table similar to:
SYMBOL TABLE:
[ 0](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000023 _binary_a_txt_size
[ 1](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x000000000000000b _binary_b_txt_size
[ 2](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x000000000000002e _binary_b_txt_end
[ 3](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000023 _binary_b_txt_start
[ 4](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000023 _binary_a_txt_end
[ 5](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000000 _binary_a_txt_startThis shows that our text documents are embedded in the resource object file, generating several symbols. We can summarize the pattern:
Symbol Naming Pattern
_binary_filename_start
_binary_filename_end
_binary_filename_size
Note: The . in filenames is replaced with _.
Step 2: Write Test Code
Importing Symbols
Our ultimate goal is to embed the resource file into the executable and use it in the program. To achieve this, we need to import symbols in the source file, for example:
extern const char _binary_a_txt_start[], _binary_a_txt_end[];
extern const char _binary_b_txt_start[], _binary_b_txt_end[];By using the extern keyword and copying the symbol names, we can import symbols into our source code.
Important Note
Symbols are addresses and should be defined as const char[] type.
Using Symbols
For example:
int main(){
size_t a_len = _binary_a_txt_end - _binary_a_txt_start;
size_t b_len = _binary_b_txt_end - _binary_b_txt_start;
printf("a.txt len=%u,content= %.*s\n", a_len, a_len, _binary_a_txt_start );
printf("b.txt len=%u,content= %.*s\n", b_len, b_len, _binary_b_txt_start );
return 0;
}Save as test.cpp and compile:
g++ res.o test.cpp -o test.exe
The executable file is now generated. The execution result should be:a.txt len=35,content= dawdawwdwkjagcbsfgbcfgfjbkajkaadgad
b.txt len=11,content= Hello,txt1!
## Step 3: Encapsulate for Easy Use
Here we use GCC's macro expansion feature to write example encapsulation code:
File: customResource.hpp
```Cpp
#pragma once
#include <cstdint>
#define RESDEF(name) extern const char _binary_##name##_start[], _binary_##name##_end[]
#define RES(name) _binary_##name##_start, _binary_##name##_end
namespace Utils {
class Resource {
public:
Resource(const char* begin, const char* end)
: _begin(begin)
, _end(end)
{
}
~Resource() { }
const char* begin() { return this->_begin; }
const char* end() { return this->_end; }
size_t length() { return this->_end - this->_begin; }
const uint8_t* data() { return (const uint8_t*)this->_begin; }
private:
const char* _begin = nullptr;
const char* _end = nullptr;
};
}File: main.cpp
#include <cstdio>
#include "customResource.hpp"
using namespace Utils;
RESDEF(a_txt);
RESDEF(b_txt);
Resource a(RES(a_txt));
Resource b(RES(b_txt));
int main()
{
printf("a.txt len=%u,content= %.*s\n", a.length(), a.length() , a.begin());
printf("b.txt len=%u,content= %.*s\n", b.length(), b.length() , b.begin());
return 0;
}Compile and run as usual.
Usage Tip
First use RESDEF(filename) to import symbols, then use Utils::Resource(RES(filename)) to instantiate objects
Postscript
This article references the following blog posts:
Detailed Discussion of #define Replacement Rules and # and ## in Macro Definitions
Written after testing on Windows 11 + MinGW-w64 11.2.0 Seh. Thanks to the original blog authors!