在静态库中初始化嵌入的Qt资源

13

我有一个问题:我需要在独立的静态库中创建小部件(widget),然后将其与最终应用程序(Visual C++ 9.0, Qt 4.5)链接。这个静态小部件库包含一些资源(图标),并由几个.cpp文件组成(每个文件包含一个独立的小部件)。据我所知,如果我在静态库中使用这些资源,我必须初始化Qt资源系统,并调用"Q_INIT_RESOURCE( resource_file_name )"。我通过下面的代码解决了这个问题(在静态库的每个.cpp文件中):


#include <QAbstractButton>
namespace { struct StaticLibInitializer { StaticLibInitializer() { Q_INIT_RESOURCE(qtwidgets_custom_resources); } }; StaticLibInitializer staticLibInitializer; }
// ... widget code ....
这段代码包含一个命名空间和一个结构体。命名空间被用于将变量和函数放在独立的作用域内,以防止名称冲突。结构体被称为“静态库初始化器”,它在创建时会调用函数`Q_INIT_RESOURCE(qtwidgets_custom_resources)`来初始化静态库。
在代码下方还有一些注释,暗示了这段代码中可能涉及的控件部分,但是具体细节需要查看其他地方的代码。

我最初的方法是在静态库项目中创建了一个单独的init.cpp文件,其中包含初始化代码(以避免在每个.cpp文件中都包含初始化代码),但这种方法失败了。

为什么这种方法行不通?

使用StaticLibInitializer的这种方法在各种编译器和平台上是否安全可靠?

2个回答

11

由于你遭遇了静态初始化顺序灾难,所以它不起作用。

你不能将初始化静态对象的代码移到使用这些静态对象的翻译单元(可以将其视为源文件)之外。不是以你这种方式。如果你想要使用当前方案来初始化这些静态对象,则只需将声明移入init.hpp头文件中,但需要在每个使用静态对象的文件中保留实例化StaticLibInitializer staticLibInitializer;
上述建议假设每个小部件仅使用其自己的资源。如果你有一个小部件的资源被另一个小部件使用的情况,那么你会再次遇到静态初始化顺序问题。你可以通过使用以下代码来处理此情况

StaticLibInitializer
{
    void initialize()
    {
        static Q_INIT_RESOURCE(qtwidgets_custom_resources);
    }

    StaticLibInitializer()
    {
         initialize();
    }
}

为了确保StaticLibInitializer的多个实例只会初始化给定资源一次,并为在给定翻译单元中使用的每个资源实例化StaticLibInitializer。


在我的当前情况下,我有三个 .cpp 文件(每个文件实现自己的小部件,其中两个使用.qrc 文件中的资源),但是我在原问题中提供的初始化代码只在其中一个文件中,而且所有工作都正常(100%,而不是50/50)。所以我不明白,为什么当我将初始化代码放入单独的 init.cpp 文件中时,就无法使用我的资源,但是当这个代码位于其中一个小部件的 .cpp 文件中时,所有内容都能够正常工作... - cybevnm
现在它能正常工作并不重要 :) 它只是偶然能够工作。当你开始使用另一个编译器甚至同一编译器的另一个版本时,它可能会停止工作。这是未定义行为。现在它能够工作的原因是因为当你在其中一个小部件文件中有初始化代码时,编译器碰巧首先初始化了你的资源。纯粹是运气,没有别的。如果你不想在某个晴朗的日子里让你的程序彻底崩溃,请按照避免静态初始化顺序混乱的说明进行操作。 - Piotr Dobrogost
静态初始化顺序是由编译器在编译阶段定义的,还是顺序可能会因程序重新启动而变化(无需重新编译)? - cybevnm
不,它不会因为程序启动而有所变化,通常取决于链接顺序。一些编译器允许使用扩展来指定跨翻译单元的初始化顺序。 - Gunther Piez

8

Q_INIT_RESOURCE宏不能在命名空间中使用。

让我引用Qt手册中的话:“注意:此宏不能在命名空间中使用。它应该从main()函数中调用”。即使这不可能,它也会给你一个正确使用的示例:

  inline void initMyResource() { Q_INIT_RESOURCE(myapp); }

    namespace MyNamespace
    {
     ...

     void myFunction()
     {
         initMyResource();
     }
  }

请自行查看为什么以及如何在未指定的方式下失败或不失败。相关代码位于QtCore中。

但是在第一种方法中(当我将代码包含在静态库的每个.cpp文件中时),这可以工作(即使使用匿名命名空间)。 - cybevnm
在上面使用 inline 并不能为您带来任何好处,因为您无法保证编译器会遵守它。不遵守此关键字符合 c++ 标准。因此,如果这个 解决方案 基于内联函数将被内联的假设,那么它是错误的。 - Piotr Dobrogost
1
inline 函数在语义上略有不同,特别是涉及到ODR时。考虑到我们不知道 Q_INIT_RESOURCE 在所有平台上的宏展开情况,很难判断它是否需要。将其放在那里肯定是合理的。 - MSalters
这解决了我的Windows问题,谢谢!接下来会尝试在Mac/Linux上。 - Dariusz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接