Windows DLL 动态链接库 | 类型 / 链接方式 / 创建 / 导入 / 导出

注:本文为 “Windows DLL” 相关文章翻译合集。

未校。


Windows 动态链接库 - DLL 第一部分:故事

Sunbreak

2021-02-23

这篇文章主要介绍了动态链接库(DLL)的相关内容,包括 DLL 与 C 语言运行时的故事、与应用程序的区别、使用优势、类型、链接方式(隐式和显式)、创建仅有资源的 DLL、导入和导出方法、初始化方式以及运行时的库行为等,并提供了相应的示例代码和说明。

原文地址:https://www.tenouk.com/ModuleBB.html
 
原文作者:
 
发布时间:约 2004 年前后

在这个模块中我们有什么?

  1. 动态链接库和 C 语言运行时故事
  2. 应用程序和 DLL 的区别
  3. 使用 DLL 的优势
  4. DLL 的类型
  5. 将一个可执行文件链接到一个 DLL
  6. 隐性使用链接
  7. 明确使用链接
  8. 确定使用哪种链接方法
  9. 隐性链接
  10. 明确链接
  11. 创建一个仅有资源的 DLL
  12. 进口和出口
  13. 使用.DEF 文件
  14. 使用 __declspec
  15. 使用 __declspec(dllimport) 导入应用程序
  16. 从 DLL 中导出
  17. 使用 __declspec(dllexport) 从 DLL 中导出。
  18. 初始化一个 DLL
  19. 初始化扩展 DLL(对于 MFC 程序)
  20. 初始化非 MFC DLLs
  21. 运行时的库行为

我的训练时间:xx 小时。在你开始之前,请阅读这里的 一些说明。DLL 的 Windows MFC 编程(GUI 编程)可以在 MFC GUI 编程步骤教程中找到。

应该获得的能力。

  • 能够理解、构建和运行动态链接库程序。
  • 能够体会到使用 DLL 与普通应用相比的好处。
  • 能够从 MSDN 文档中收集信息,以了解和使用 DLL。

动态链接库和 C 语言运行时的故事

:本模块是一个通用的 MSDN 文档,涉及 Visual C++ 上的 C/C++ 运行时库和 MFC。让我们先来了解一下全貌吧!

动态链接库(DLL)是一个可执行文件,作为一个共享的函数库。动态链接为一个进程提供了一种方法来调用一个不属于其可执行代码的函数。函数的可执行代码位于 DLL 中,它包含一个或多个函数,这些函数被编译、链接,并与使用它们的进程分开存储。DLL 还有利于数据和资源的共享。多个应用程序可以同时访问内存中一个 DLL 副本的内容。动态链接与静态链接的不同之处在于,它允许可执行模块(无论是.dll 还是.exe 文件)在运行时只包含定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库中获取所有被引用的函数,并将其与你的代码一起放入可执行文件中。使用动态链接代替静态链接有几个优点。DLL 可以节省内存,减少交换,节省磁盘空间,升级更容易,提供后市场支持,提供扩展 MFC 库类的机制,支持多语言程序,方便创建国际版本。

应用程序和 DLL 的区别

尽管 DLLs 和应用程序都是可执行的程序模块,但它们在几个方面是不同的。对于终端用户来说,最明显的区别是 DLL 不是可以直接执行的程序。从系统的角度来看,应用程序和 DLL 有两个根本的区别。

  1. 一个应用程序可以有多个实例同时在系统中运行,而一个 DLL 只能有一个实例。
  2. 应用程序可以拥有诸如堆栈、全局内存、文件句柄和消息队列等东西,但 DLL 不能。

使用 DLL 的优势

动态链接具有以下优点。

  1. 节省内存,减少交换。许多进程可以同时使用一个 DLL,在内存中共享一个 DLL 的副本。相比之下,Windows 必须为每个使用静态链接库构建的应用程序在内存中加载一份库代码。
  2. 节省了磁盘空间。许多应用程序可以在磁盘上共享 DLL 的单一副本。相比之下,每个使用静态链接库构建的应用程序都有库代码作为单独的副本链接到其可执行映像中。
  3. DLL 的升级更容易。当 DLL 中的函数发生变化时,只要函数的参数和返回值不发生变化,使用它们的应用程序就不需要重新编译或重新链接。而静态链接的对象代码则需要在函数改变时重新链接应用程序。
  4. 提供后市场支持。例如,可以修改显示驱动 DLL,以支持应用程序出厂时没有的显示器。
  5. 支持多语言程序。用不同编程语言编写的程序可以调用相同的 DLL 函数,只要程序遵循函数的调用约定即可。程序和 DLL 函数必须在以下方面兼容:函数期望其参数被推到堆栈上的顺序,是函数还是应用程序负责清理堆栈,以及是否有任何参数在寄存器中传递。
  6. 提供了一个扩展 MFC 库类的机制。您可以从现有的 MFC 类中派生出类,并将它们放在 MFC 扩展 DLL 中,供 MFC 应用程序使用。
  7. 简化国际版本的创建。通过在 DLL 中放置资源,可以更容易地创建应用程序的国际版本。您可以将您的应用程序的每个语言版本的字符串放置在一个单独的资源 DLL 中,并让不同语言版本加载适当的资源。

使用 DLL 的一个潜在的缺点是,应用程序并不是自成一体的;它依赖于一个单独的 DLL 模块的存在。

DLL 的类型

使用 Visual C++,你可以用 C 或 C++ 构建。

  1. 用 C 或 C++ 构建不使用微软基础类库(MFC)的 Win32 DLL。
  2. 你可以用 Win32 应用向导创建一个非 MFC DLL 项目。
  3. 通过 MFC DLL 向导可以获得 MFC 库本身,可以是静态链接库,也可以是一些 DLL。如果你的 DLL 使用的是 MFC,Visual C++ 支持三种不同的 DLL 开发方案。
    • 构建一个静态链接 MFC 的常规 DLL。
    • 构建一个动态链接 MFC 的常规 DLL。
    • 构建一个 MFC 扩展 DLL。这些总是动态链接 MFC。

将一个可执行文件链接到一个 DLL

可执行文件以两种方式之一链接到(或加载)DLL。

  1. 隐式链接
  2. 显式链接

隐式链接有时被称为静态加载或加载时动态链接。显式链接有时被称为动态加载或运行时动态链接。在隐式链接中,使用 DLL 的可执行文件链接到由 DLL 制作者提供的导入库(.LIB 文件)。当使用 DLL 的可执行程序被加载时,操作系统会加载该 DLL。客户端可执行文件调用 DLL 的导出函数,就像这些函数包含在可执行文件中一样。

使用显式链接时,使用 DLL 的可执行程序必须进行函数调用,以显式加载和卸载 DLL,并访问 DLL 的导出函数。客户端可执行文件必须通过函数指针来调用导出的函数。一个可执行文件可以用这两种链接方法使用同一个 DLL。此外,这些机制并不相互排斥,因为一个可执行文件可以隐式地链接到 DLL,而另一个可执行文件可以显式地附加到它。

使用隐式链接

要隐式链接到 DLL,可执行文件必须从 DLL 的提供者那里获得以下内容。

  1. 一个头文件(.H 文件),包含导出的函数和 / 或 C++ 类的声明。
  2. 要链接的导入库(.LIB 文件)。当 DLL 被构建时,链接器会创建导入库。
  3. 实际的 DLL(.DLL 文件)。

使用 DLL 的可执行文件必须在每个包含对导出函数调用的源文件中包含包含导出函数(或 C++ 类)的头文件。从编码的角度来看,对导出函数的函数调用就像其他函数调用一样。要建立调用的可执行文件,必须与导入库链接。如果你使用的是外部的 makefile,请指定导入库的文件名,在这里列出你要链接的其他对象(.OBJ)文件或库。操作系统在加载调用的可执行文件时,必须能够找到.DLL 文件。

使用显式链接

通过显式链接,应用程序必须在运行时调用一个函数来显式加载 DLL。要显式链接到一个 DLL,应用程序必须。

  1. 调用 LoadLibrary()(或类似的函数)来加载 DLL 并获得一个模块句柄。
  2. 调用 GetProcAddress()获得应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针来调用 DLL 的函数,编译器不会产生外部引用,所以不需要与导入库链接。
  3. 当处理完 DLL 后,调用 FreeLibrary()。

例如

typedef UINT(CALLBACK* LPFNDLLFUNC1)(DWORD, UINT);
...
HINSTANCE hDLL;               // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer
DWORD dwParam1;
UINT  uParam2, uReturnVal;

hDLL = LoadLibrary("MyDLL");
if(hDLL != NULL)
{
   
   lpfnDllFunc1 =(LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
   if(!lpfnDllFunc1)
   {
   
      //handle the error
      FreeLibrary(hDLL);
      return SOME_ERROR_CODE;
   }
   else
   {
   
      //call the function
      uReturnVal = lpfnDllFunc1(dwParam1, uParam2);
   }
}

确定使用哪种链接方法

隐性链接

当应用程序的代码调用导出的 DLL 函数时,会发生隐式链接。当调用可执行文件的源代码被编译或组装时,DLL 函数调用会在对象代码中产生一个外部函数引用。为了解决这个外部引用,应用程序必须与 DLL 制作者提供的导入库(.LIB 文件)链接。导入库只包含加载 DLL 和实现对 DLL 中函数调用的代码。在导入库中找到一个外部函数会通知链接器该函数的代码在 DLL 中。为了解决对 DLL 的外部引用,链接器只需在可执行文件中添加信息,告诉系统在进程启动时在哪里找到 DLL 代码。

当系统启动包含动态链接引用的程序时,它会使用程序可执行文件中的信息来定位所需的 DLL。如果无法定位 DLL,系统就会终止进程,并显示一个报告错误的对话框,如果正在构建应用程序,则可能输出以下错误信息。

testdll.obj : error LNK2019: unresolved external symbol "int __cdecl mydll(char *)"(?mydll@@YAHPAD@Z)referenced in function _main
Debug/mydlltest.exe : fatal error LNK1120: 1 unresolved externals

否则,系统会将 DLL 模块映射到进程的地址空间。如果任何一个 DLL 有一个入口点函数(用于初始化和终止代码),操作系统就会调用该函数。传递给切入点函数的参数之一指定了一个代码,该代码表示 DLL 附加到进程中。如果切入点函数没有返回 TRUE,系统就会终止进程并报告错误。

最后,系统修改进程的可执行代码,为 DLL 函数提供起始地址。与程序的其他代码一样,DLL 代码在进程启动时就被映射到进程的地址空间中,只有在需要时才加载到内存中。因此,在以前的 Windows 版本中,.DEF 文件用来控制加载的 PRELOAD 和 LOADONCALL 代码属性不再有意义。

显式链接

大多数应用程序使用隐式链接,因为它是最简单的链接方法。然而,有时显式链接是必要的。下面是一些使用显式链接的常见原因。

  1. 应用程序在运行前不知道必须加载的 DLL 的名称。例如,应用程序可能需要从配置文件中获取 DLL 的名称和导出的函数。
  2. 如果在进程启动时没有找到 DLL,使用隐式链接的进程就会被操作系统终止。使用显式链接的进程在这种情况下不会被终止,可以尝试从错误中恢复。例如,进程可以通知用户这个错误,并让用户指定另一个 DLL 的路径。
  3. 如果任何一个被链接到的 DLLs 的 DllMain()函数失败,使用隐式链接的进程也会被终止。在这种情况下,使用显式链接的进程不会被终止。
  4. 隐式链接到许多 DLL 的应用程序可能会启动缓慢,因为 Windows 在应用程序加载时加载所有的 DLL。为了提高启动性能,应用程序可以在加载后立即隐式链接到那些需要的 DLL,并等待在需要时显式链接到其他 DLL。
  5. 显式链接消除了用导入库链接应用程序的需要。如果 DLL 中的变化导致导出序数发生变化,使用显式链接的应用程序不必重新链接(假设他们调用 GetProcAddress()时使用的是函数名而不是序数值),而使用隐式链接的应用程序必须重新链接到新的导入库。

这里有两个显式链接的危害需要注意。

  1. 如果 DLL 有一个 DllMain()入口点函数,操作系统会在调用 LoadLibrary()的线程的上下文中调用该函数。如果因为之前调用 LoadLibrary()而没有相应调用 FreeLibrary()函数,DLL 已经被连接到进程中,那么这个入口点函数就不会被调用。如果 DLL 使用 DllMain()函数为进程的每个线程执行初始化,那么显式链接可能会引起问题,因为当 LoadLibrary()(或 AfxLoadLibrary())被调用时存在的线程将不会被初始化。
  2. 如果一个 DLL 将静态扩展数据声明为 __declspec(thread),如果显式链接,就会引起保护故障。在用 LoadLibrary()加载 DLL 后,只要代码引用这些数据,就会引起保护故障。(Static-extent 数据包括全局和本地静态项。)因此,当你创建一个 DLL 时,你应该避免使用线程本地存储,或者告知 DLL 用户潜在的陷阱(以防他们尝试动态加载)。

创建一个仅有资源的 DLL

纯资源 DLL 是一个只包含资源的 DLL,如图标、位图、字符串和对话框。使用只包含资源的 DLL 是在多个程序中共享同一资源集的好方法。它也是为应用程序提供多语言本地化资源的好方法。要创建一个资源专用 DLL,您需要创建一个新的 Win32 DLL(非 MFC)项目,并将您的资源添加到该项目中。

  1. 在 “新建项目” 对话框中选择 “Win32 项目”,并在 “Win32 项目向导” 中指定 DLL 项目类型。
  2. 为 DLL 创建一个包含资源(如字符串或菜单)的新资源脚本,并保存.rc 文件。
  3. 在 “项目” 菜单上,单击 “添加现有项目”,并将新的.rc 文件插入到项目中。
  4. 指定 / NOENTRY 链接器选项。/NOENTRY 可以防止链接器将对 _main 的引用链接到 DLL 中;创建一个仅有资源的 DLL 时需要这个选项。
  5. 构建 DLL。

使用资源专用 DLL 的应用程序应该调用 LoadLibrary()来显式链接到 DLL。要访问资源,可以调用通用函数 FindResource()和 LoadResource(),这两个函数适用于任何类型的资源,或者调用以下资源专用函数之一。

  1. FormatMessage()
  2. LoadAccelerators()
  3. LoadBitmap()
  4. LoadCursor()
  5. LoadIcon()
  6. LoadMenu()
  7. LoadString()

当应用程序使用完资源后,应该调用 FreeLibrary()。

导入和导出

您可以使用两种方法将公共符号导入应用程序或从 DLL 中导出函数。

  1. 在构建 DLL 时使用模块定义(.DEF)文件。
  2. 在主应用程序的函数定义中使用关键字 __declspec(dllimport)__declspec(dllexport)

使用.DEF 文件

模块定义(.DEF)文件是一个文本文件,它包含了一个或多个模块声明,这些声明描述了 DLL 的各种属性。如果你没有使用 __declspec(dllimport)__declspec(dllexport) 来导出 DLL 的函数,那么 DLL 需要一个 .DEF 文件。你可以使用 .DEF 文件导入到应用程序中或者从 DLL 中导出。

使用 __declspec

32 位版本的 Visual C++ 使用 __declspec(dllimport)__declspec(dllexport) 来代替以前在 16 位版本的 Visual C++ 中使用的 __export 关键字。你不需要使用 __declspec(dllimport) 来让你的代码正确编译,但是这样做可以让编译器生成更好的代码。编译器能够生成更好的代码,因为它知道一个函数是否存在于 DLL 中,所以编译器可以生成跳过通常会出现在一个跨越 DLL 边界的函数调用中的间接层次的代码。然而,你必须使用 __declspec(dllimport) 来导入 DLL 中使用的变量。如果使用适当的.DEF 文件 EXPORTS 部分,__declspec(dllexport) 是不需要的。增加了 __declspec(dllexport) 来提供一种简单的方法来从 .EXE 或 .DLL 中导出函数,而无需使用 .DEF 文件。Win32 Portable Executable(PE)格式被设计为最小化为修复导入而必须触及的页面数量。为了做到这一点,它将任何程序的所有导入地址放在一个叫做导入地址表的地方。这使得加载器在访问这些导入时,只需修改一两个页面。

使用 __declspec(dllimport) 导入应用程序

一个使用 DLL 定义的公共符号的程序被称为导入它们。当你为使用你的 DLL 来构建的应用程序创建头文件时,在公共符号的声明中使用 __declspec(dllimport)。无论你是用.DEF 文件还是用 __declspec(dllexport) 关键字导出,关键字 __declspec(dllimport) 都能发挥作用。为了使你的代码更易读,定义一个 __declspec(dllimport) 的宏,然后用这个宏来声明每个导入的符号。

#define DllImport   __declspec(dllimport)

DllImport int  j;
DllImport void func();

在函数声明中使用 __declspec(dllimport) 是可选的,但是如果你使用这个关键字,编译器会产生更有效的代码。然而,你必须使用 __declspec(dllimport) 才能让导入的可执行文件访问 DLL 的公共数据符号和对象。请注意,您的 DLL 的用户仍然需要与导入库链接。您可以为 DLL 和客户端应用程序使用相同的头文件。要做到这一点,请使用一个特殊的预处理符号,它表明你是在构建 DLL 还是在构建客户端应用程序。例如

#ifdef _EXPORTING
   #define CLASS_DECLSPEC    __declspec(dllexport)
#else
   #define CLASS_DECLSPEC    __declspec(dllimport)
#endif

class CLASS_DECLSPEC CExampleA : public CObject
{
    ... class definition ... };

从 DLL 中导出

.DLL 文件的布局与.EXE 文件非常相似,但有一个重要的区别:DLL 文件包含一个导出表。导出表包含 DLL 向其他可执行文件导出的每个函数的名称。这些函数是进入 DLL 的入口点;只有导出表中的函数可以被其他可执行文件访问。DLL 中的任何其他函数都是 DLL 的私有函数。DLL 的导出表可以通过 DUMPBIN 工具(Visual Studio 自带的,或者你可以尝试更强大的工具,PEBrowser( www.smidgeonsoft.prohosting.com/),使用 / EXPORTS 选项来查看。你可以使用两种方法从 DLL 中导出函数。

  1. 创建一个模块定义(.DEF)文件,并在构建 DLL 时使用该.DEF 文件。如果你想从 DLL 中按序号而不是按 byname 导出函数,请使用这种方法。
  2. 在函数的定义中使用关键字 __declspec(dllexport)

当用这两种方法导出函数时,一定要使用 __stdcall 调用约定。模块定义(.DEF)文件是一个文本文件,它包含了一个或多个描述 DLL 各种属性的模块语句。如果你没有使用 __declspec(dllexport) 关键字来导出 DLL 的函数,DLL 需要一个.DEF 文件。一个最小的.DEF 文件必须包含以下模块定义语句。

  1. 文件中的第一条语句必须是 LIBRARY 语句。该语句标识了.DEF 文件属于一个 DLL。LIBRARY 语句后面是 DLL 的名称。链接器将这个名字放在 DLL 的导入库中。
  2. EXPORTS 语句列出了 DLL 导出的函数的名称和可选的序数值。你可以通过在函数名称后面用 at 符号(@)和一个数字给函数分配一个序数值。当您指定序数值时,它们必须在 1 到 N 的范围内,其中 N 是 DLL 导出的函数的数量。

例如,一个包含实现二进制搜索树的代码的 DLL 可能看起来像下面这样。

LIBRARY   BTREE
EXPORTS
   Insert   @1
   Delete   @2
   Member   @3
   Min   @4

如果您使用 MFC DLL 向导来创建 MFC DLL,向导会为您创建一个骨架 .DEF 文件,并自动将其添加到您的项目中。添加要导出到该文件的函数名称。对于非 MFC DLL,您必须自己创建 .DEF 文件并将其添加到您的项目中。如果您要导出 C++ 文件中的函数,您必须将装饰的名称放在.DEF 文件中,或者使用 extern “C” 用标准的 C 语言链接定义您导出的函数。如果您需要将装饰名放在.DEF 文件中,您可以使用 DUMPBIN 工具或使用链接器 / MAP 选项来获得它们。注意,编译器产生的装饰名是编译器特有的。如果您将 Visual C++ 编译器产生的装饰名放入.DEF 文件中,那么链接到您的 DLL 的应用程序也必须使用相同版本的 Visual C++ 来构建,以便调用应用程序中的装饰名与 DLL 的.DEF 文件中导出的名称相匹配。如果你正在构建一个扩展 DLL(MFC),并使用一个.DEF 文件导出,请在包含导出类的头文件的开头和结尾放置以下代码。

#undef AFX_DATA
#define AFX_DATA AFX_EXT_DATA
// 
#undef AFX_DATA
#define AFX_DATA

这些行确保内部使用的 MFC 变量或添加到你的类中的 MFC 变量能从你的扩展 DLL 中导出(或导入)。例如,当使用 DECLARE_DYNAMIC 派生一个类时,该宏会展开将一个 CRuntimeClass 成员变量添加到你的类中。漏掉这四行可能会导致你的 DLL 编译或链接不正确,或者在客户端应用程序链接到 DLL 时导致错误。

当构建 DLL 时,链接器使用.DEF 文件创建一个导出(.EXP)文件和一个导入库(.LIB)文件。然后链接器使用导出文件来构建.DLL 文件。隐式链接到 DLL 的可执行文件会在构建时链接到导入库。请注意,MFC 本身使用.DEF 文件从 MFCx0.DLL 导出函数和类。

使用 __declspec(dllexport) 从 DLL 中导出。

微软在 Visual C++ 的 16 位编译器版本中引入了 __export,允许编译器自动生成导出名,并将它们放在一个.LIB 文件中。然后,这个.LIB 文件就可以像静态的.LIB 一样,用来与 DLL 链接。在 32 位编译器版本中,你可以使用 __declspec(dllexport) 关键字从 DLL 中导出数据、函数、类或类成员函数。__declspec(dllexport) 将导出指令添加到对象文件中,因此你不需要使用 .DEF 文件。这种便利性在尝试导出装饰的 C++ 函数名时最为明显。由于没有标准的名称装饰规范,所以在不同的编译器版本之间,导出的函数名称可能会发生变化。如果你使用 __declspec(dllexport),重新编译 DLL 和依赖的.EXE 文件是必要的,只是为了说明任何命名约定的变化。许多导出指令,如 ordinals、NONAME 和 PRIVATE,只能在 a.DEF 文件中进行,没有 a.DEF 文件就无法指定这些属性。然而,除了使用 .DEF 文件之外,使用 __declspec(dllexport) 不会导致构建错误。要导出函数,__declspec(dllexport) 关键字必须出现在调用约定关键字的左边,如果有指定关键字的话。例如:

__declspec(dllexport)void __stdcall WilBeExportedFunctionName(void);

而真正的例子可能是这样的。

__declspec(dllexport)int mydll(LPTSTR lpszMsg)

要导出一个类中所有的公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示。

class __declspec(dllexport)CExampleExport : public CObject
{
    ... class definition ... };

当构建你的 DLL 时,你通常会创建一个头文件,其中包含你要导出的函数原型和 / 或类,并在头文件的声明中添加 __declspec(dllexport) 。为了使你的代码更易读,为 __declspec(dllexport) 定义一个宏,并对你要导出的每个符号使用这个宏。

#define DllExport   __declspec(dllexport)

__declspec(dllexport) 在 DLL 的导出表中存储函数名。

初始化一个 DLL

通常,你的 DLL 有初始化代码(如分配内存),当你的 DLL 加载时必须执行。当使用 Visual C++ 时,你在哪里添加代码来初始化你的 DLL 取决于你正在构建的 DLL 的种类。如果你不需要添加初始化或终止代码,那么在构建你的 DLL 时就没有什么特别的事情要做。如果你需要初始化你的 DLL,下面的表格描述了添加代码的位置。

DLL 的类型 在哪里添加初始化和终止代码
常规 DLL 在 DLL 的 CWinApp()对象的 InitInstance()和 ExitInstance()中
扩展 DLL 在 MFC DLL 向导生成的 DllMain()函数中
非 MFC DLL 在你提供的一个名为 DllMain()的函数中。

在 Win32 中,所有的 DLL 都可能包含一个可选的入口点函数(通常称为 DllMain()),该函数在初始化和终止时都会被调用。这使你有机会根据需要分配或释放额外的资源。Windows 在四种情况下调用入口点函数:进程附加、进程分离、线程附加和线程分离。C 运行时库提供了一个名为 _DllMainCRTStartup() 的入口点函数,它调用 DllMain()。根据 DLL 的种类,要么在源代码中应该有一个叫做 DllMain()的函数,要么使用 MFC 库中提供的 DllMain()。

初始化扩展 DLL(对于 MFC 程序)

由于扩展 DLL 没有 CWinApp 派生的对象(和普通 DLL 一样),所以你应该将你的初始化和终止代码添加到 MFC DLL 向导生成的 DllMain()函数中。向导提供了以下扩展 DLL 的代码。在下面的代码部分,PROJNAME 是你的项目名称的占位符。

#include "stdafx.h"
#include 

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE [] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL = {
    NULL, NULL };

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   
   if(dwReason == DLL_PROCESS_ATTACH)
   {
   
      TRACE0("PROJNAME.DLL Initializing!\n");
      // Extension DLL one-time initialization
      AfxInitExtensionModule(PROJNAMEDLL, hInstance)

你可能感兴趣的:(Windows,Windows,DLL)