使用 C 运行时

本文介绍如何使用 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,

  1. 初始化函数必须命名 DllMain() ,并且必须使用链接器选项指定入口点 -entry:_DllMainCRTStartup@12

  2. 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()则有两种方法:

  1. 如果没有执行初始化代码的入口函数,请指定 CRT_INIT() 为 DLL 的入口点。 假设已包含NTWIN32。MAK(定义为 DLLENTRY @12)将选项添加到 DLL 的链接行:-entry:_CRT_INIT$(DLLENTRY)

  2. 如果具有自己的 DLL 入口点,请在入口点中执行以下操作:

    1. 将此原型用于 CRT_INIT()BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);

      有关返回值的信息 CRT_INIT() ,请参阅 DllEntryPoint 文档;返回相同的值。

    2. 打开 DLL_PROCESS_ATTACHDLL_THREAD_ATTACH (请参阅 Win32 API 参考中的 DllEntryPoint,了解有关这些标志的详细信息), CRT_INIT()首先,在调用任何 C 运行时函数或执行任何浮点操作之前调用。

    3. 调用自己的进程/线程初始化/终止代码。

    4. DLL_PROCESS_DETACH 调用所有 DLL_THREAD_DETACHC 运行时函数并完成所有浮点操作之后,最后调用 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)。