Qt 资源系统

Qt 资源系统是一套平台独立的机制,用于将二进制文件存储至应用程序的可执行文件中。这在您的应用始终依赖一组特定文件(如图标、翻译文件等),并且不想承担丢失这些文件的风险时,会非常有用。

资源系统基于 qmakercc (Qt 的资源编译器) 和 QFile 的密切协作。

资源汇总文件(.qrc)

通过 .qrc 文件来指定应用程序所关联的资源,该文件基于 XML 格式,包含了磁盘中的文件列表,并为它们标注可选的别名,以供应用程序来获取资源内容。

下文为一个 .qrc 文件范例:

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file>images/copy.png</file>
    <file>images/cut.png</file>
    <file>images/new.png</file>
    <file>images/open.png</file>
    <file>images/paste.png</file>
    <file>images/save.png</file>
</qresource>
</RCC>

.qrc 文件中列举的资源文件是应用程序资源树的一部分,其中指定的路径为 .qrc 文件所在目录的相对路径。注意,列举的资源文件必须位于 .qrc 文件所在目录或其子目录。

资源数据可以被编译进二进制程序,从而在运行时可被立即获取;也可以生成为二进制文件,随后在应用程序代码中注册至资源系统。

默认情况下,资源文件在应用程序中,可使用它们在资源树中的路径,附加上 :/ 前缀来访问,也可以通过名为 qrcURL Scheme 来访问。

例如,文件路径 :/images/cut.png,或 链接地址 qrc:///images/cut.png,可以用于访问 cut.png 文件,该文件在应用程序资源树中的路径为 images/cut.png。该路径也可以通过 file 标签的 alias 属性进行修改:

<file alias="cut-img.png">images/cut.png</file>

此时,该文件可通过 :/cut-img.png 路径访问。也可以通过 qresource 标签的 prefix 属性为 .qrc 文件中的所有资源文件设置路径前缀:

<qresource prefix="/myresources">
    <file alias="cut-img.png">images/cut.png</file>
</qresource>

在此场景下,该文件可通过 :/myresources/cut-img.png 路径访问。

某些资源需要随用户的区域设置发生改变,如翻译文件或图标。这可以通过在 qresource 标签中添加 lang 属性,为其指定对应的区域名称字符串来实现,例如:

<qresource>
    <file>cut.jpg</file>
</qresource>
<qresource lang="fr">
    <file alias="cut.jpg">cut_fr.jpg</file>
</qresource>

若用户区域为法国(即 QLocale::system().name() 函数返回 "fr_FR"),则 :/cut.jpg 会引用至 cut_fr.jpg,其它情况下使用 cut.jpg

另请参阅QLocale 文档以获取区域字符串格式的说明。

另请参阅QFileSelector 文档以了解另一个基于区域选取资源的机制,该机制可基于操作系统等更多附加信息来进行选取。

外部二进制资源

若要生成外部二进制资源,您需要传递 -binary 选项至 rcc 来创建资源数据文件(通常使用 .rcc 扩展名)。二进制资源文件创建完毕后,可以通过 QResource 接口进行注册。

例如,.qrc 文件中指定的一组资源数据,可以通过如下方式进行编译:

rcc -binary myresource.qrc -o myresource.rcc

在应用程序中,通过如下代码注册资源:

QResource::registerResource("/path/to/myresource.rcc");

内建资源

若要将资源编译至二进制程序内,您需要在应用程序的 .pro 文件中指定 .qrc 文件,从而让 qmake 可以感知到它。例如:

RESOURCES     = application.qrc

qmake 会生成编译规则来创建名为 qrc_application.cpp 的源文件,并将其链接至应用程序。该文件包含以 C++ 静态数组的形式存储的图片和其它所有资源的二进制压缩数据。qrc_application.cpp 文件会在 .qrc 文件或其引用的任意资源文件发生改变时自动重新生成。若您不使用 .pro 文件,则需要手动执行 rcc,或将其添加到您的构建系统中。

资源编译至应用程序内

目前,Qt 总是将数据直接存储至可执行程序内,无论是 Windows、macOS 还是 iOS——这些操作系统都提供了内嵌资源的原生支持。这个模式在 Qt 未来的版本中可能会有改变。

压缩

rcc 会尝试压缩资源数据来减少最终生成的二进制文件的磁盘占用。通常情况下,会通过启发式的检查来决定是否值得进行压缩,并且在无法有效压缩的情况下直接存储未压缩的内容。您可以使用 -threshold 来控制判断阈值,该选项告知 rcc 源文件压缩后获得的体积缩减必须不小于该百分比。

rcc -threshold 25 myresources.qrc

默认值为 70,意味着压缩后的文件必须比源文件小 70%(即不大于源文件的 30% 大小)。

若有需要的话,也可以关闭压缩,这在资源文件中包含已经压缩过的数据时(如 .png 文件)非常有用——否则会在编译时浪费 CPU 时间来确认它的确不能被再次压缩。另一个原因则是无需考虑磁盘占用,并且想让资源在运行时以可直接访问的原始数据存放于内存中,而非需要解压才可使用的压缩数据。您可以通过命令行参数 -no-compress 来实现此目的。

rcc -no-compress myresources.qrc

rcc 同样给予用户控制压缩算法和压缩等级的权限,例如:

rcc -compress 2 -compress-algo zlib myresources.qrc

另外,也可以在 .qrc 文件中 file 标签的 thresholdcompresscompress-algo 属性来进行设置:

<qresource>
    <file compress="1" compress-algo="zstd">data.txt</file>
</qresource>

上述代码选择 zstd 算法,使用压缩等级 1.

rcc 支持如下压缩算法和压缩等级:

  • best:使用下述算法中的最优者,并使用其最高压缩等级,以编译时的 CPU 时间为代价来获得最高压缩率。此参数在 XML 文件中可用于指明无论 rcc 支持何种压缩算法,该文件都应被尽可能地压缩。
  • zstd:使用 Zstandard 库进行压缩。有效压缩等级为 1 至 19,1 为最低压缩率(最短 CPU 时间),19 为最高压缩率(最长 CPU 时间)。默认等级为 14。特殊值 0 用于告知 zstd 库自由选择其默认的压缩率。
  • zlib:使用 zlib 库进行压缩。有效压缩等级为 1 至 9,1 为最低压缩率(最短 CPU 时间),9 为最高压缩率(最长 CPU 时间)。特殊值 0 代表“无需压缩”,并且不应被使用。默认值为实现定义,通常为 6。
  • none:无压缩。该参数等价于 -no-compress 选项。

Zstandard 和 zlib 均为可选支持。若运行时未检测到对应的库,则通过 -compress-algo 指定该库的操作会报错。默认情况下,若 zstd 可用则使用该算法,否则使用 zlib

在应用程序中使用资源

在应用程序中,资源路径可用于绝大多数常规文件系统路径的中。例如,可以传递资源路径而非文件路径至 QIconQImageQPixmap 的构造函数:

cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this);

另请参阅Application 范例以了解实际程序中如何使用 Qt 的资源系统来存储图标。

资源在内存中以资源对象树形式表达,该对象树在程序启动时构建,通过 QFile 来将其路径解析至资源数据。可通过 ":/" 初始化 QDir 对象,来从根部遍历该对象树。

Qt 的资源系统支持搜索路径列表概念。若通过 : 而非 :/ 前缀来指定资源,则会通过搜索路径列表来检索该资源。搜索路径列表在程序启动时为空,可通过 QDir::addSearchPath() 来添加路径。

使用库中的资源

若在库中包含资源,则需要调用 Q_INIT_RESOURCE() 来强制初始化资源,调用参数为 .qrc 文件的基本名称,例如

MyClass::MyClass() : BaseClass()
{
    Q_INIT_RESOURCE(resources);

    QFile file(":/myfile.dat");
    ...
}

这确保了在静态链接时,资源数据可被链接至最终的应用程序。该初始化代码应该紧贴在库中使用资源的位置之前,以确保仅当该库的使用者使用到依赖资源的特性时,才会将资源链接至程序。

注意:由于 rcc 生成的资源初始化器定义在全局命名空间,Q_INIT_RESOURCE() 的调用也必须位于任何命名空间之外。

若该库包含的资源不在内部使用,而是暴露给库的使用者,则初始化操作需要发生在应用程序代码中,例如:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Q_INIT_RESOURCE(graphlib);

    QFile file(":/graph.png");
    ...
    return app.exec();
}

如上文所述,此方式确保了资源数据可在静态链接时被链接至最终的应用程序,同时也会在动态链接时触发库的加载动作,如加载插件。

类似地,若想显示地卸载一组资源(因为需要卸载插件,或该资源不再被使用),则可通过调用 Q_CLEANUP_RESOURCE() 来强制移除资源,调用参数与上文相同。

注意:若资源文件被编译为应用程序的一部分,则不需要使用 Q_INIT_RESOURCE() 和 Q_CLEANUP_RESOURCE()。