GCC 工具链生成并嵌入资源文件

9/12/2022 C++GCC资源文件

# 简介

众所周知,资源文件对程序的运行具有不可或缺的作用。然而,想要实现跨平台的资源文件利用,我们就不得不摒弃 Win32 资源文件。本文介绍了利用 (MinGW) GCC 工具链生成并嵌入资源文件的一种方法。

# Step 1:利用 ld 生成资源对象文件

在这一步,我们将利用 链接器(ld) 生成我们所需要的资源对象文件(.o)。

# 准备资源文件

资源文件可以是任意的格式,包括但不限于图片,文本;以下用两个文本文档为例:

文件:a.txt

dawdawwdwkjagcbsfgbcfgfjbkajkaadgad
1

文件:b.txt

Hello,txt1!
1

# 生成资源对象文件

在包含该资源文件的文件夹路径下打开 CMD,执行指令:

ld -r -b binary a.txt b.txt -o res.o
1

提示

指令模板为:ld -r -b binary {文件1} {文件2} -o {输出文件路径};通配符可用。

这样,我们就得到了资源对象文件res.o

# 检查资源对象文件符号(可选)

为了确保编译的成功,我们可以先行检查资源文件暴露的符号,得出符号命名规律。

在原先的 CMD 中执行指令

objdump -x res.o
1

我们便得到了一大段输出。找到输出的最后,有类似的表格:

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_start
1
2
3
4
5
6
7

这表示,我们的文本文档嵌入了资源对象文件之中,并生成了几个符号。可以总结得出:

符号的命名规律

_binary_文件名_start
_binary_文件名_end
_binary_文件名_size

注意:文件名中.转为_

# Step 2:编写测试代码

# 符号的引入

我们的最终目标是将资源文件嵌入可执行文件中并在程序中使用。为了实现这一目标,我们需要在源码文件中引入符号,例如:

extern const char _binary_a_txt_start[], _binary_a_txt_end[];
extern const char _binary_b_txt_start[], _binary_b_txt_end[];
1
2

使用extern关键字,并复制符号名称,我们就可以将符号引入源码了。

特别注意

符号是一个地址,应定义为 const char[] 类型。

# 符号的使用

例如:

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;
}
1
2
3
4
5
6
7

保存为test.cpp,然后编译:

g++ res.o test.cpp -o test.exe
1

这样,我们就得到了可执行文件。执行的结果应如下所示:

a.txt len=35,content= dawdawwdwkjagcbsfgbcfgfjbkajkaadgad
b.txt len=11,content= Hello,txt1!
1
2

# Step 3:封装起来,便于使用

这里利用 GCC 的宏定义展开特性,编写示例封装代码:

文件:customResource.hpp

#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;
};

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

文件: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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

编译运行即可。

使用提示

先使用RESDEF(文件名)来引入符号,然后使用Utils::Resource(RES(文件名))来实例化对象

# 后记

本文参考了下列博文:

GCC 中实现添加资源 (opens new window)

详谈#define的替换规则以及宏定义中的#和##详解 (opens new window)

ld参数解释 (opens new window)

并经 Windows 11 + MinGW-w64 11.2.0 Seh 测试通过再行编写而成。在此对原博文博主们表示感谢!