我觉得这个问题可能有一个简单的解决方案,但对我来说并不明显——我有一个配置类,用于存储从 ini 文件加载的各种配置选项,以及其他地方。在我的应用程序中,我有一个库和客户端,以及 2 个配置 - 将库构建为 DLL 并动态链接客户端,或者将它们一起构建为单个二进制文件。那么如何在库和客户端中拥有/使用我的配置对象呢?如果我在两者中都包含配置类定义,我认为它会由于重新定义而给我链接错误。
1 回答
如果我在两者中都包含配置类定义,我认为它会由于重新定义而给我链接错误。
不,不会的。Windows DLL 不遵守单一定义规则。基本上,在 Windows 中,ODR 停止在模块边界,即 ODR在可执行文件和DLL 中受到尊重,但不在它们之间。这是否是一件好事并不重要,事情就是这样。因此,您可以在 DLL 和可执行文件中包含配置类的定义。但是,将有两个单独的单例实例(我假设它是一个单例类,就像配置类通常那样),每个模块中都有一个。从这个意义上说,它不会是真正的单例,至少不会跨模块。
如果你想要一个真正的跨模块的单例,你必须做更多的工作。您有两个选择:主从或合并(或“菊花链”)。
第一种选择是将一个模块(例如,可执行文件)指定为实例化(并保留)单例对象的模块,然后将指向该实例的指针传递给所有“从属”模块,然后这些“从属”模块可以通过公共接口(因此两个模块对配置类具有相同的声明,但只有一个模块创建它并将其传递给其他模块)。它看起来像这样:
在头文件“config_class.h”中:
class ConfigClass {
// a bunch of declarations...
public:
static ConfigClass& getInstance(); // the access-point for the singleton.
};
#ifdef MY_LIB_NOW_BUILDING_MASTER
extern "C" __declspec(dllimport) void setConfigClassInstance(ConfigClass* pobj);
#else
extern "C" __declspec(dllexport) void setConfigClassInstance(ConfigClass* pobj);
#endif
在 cpp 文件“config_class.cpp”中:
#include "config_class.h"
// a bunch of definitions for the config_class member functions.
#ifdef MY_LIB_NOW_BUILDING_MASTER
ConfigClass& ConfigClass::getInstance() {
static ConfigClass instance( /* */ );
return instance;
};
#else
static ConfigClass* masterInstance;
void setConfigClassInstance(ConfigClass* pobj) {
masterInstance = pobj;
};
ConfigClass& ConfigClass::getInstance() {
return *masterInstance;
};
#endif
在上面的示例中,您将从主模块(很可能是主可执行文件)调用 setConfigClassInstance 来设置 DLL 的配置对象,但要确保 DLL 在静态初始化(加载)期间不需要配置类.
第二种选择是合并或菊花链您的单例。在这种情况下,每个模块都会创建自己的单例实例,但随后,使用与上述类似的方案,它们将指针传递给彼此的实例,从而允许它们合并到一个实例中(交叉链接),或者将它们链接在一起(例如,像循环链表或环表),然后您将调用分派到适当的实例。
我认为对于您的应用程序,第一个选项可能是最简单的。
注意:在非 Windows 环境中,情况完全不同,以上均不适用。