在哪些情况下动态 CRT 在调用用户提供的 DllMain 时尚未初始化?
In which cases is the dynamic CRT not already initialized on call to user supplied DllMain?
序言: 这个问题特别关注并且只关注通过 [=10= 使用的 dynamic CRT 的行为].它不质疑任何其他建议的有效性。 DllMain
.
As we've been told:(参考:Dynamic-Link 库最佳实践,MSDN,2006 年 5 月 17 日)
You should never perform the following tasks from within DllMain:
- ...
- Use the memory management function from the dynamic C Run-Time (CRT). If the CRT DLL is not initialized, calls to these functions can cause the process to crash.
- ...
其他have questioned this already (as in: questioned the validity of the argument) and since we helpfully get an answer there, we can clearly see one rather simple case where this could potentially cause troubles:
You are working from the assumption that the entrypoint for a DLL is always _DllMainCRTStartup. This is not the case, it is merely the linker's default. It can be anything a programmer wants it to be, swiftly and easily changed with the linker's /ENTRYPOINT option. There is nothing that Microsoft can do to prevent this.
所以这些是这个问题的要素:
有没有任何其他情况linking /MD
而没有 提供自定义 /ENTRYPOINT
,其中 动态 CRT 不应该完全初始化?
- 具体来说,如果所有 DLL 加载仅通过 "static dependencies" 完成,即根本没有显式
LoadLibrary
调用,只是 link 时间 DLL 依赖项。
奖励:MS 文档专门调用了 "memory management function",但据我所知,如果 CRT 未初始化,可能any CRT 函数应该是不安全的。为什么要这样调出内存管理函数?
3号:
Wrt。到习惯 ENTRYPOINT
:我不太明白这怎么会是一个如此重要的场景,以至于它需要在没有进一步限定的情况下包含在“不要在 DllMain 中执行”列表中。 IFF 我提供自定义入口点,我负责正确初始化 CRT,否则 CRT 将无法在我的程序中的任何地方正常工作,不只是 DllMain。为什么要专门调出DllMain部分呢?
这让我回到问题 1,即如果这是 dynamic CRT 出现问题的唯一场景。澄清或开眼界为什么这对 DllMain 比对 DLL 的其他部分更重要,或者我可能在这里错过的东西,将不胜感激。
奖金link秒:
- When are global objects constructed and destructed by Visual C++?
- DllMain : a horror story
- Calling LoadLibrary from DllMain
理由:我觉得我应该添加这个作为背景:我问这个是因为我们有大量数量通过全局 C++ 对象构造函数执行操作的代码数量。多年来,实际崩溃的事情已经过审查(如并发 LoadLibrary
、线程同步等),但所有代码都充满了 std
C++ 和 CRT 函数,它们很高兴一直在为Windows XP、7 和 Windows 10 年,没有任何已知问题。虽然我不会哭"but it just works",但我必须在这里做一个工程判断,尝试"fix"是否有任何中短期价值] 这个。因此,如果可以将肥皂盒的答案留在他们的盒子里,我将不胜感激。
Is there any other situation when linking /MD
and not supplying a
custom /ENTRYPOINT
, where the dynamic CRT ought to not be fully
initialized?
首先是一些符号:
- X 有静态导入(取决于)Y 和 Z :
X[ Y, Z]
- X 入口点:
X_DllMain
X_DllMain
调用 LoadLibrary(Y)
: X<Y>
当我们使用 /MD
时 - 我们在单独的 DLL 中使用 crt。在此上下文中初始化意味着已经调用了 crt DLL 的入口点。所以问题可以更笼统和明确:
来自 X[Y]
=> Y_DllMain
在 X_DllMain
之前调用?
一般情况下没有。因为可以是循环依赖,当 Y[X]
或 Y[Z[X]]
.
最知名的示例 user32[gdi32]
,而 gdi32[user32]
或在 win10 中取决于 gdi32[gdi32full[user32]]
。所以必须先调用 user32_DllMain
或 gdi32_DllMain
?但是很明显,任何 crt DLL 都不依赖于我们的自定义 DLL。所以让我们排除循环依赖的情况。
当加载器加载模块时 X - 它加载所有它的依赖模块(和它的依赖 - 这是递归过程),如果它已经不在内存中,那么加载器构建调用图, 并开始调用模块入口点。显而易见,如果 A[B]
,加载程序总是在 A_DllMain
之前尝试调用 B_DllMain
(调用顺序未定义时的循环依赖除外)。但是哪些模块将在调用图中?所有 X 依赖模块?当然不。当我们开始加载 X 时,其中一些模块可能已经在内存中(已加载)。所以它的入口点已经用 DLL_PROCESS_ATTACH
调用了,现在不能再调用它了。 xp、vista、win7使用此策略:
当我们加载 X:
- 在内存中加载或定位所有依赖模块
- 只加载 new 模块的调用入口点(在 X 之后)。
- if
A[B]
- 在 A_DllMain
之前调用 B_DllMain
示例:已加载 X[Y[W[Z]], Z]
//++begin load X
Z_DllMain
W_DllMain
Y_DllMain
X_DllMain
// --end load X
但是这种情况没有考虑下一种情况——一些模块可能已经在内存中,但它的入口点还没有被调用。这怎么会发生?
如果某些模块入口点调用 LoadLibrary
.
,就会发生这种情况
示例 - 已加载 X[Y<W[ Z]>, Z]
//++begin load X
Y_DllMain
//++begin load W
W_DllMain
//--end load W
Z_DllMain
X_DllMain
// --end load X
所以 W_DllMain
将在 Z_DllMain
之前被调用,尽管 W[Z]
。正是因为不推荐从 DLL 入口点调用 LoadLibrary
。
但来自 Dynamic-Link 库最佳实践
This can cause a deadlock or a crash.
关于死锁的说法是不正确的——当然任何死锁基本上都不可能。在哪里 ?如何 ?我们已经在 DLL 入口点内持有加载程序锁,并且可以递归获取此锁。真的可以死机(win8之前)
或另一个 false:
Call ExitThread
. Exiting a thread during DLL detach can cause the
loader lock to be acquired again, causing a deadlock or a crash.
- can 导致再次获取加载程序锁 - 不是 can 但 always
- 导致死锁 - false - 我们已经持有这个锁
- 一次崩溃 - 不会发生任何崩溃,否则会发生一次 false
但实际上是 - 线程退出时没有空闲加载器锁。它变得永远忙碌。结果,任何新的线程创建或退出,任何新的 DLL 加载或卸载,或者只是 ExitProcess
调用 - 在尝试获取加载程序锁时挂起。所以这里确实会出现死锁,但不会在通话期间出现 ExitThread
- 后者。
当然还有有趣的注意事项 - windows 本身从 DllMain
调用 LoadLibrary
- user32.dll 总是调用 LoadLibrary
for imm32.dll 从它的入口点(在 win10 上仍然如此)
但从 win8(或 win8.1)开始,加载程序在处理依赖模块时变得更加智能。现在 2 已更改
2. 调用 new 加载(X 之后)模块的入口点,或者如果模块尚未初始化。
所以在现代 windows (8+) for load X[Y<W[Z]>, Z]
//++begin load X
Y_DllMain
//++begin load W
Z_DllMain
W_DllMain
//--end load W
X_DllMain
// -- end load X
Z 初始化将移动到 W 加载调用图。结果现在一切都正确了。
为了测试这个我们可以构建下一个解决方案:test.exe[ kernel32, D1< D2[kernel32, msvcrt] >, msvcrt ]
- D2 仅从 kernel32 和 msvcrt 导入并导出
SomeFunc
- D1 仅从 kernel32 导入并从其入口点调用
LoadLibraryW(L"D2")
,然后调用 D2.SomeFunc
- test.exe 从 kernel32、D1 和 [=166= 导入]msvcrt
(完全按照这个顺序!这很重要 - D1 必须 before msvcrt 在导入中,为此需要在链接器命令行中的 msvcrt 之前设置 D1)
因为 D1 入口点将在 msvcrt 之前调用。这是正常的 - D1 不依赖于 msvcrt
但是当 D1 从入口点加载 D2 时,变得有趣
代码 D2.dll ( /NODEFAULTLIB kernel32.lib msvcrt.lib
)
#include <Windows.h>
extern "C"
{
__declspec(dllimport) int __cdecl sprintf(PSTR buf, PCSTR format, ...);
}
BOOLEAN WINAPI MyEp( HMODULE , DWORD ul_reason_for_call, PVOID )
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
OutputDebugStringA("D2.DllMain\n");
}
return TRUE;
}
INT_PTR WINAPI SomeFunc()
{
__pragma(message(__FUNCDNAME__))
char buf[32];
// this is only for link to msvcrt.dll
sprintf(buf, "D2.SomeFunc\n");
OutputDebugStringA(buf);
return 0;
}
#ifdef _WIN64
#define FuncName "?SomeFunc@@YA_JXZ"
#else
#define FuncName "?SomeFunc@@YGHXZ"
#endif
__pragma(comment(linker, "/export:" FuncName ",@1,NONAME,PRIVATE"))
代码 D1.dll ( /NODEFAULTLIB kernel32.lib
)
#include <Windows.h>
#pragma warning(disable : 4706)
BOOLEAN WINAPI MyEp( HMODULE hmod, DWORD ul_reason_for_call, PVOID )
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
OutputDebugStringA("D1.DllMain\n");
if (hmod = LoadLibraryW(L"D2"))
{
if (FARPROC fp = GetProcAddress(hmod, (PCSTR)1))
{
fp();
}
}
}
return TRUE;
}
INT_PTR WINAPI SomeFunc()
{
__pragma(message(__FUNCDNAME__))
OutputDebugStringA("D1.SomeFunc\n");
return 0;
}
#ifdef _WIN64
#define FuncName "?SomeFunc@@YA_JXZ"
#else
#define FuncName "?SomeFunc@@YGHXZ"
#endif
__pragma(comment(linker, "/export:" FuncName ",@1,NONAME"))
exe 的代码 ( /NODEFAULTLIB kernel32.lib D1.lib msvcrt.lib
)
#include <Windows.h>
extern "C"
{
__declspec(dllimport) int __cdecl sprintf(PSTR buf, PCSTR format, ...);
}
__declspec(dllimport) INT_PTR WINAPI SomeFunc();
void ep()
{
char buf[32];
// this is only for link to msvcrt.dll
sprintf(buf, "exe entry\n");
OutputDebugStringA(buf);
ExitProcess((UINT)SomeFunc());
}
xp 输出:
LDR: D1.dll loaded - Calling init routine
D1.DllMain
Load: D2.dll
LDR: D2.dll loaded - Calling init routine
D2.DllMain
D2.SomeFunc
LDR: msvcrt.dll loaded - Calling init routine
exe entry
D1.SomeFunc
对于 win7:
LdrpRunInitializeRoutines - INFO: Calling init routine for DLL "D1.dll"
D1.DllMain
Load: D2.dll
LdrpRunInitializeRoutines - INFO: Calling init routine for DLL "D2.DLL"
D2.DllMain
D2.SomeFunc
LdrpRunInitializeRoutines - "msvcrt.dll"
exe entry
D1.SomeFunc
在这两种情况下调用流程是相同的 - D2.DllMain
在 之前 msvcrt 调用入口点,尽管 D2[msvcrt]
但在 win8.1 和 win10 上 - 调用流程是另一个:
LdrpInitializeNode - INFO: Calling init routine for DLL "D1.dll"
D1.DllMain
LdrpInitializeNode - INFO: Calling init routine for DLL "msvcrt.dll"
LdrpInitializeNode - INFO: Calling init routine for DLL "D2.DLL"
D2.DllMain
D2.SomeFunc
exe entry
D1.SomeFunc
D2 入口点调用 after msvcrt 初始化。
那么结论是什么?
if 当模块 X[Y]
被加载并且内存中没有未初始化 Y - Y_DllMain
将在 之前被调用 X_DllMain
。或者换句话说 - 如果没有人从 DLL 入口点调用 LoadLibrary(X)
(或 LoadLibrary(Z[X])
)。因此,如果您的 DLL 将以 "normal" 方式加载(不是通过从 DllMain
调用 LoadLibrary
或在某些 dll 加载事件中从驱动程序注入) - 您可以确定 crt 入口点已经调用( crt 已初始化)
more - 如果你在 win8.1+ 上 运行 - 并且 X[Y]
已加载 - Y_DllMain
将始终在 before X_DllMain
.
现在关于您的 dll 中的自定义 /ENTRYPOINT
。
即使您在单独的 DLL 中使用 crt - 一些小的 crt 代码将静态链接到您的模块 DllMainCRTStartup
- 它按名称调用您的函数 DllMain
(这不是入口点) .所以如果是动态 crt - 我们真的有 2 个 crt 部分 - 主要部分在单独的 DLL 中,它将在 在 调用你的 DLL 入口点之前初始化( 如果不是特殊情况我描述的更高和 win7、vista、xp)。和小的静态部分(模块内的代码)。何时调用此静态部分已经完全取决于您。这部分 DllMainCRTStartup
进行一些内部初始化,在您的代码中初始化全局对象 (initterm
) 并调用 DllMain
,之后 return (在 dll 分离上)调用全局对象的析构函数。 .
如果您在 DLL 中设置自定义入口点 - 此时单独的 DLL 中的 crt 已经初始化,但您的静态 crt 没有(作为和全局对象)。从这个自定义入口点,您需要调用 DllMainCRTStartup
序言: 这个问题特别关注并且只关注通过 [=10= 使用的 dynamic CRT 的行为].它不质疑任何其他建议的有效性。 DllMain
.
As we've been told:(参考:Dynamic-Link 库最佳实践,MSDN,2006 年 5 月 17 日)
You should never perform the following tasks from within DllMain:
- ...
- Use the memory management function from the dynamic C Run-Time (CRT). If the CRT DLL is not initialized, calls to these functions can cause the process to crash.
- ...
其他have questioned this already (as in: questioned the validity of the argument) and since we helpfully get an answer there, we can clearly see one rather simple case where this could potentially cause troubles:
You are working from the assumption that the entrypoint for a DLL is always _DllMainCRTStartup. This is not the case, it is merely the linker's default. It can be anything a programmer wants it to be, swiftly and easily changed with the linker's /ENTRYPOINT option. There is nothing that Microsoft can do to prevent this.
所以这些是这个问题的要素:
有没有任何其他情况linking
/MD
而没有 提供自定义/ENTRYPOINT
,其中 动态 CRT 不应该完全初始化?- 具体来说,如果所有 DLL 加载仅通过 "static dependencies" 完成,即根本没有显式
LoadLibrary
调用,只是 link 时间 DLL 依赖项。
- 具体来说,如果所有 DLL 加载仅通过 "static dependencies" 完成,即根本没有显式
奖励:MS 文档专门调用了 "memory management function",但据我所知,如果 CRT 未初始化,可能any CRT 函数应该是不安全的。为什么要这样调出内存管理函数?
3号:
Wrt。到习惯
ENTRYPOINT
:我不太明白这怎么会是一个如此重要的场景,以至于它需要在没有进一步限定的情况下包含在“不要在 DllMain 中执行”列表中。 IFF 我提供自定义入口点,我负责正确初始化 CRT,否则 CRT 将无法在我的程序中的任何地方正常工作,不只是 DllMain。为什么要专门调出DllMain部分呢?这让我回到问题 1,即如果这是 dynamic CRT 出现问题的唯一场景。澄清或开眼界为什么这对 DllMain 比对 DLL 的其他部分更重要,或者我可能在这里错过的东西,将不胜感激。
奖金link秒:
- When are global objects constructed and destructed by Visual C++?
- DllMain : a horror story
- Calling LoadLibrary from DllMain
理由:我觉得我应该添加这个作为背景:我问这个是因为我们有大量数量通过全局 C++ 对象构造函数执行操作的代码数量。多年来,实际崩溃的事情已经过审查(如并发 LoadLibrary
、线程同步等),但所有代码都充满了 std
C++ 和 CRT 函数,它们很高兴一直在为Windows XP、7 和 Windows 10 年,没有任何已知问题。虽然我不会哭"but it just works",但我必须在这里做一个工程判断,尝试"fix"是否有任何中短期价值] 这个。因此,如果可以将肥皂盒的答案留在他们的盒子里,我将不胜感激。
Is there any other situation when linking
/MD
and not supplying a custom/ENTRYPOINT
, where the dynamic CRT ought to not be fully initialized?
首先是一些符号:
- X 有静态导入(取决于)Y 和 Z :
X[ Y, Z]
- X 入口点:
X_DllMain
X_DllMain
调用LoadLibrary(Y)
:X<Y>
当我们使用 /MD
时 - 我们在单独的 DLL 中使用 crt。在此上下文中初始化意味着已经调用了 crt DLL 的入口点。所以问题可以更笼统和明确:
来自 X[Y]
=> Y_DllMain
在 X_DllMain
之前调用?
一般情况下没有。因为可以是循环依赖,当 Y[X]
或 Y[Z[X]]
.
最知名的示例 user32[gdi32]
,而 gdi32[user32]
或在 win10 中取决于 gdi32[gdi32full[user32]]
。所以必须先调用 user32_DllMain
或 gdi32_DllMain
?但是很明显,任何 crt DLL 都不依赖于我们的自定义 DLL。所以让我们排除循环依赖的情况。
当加载器加载模块时 X - 它加载所有它的依赖模块(和它的依赖 - 这是递归过程),如果它已经不在内存中,那么加载器构建调用图, 并开始调用模块入口点。显而易见,如果 A[B]
,加载程序总是在 A_DllMain
之前尝试调用 B_DllMain
(调用顺序未定义时的循环依赖除外)。但是哪些模块将在调用图中?所有 X 依赖模块?当然不。当我们开始加载 X 时,其中一些模块可能已经在内存中(已加载)。所以它的入口点已经用 DLL_PROCESS_ATTACH
调用了,现在不能再调用它了。 xp、vista、win7使用此策略:
当我们加载 X:
- 在内存中加载或定位所有依赖模块
- 只加载 new 模块的调用入口点(在 X 之后)。
- if
A[B]
- 在A_DllMain
之前调用
B_DllMain
示例:已加载 X[Y[W[Z]], Z]
//++begin load X
Z_DllMain
W_DllMain
Y_DllMain
X_DllMain
// --end load X
但是这种情况没有考虑下一种情况——一些模块可能已经在内存中,但它的入口点还没有被调用。这怎么会发生?
如果某些模块入口点调用 LoadLibrary
.
示例 - 已加载 X[Y<W[ Z]>, Z]
//++begin load X
Y_DllMain
//++begin load W
W_DllMain
//--end load W
Z_DllMain
X_DllMain
// --end load X
所以 W_DllMain
将在 Z_DllMain
之前被调用,尽管 W[Z]
。正是因为不推荐从 DLL 入口点调用 LoadLibrary
。
但来自 Dynamic-Link 库最佳实践
This can cause a deadlock or a crash.
关于死锁的说法是不正确的——当然任何死锁基本上都不可能。在哪里 ?如何 ?我们已经在 DLL 入口点内持有加载程序锁,并且可以递归获取此锁。真的可以死机(win8之前)
或另一个 false:
Call
ExitThread
. Exiting a thread during DLL detach can cause the loader lock to be acquired again, causing a deadlock or a crash.
- can 导致再次获取加载程序锁 - 不是 can 但 always
- 导致死锁 - false - 我们已经持有这个锁
- 一次崩溃 - 不会发生任何崩溃,否则会发生一次 false
但实际上是 - 线程退出时没有空闲加载器锁。它变得永远忙碌。结果,任何新的线程创建或退出,任何新的 DLL 加载或卸载,或者只是 ExitProcess
调用 - 在尝试获取加载程序锁时挂起。所以这里确实会出现死锁,但不会在通话期间出现 ExitThread
- 后者。
当然还有有趣的注意事项 - windows 本身从 DllMain
调用 LoadLibrary
- user32.dll 总是调用 LoadLibrary
for imm32.dll 从它的入口点(在 win10 上仍然如此)
但从 win8(或 win8.1)开始,加载程序在处理依赖模块时变得更加智能。现在 2 已更改
2. 调用 new 加载(X 之后)模块的入口点,或者如果模块尚未初始化。
所以在现代 windows (8+) for load X[Y<W[Z]>, Z]
//++begin load X
Y_DllMain
//++begin load W
Z_DllMain
W_DllMain
//--end load W
X_DllMain
// -- end load X
Z 初始化将移动到 W 加载调用图。结果现在一切都正确了。
为了测试这个我们可以构建下一个解决方案:test.exe[ kernel32, D1< D2[kernel32, msvcrt] >, msvcrt ]
- D2 仅从 kernel32 和 msvcrt 导入并导出
SomeFunc
- D1 仅从 kernel32 导入并从其入口点调用
LoadLibraryW(L"D2")
,然后调用D2.SomeFunc
- test.exe 从 kernel32、D1 和 [=166= 导入]msvcrt
(完全按照这个顺序!这很重要 - D1 必须 before msvcrt 在导入中,为此需要在链接器命令行中的 msvcrt 之前设置 D1)
因为 D1 入口点将在 msvcrt 之前调用。这是正常的 - D1 不依赖于 msvcrt 但是当 D1 从入口点加载 D2 时,变得有趣
代码 D2.dll ( /NODEFAULTLIB kernel32.lib msvcrt.lib
)
#include <Windows.h>
extern "C"
{
__declspec(dllimport) int __cdecl sprintf(PSTR buf, PCSTR format, ...);
}
BOOLEAN WINAPI MyEp( HMODULE , DWORD ul_reason_for_call, PVOID )
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
OutputDebugStringA("D2.DllMain\n");
}
return TRUE;
}
INT_PTR WINAPI SomeFunc()
{
__pragma(message(__FUNCDNAME__))
char buf[32];
// this is only for link to msvcrt.dll
sprintf(buf, "D2.SomeFunc\n");
OutputDebugStringA(buf);
return 0;
}
#ifdef _WIN64
#define FuncName "?SomeFunc@@YA_JXZ"
#else
#define FuncName "?SomeFunc@@YGHXZ"
#endif
__pragma(comment(linker, "/export:" FuncName ",@1,NONAME,PRIVATE"))
代码 D1.dll ( /NODEFAULTLIB kernel32.lib
)
#include <Windows.h>
#pragma warning(disable : 4706)
BOOLEAN WINAPI MyEp( HMODULE hmod, DWORD ul_reason_for_call, PVOID )
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
OutputDebugStringA("D1.DllMain\n");
if (hmod = LoadLibraryW(L"D2"))
{
if (FARPROC fp = GetProcAddress(hmod, (PCSTR)1))
{
fp();
}
}
}
return TRUE;
}
INT_PTR WINAPI SomeFunc()
{
__pragma(message(__FUNCDNAME__))
OutputDebugStringA("D1.SomeFunc\n");
return 0;
}
#ifdef _WIN64
#define FuncName "?SomeFunc@@YA_JXZ"
#else
#define FuncName "?SomeFunc@@YGHXZ"
#endif
__pragma(comment(linker, "/export:" FuncName ",@1,NONAME"))
exe 的代码 ( /NODEFAULTLIB kernel32.lib D1.lib msvcrt.lib
)
#include <Windows.h>
extern "C"
{
__declspec(dllimport) int __cdecl sprintf(PSTR buf, PCSTR format, ...);
}
__declspec(dllimport) INT_PTR WINAPI SomeFunc();
void ep()
{
char buf[32];
// this is only for link to msvcrt.dll
sprintf(buf, "exe entry\n");
OutputDebugStringA(buf);
ExitProcess((UINT)SomeFunc());
}
xp 输出:
LDR: D1.dll loaded - Calling init routine
D1.DllMain
Load: D2.dll
LDR: D2.dll loaded - Calling init routine
D2.DllMain
D2.SomeFunc
LDR: msvcrt.dll loaded - Calling init routine
exe entry
D1.SomeFunc
对于 win7:
LdrpRunInitializeRoutines - INFO: Calling init routine for DLL "D1.dll"
D1.DllMain
Load: D2.dll
LdrpRunInitializeRoutines - INFO: Calling init routine for DLL "D2.DLL"
D2.DllMain
D2.SomeFunc
LdrpRunInitializeRoutines - "msvcrt.dll"
exe entry
D1.SomeFunc
在这两种情况下调用流程是相同的 - D2.DllMain
在 之前 msvcrt 调用入口点,尽管 D2[msvcrt]
但在 win8.1 和 win10 上 - 调用流程是另一个:
LdrpInitializeNode - INFO: Calling init routine for DLL "D1.dll"
D1.DllMain
LdrpInitializeNode - INFO: Calling init routine for DLL "msvcrt.dll"
LdrpInitializeNode - INFO: Calling init routine for DLL "D2.DLL"
D2.DllMain
D2.SomeFunc
exe entry
D1.SomeFunc
D2 入口点调用 after msvcrt 初始化。
那么结论是什么?
if 当模块 X[Y]
被加载并且内存中没有未初始化 Y - Y_DllMain
将在 之前被调用 X_DllMain
。或者换句话说 - 如果没有人从 DLL 入口点调用 LoadLibrary(X)
(或 LoadLibrary(Z[X])
)。因此,如果您的 DLL 将以 "normal" 方式加载(不是通过从 DllMain
调用 LoadLibrary
或在某些 dll 加载事件中从驱动程序注入) - 您可以确定 crt 入口点已经调用( crt 已初始化)
more - 如果你在 win8.1+ 上 运行 - 并且 X[Y]
已加载 - Y_DllMain
将始终在 before X_DllMain
.
现在关于您的 dll 中的自定义 /ENTRYPOINT
。
即使您在单独的 DLL 中使用 crt - 一些小的 crt 代码将静态链接到您的模块 DllMainCRTStartup
- 它按名称调用您的函数 DllMain
(这不是入口点) .所以如果是动态 crt - 我们真的有 2 个 crt 部分 - 主要部分在单独的 DLL 中,它将在 在 调用你的 DLL 入口点之前初始化( 如果不是特殊情况我描述的更高和 win7、vista、xp)。和小的静态部分(模块内的代码)。何时调用此静态部分已经完全取决于您。这部分 DllMainCRTStartup
进行一些内部初始化,在您的代码中初始化全局对象 (initterm
) 并调用 DllMain
,之后 return (在 dll 分离上)调用全局对象的析构函数。 .
如果您在 DLL 中设置自定义入口点 - 此时单独的 DLL 中的 crt 已经初始化,但您的静态 crt 没有(作为和全局对象)。从这个自定义入口点,您需要调用 DllMainCRTStartup