我知道多年来这个问题已经以不同的形式被提出和回答,但我发现之前的问答与具体的例子过于相关,并没有让我充分理解为什么以及在什么情况下,dll 可以生成同一个单例的多个实例。我自己正在努力想出一个最小的可重复示例。
给定一个粗略形式的单例:
class Singleton
{
private:
static Singleton* singleton;
public:
Singelton(); //
~Singleton(); // Not shown for brevity.
static Singelton* getSingleton();
};
Singleton* Singleton::getSingleton()
{
if (!singleton)
{
singleton= new Singleton;
}
return singleton;
}
这被编译到一个 dll 中(我称之为
SingletonAndOtherStuff.dll
),旨在由其他应用程序通过 LoadLibrary
加载。
接下来我开始感到困惑。
在一个单独的主应用程序(我将其称为
app.exe
)中,SingletonAndOtherStuff.dll
通过两个位置的LoadLibrary
加载。什么情况会导致静态变量m_instance
出现多个副本?
app.exe
/ \
static/shared_lib static/shared_lib
\ /
SingletonAndOtherStuff.dll
app.exe
/ \
/ \
static/shared_lib static/shared_lib
| |
SingletonAndOtherStuff.dll SingletonAndOtherStuff.dll
STATIC
和 SHARED
之间存在其他组合或更长的 app.exe
和 SingletonAndOtherStuff.dll
库链,是否会导致单例的多个实例?我只是在寻找一个解释,解释在 dll 加载两次的情况下,什么会导致此模式失败。
好吧,让我们一点一点地分解。在处理共享库时,您面临的这种困境在开发人员中很常见,而单例的存在只会增加另一层复杂性。感谢您提供详细的背景信息;我会处理每个部分。
首先,我们的单例指针是静态的,一旦我们的 DLL 被加载,它的位置就被保留在内存中。这个空间保持唯一,确保我们的单例在 DLL 仅加载一次时保持单一。
事情是这样的:即使您在同一个进程中重复调用同一个 DLL 的 LoadLibrary,操作系统也不会每次都重新加载它。它很聪明,只是增加了其内部引用计数。这确保了我们的单例不会被克隆。
你已经绘制出的模式:
app.exe
/ \
static/shared_lib static/shared_lib
\ /
SingletonAndOtherStuff.dll
即使这两个静态/共享库都召唤了
SingletonAndOtherStuff.dll
,它在内存中仍然保持不变。所以,我们的伙伴singleton
仍然是独一无二的。
现在,考虑这个设置:
app.exe
/ \
/ \
static/shared_lib static/shared_lib
| |
SingletonAndOtherStuff.dll SingletonAndOtherStuff.dll
如果您有两个相同的 DLL,但它们位于磁盘上的不同地址并且都被加载,则操作系统将它们视为不同的实体。这意味着它们各自在内存中拥有自己的位置,从而导致我们的
singleton
被复制。是的,你最终会得到两个单身人士。
图书馆链变得多么复杂并不重要。关键是什么?
SingletonAndOtherStuff.dll
被加载多少次,从磁盘上的哪些位置加载。不同的位置意味着不同的单例实例。
总结一下:当我们的 DLL 从一个进程内的单个位置被邀请一次时,单例会按预期运行。但是,如果您从多个地址提取同一个 DLL,即使它们是副本,您也会获得多个单例。这是共享库的一个奇怪的方面,在进入这样的领域时掌握这些动态至关重要。