本文介绍如何使用 C 运行时。
原始产品版本: Visual C++
原始 KB 编号: 94248
第 1 部分:C 运行时库的三种形式可用
Win32 SDK 提供的 C 运行时库有三种形式:
LIBC。LIB 是单线程程序的静态链接库。
LIBCMT。LIB 是一个静态链接库,支持多线程程序。
CRTDLL。LIB 是CRTDLL.DLL的导入库,还支持多线程程序。 CRTDLL.DLL本身是 Windows NT 的一部分。
Microsoft Visual C++ 32 位版本也包含这三种形式,但是 DLL 中的 CRT 也名为 MSVCRT。自由。 DLL 是可再发行的。 其名称取决于 VC++ 的版本(即MSVCRT10.DLL或MSVCRT20.DLL)。 但请注意,Win32s 不支持MSVCRT10.DLL,而 CRTDLL 则不支持。Win32s 支持 LIB。 MSVCRT20.DLL有两个版本:一个用于 Windows NT,另一个用于 Win32。
第 2 部分:生成 DLL 时使用 CRT 库
生成使用任何 C 运行时库的 DLL 时,为了确保正确初始化 CRT,
初始化函数必须命名
DllMain()
,并且必须使用链接器选项指定入口点-entry:_DllMainCRTStartup@12
或
DLL 的入口点必须显式调用
CRT_INIT()
进程附加和进程分离。
这允许 C 运行时库在进程或线程附加到 DLL 时正确分配和初始化 C 运行时数据,以便在进程与 DLL 分离时正确清理 C 运行时数据,以及正确构造和析构 DLL 中的全局C++对象。
Win32 SDK 示例均使用第一种方法。 以它们为例。 另请参阅 Win32 程序员参考和 DllEntryPoint()
Visual C++ 文档 DllMain()
。 请注意, DllMainCRTStartup()
调用 CRT_INIT()
并 CRT_INIT()
调用应用程序的 DllMain(如果存在)。
如果想要使用第二种方法并自行调用 CRT 初始化代码,而不是使用 DllMainCRTStartup()
, DllMain()
则有两种方法:
如果没有执行初始化代码的入口函数,请指定
CRT_INIT()
为 DLL 的入口点。 假设已包含NTWIN32。MAK(定义为DLLENTRY
@12)将选项添加到 DLL 的链接行:-entry:_CRT_INIT$(DLLENTRY)
或
如果具有自己的 DLL 入口点,请在入口点中执行以下操作:
将此原型用于
CRT_INIT()
:BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
有关返回值的信息
CRT_INIT()
,请参阅 DllEntryPoint 文档;返回相同的值。打开
DLL_PROCESS_ATTACH
和DLL_THREAD_ATTACH
(请参阅 Win32 API 参考中的 DllEntryPoint,了解有关这些标志的详细信息),CRT_INIT()
首先,在调用任何 C 运行时函数或执行任何浮点操作之前调用。调用自己的进程/线程初始化/终止代码。
在
DLL_PROCESS_DETACH
调用所有DLL_THREAD_DETACH
C 运行时函数并完成所有浮点操作之后,最后调用CRT_INIT()
。
请务必传递给 CRT_INIT()
入口点的所有参数; CRT_INIT()
需要这些参数,因此,如果省略这些参数(特别是需要 fdwReason 来确定是否需要进程初始化或终止),这些参数可能无法可靠地工作。
下面是一个主干示例入口点函数,用于显示 DLL 入口点中何时以及如何调用这些 CRT_INIT()
调用:
BOOL WINAPI DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason,
LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH || fdwReason == DLL_THREAD_ATTACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
if (fdwReason == DLL_PROCESS_DETACH || fdwReason == DLL_THREAD_DETACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
return(TRUE);
}
注意
如果使用 DllMain()
和 -entry:_DllMainCRTStartup@12
。
第 3 节:使用NTWIN32。MAK 以简化生成过程
NTWIN32中定义了宏。MAK 可用于简化生成文件并确保正确生成,以避免冲突。 因此,Microsoft强烈建议使用NTWIN32。MAK 及其宏。
对于编译,请使用: $(cvarsdll) for apps/DLLs using CRT in a DLL
。
若要链接,请使用下列项之一:
$(conlibsdll) for console apps/DLLs using CRT in a DLL
$(guilibsdll) for GUI apps using CRT in a DLL
第 4 部分:使用多个 CRT 库时遇到的问题
如果发出 C 运行时调用的应用程序链接到也进行 C 运行时调用的 DLL,请注意,如果它们都与静态链接的 C 运行时库之一(LIBC)链接。LIB 或 LIBCMT。LIB),.EXE和 DLL 将具有所有 C 运行时函数和全局变量的单独副本。 这意味着 C 运行时数据不能在.EXE和 DLL 之间共享。 因此可能出现的一些问题包括:
将缓冲流句柄从 .EXE/DLL 传递到另一个模块
在 .EXE/DLL 中使用 C 运行时调用分配内存,并在其他模块中重新分配或释放内存
在 .EXE/DLL 中检查或设置全局 errno 变量的值,并期望它在另一个模块中相同。 相关问题是在发生 C 运行时错误的相反模块中调用
perror()
,因为perror()
使用 errno。
若要避免这些问题,请将.EXE和 DLL 与 CRTDLL 链接。LIB 或 MSVCRT。LIB 允许.EXE和 DLL 使用 DLL 中 CRT 中包含的常见函数和数据集,C 运行时数据(例如流句柄)随后可由.EXE和 DLL 共享。
第 5 节:混合库类型
可以将 DLL 与 CRTDLL 链接。LIB/MSVCRT。如果避免混合 CRT 数据结构并将 CRT 文件句柄或 CRT FILE* 指针传递给其他模块,则无论.EXE与什么链接,LIB。
混合库类型时,应遵循以下各项:
CRT 文件句柄只能由创建它们的 CRT 模块操作。
CRT FILE* 指针只能由创建它们的 CRT 模块操作。
使用 CRT 函数
malloc()
分配的内存只能由分配它的 CRT 模块释放或重新分配。
若要说明这一点,请考虑以下示例:
- .EXE与 MSVCRT 链接。自由
- DLL A 与 LIBCMT 链接。自由
- DLL B 与 CRTDLL 链接。自由
如果.EXE使用_create()
_open()
或创建 CRT 文件句柄,则此文件句柄只能在.EXE文件中传递给 _lseek()
、_read()
、_write()
,_close()
等等。 不要将此 CRT 文件句柄传递给任一 DLL。 不要将从 DLL 获取的 CRT 文件句柄传递到其他 DLL 或.EXE。
如果 DLL A 分配了内存 malloc()
块,则只有 DLL A 可以调用 free()
, _expand()
或 realloc()
对该块进行操作。 无法从 DLL A 调用 malloc()
并尝试从 .EXE 或 DLL B 释放该块。
注意
如果所有三个模块都与 CRTDLL 链接。LIB 或所有三者都与 MSVCRT 相关联。LIb,这些限制不适用。
将 DLL 与 LIBC 链接时。LIB,请注意,如果有可能由多线程程序调用此类 DLL,DLL 将不支持同时在 DLL 中运行的多个线程,这可能会导致重大问题。 如果 DLL 可能由多线程程序调用,请确保将其链接到支持多线程程序的库之一(LIBCMT)。LIB、CRTDLL。LIB 或 MSVCRT。LIB)。