Visual C++的DLL
动态链接库 (DLL) 是作为共享函数库的可执行文件。 动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。 函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。 DLL 还有助于共享数据和资源。 多个应用程序可同时访问内存中单个 DLL 副本的内容。
动态链接与静态链接的不同之处在于:动态链接允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。 在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。
使用动态链接代替静态链接有若干优点。 DLL 节省内存,减少交换操作,节省磁盘空间,更易于升级,提供售后支持,提供扩展 MFC 库类的机制,支持多语言程序,并使国际版本的创建轻松完成。
下列主题提供有关对 DLL 编程的详细信息。
- 演练:创建和使用动态链接库 (C++)
-
介绍如何使用 Visual Studio 创建和使用 DLL。
- 如何:创建类库
-
使用项目模板,如何创建选件类库。
- 应用程序和 DLL 之间的区别
-
描述应用程序和 DLL 之间的基本区别。
- 使用 DLL 的优点
-
描述动态链接的优点。
- DLL 类型
-
提供有关可生成的不同类型的信息 DLL。
- DLL 常见问题
-
提供有关 DLL 的常见问题解答。
- 将可执行文件链接到 DLL
-
描述与 DLL 的显式链接和隐式链接。
可执行文件以下列两种方式之一链接到(或加载)DLL:
-
隐式链接
-
为隐式链接到 DLL,可执行文件必须从 DLL 的提供程序获取下列各项:
-
包含导出函数和/或 C++ 类的声明的头文件(.h 文件)。 类、函数和数据均应具有 __declspec(dllimport),有关更多信息,请参见 dllexport, dllimport。
-
要链接的导入库( .LIB files)。 (生成 DLL 时链接器创建导入库。)
-
实际的 DLL(.dll 文件)。
使用 DLL 的可执行文件必须包括头文件,此头文件包含每个源文件中的导出函数(或 C++ 类),而这些源文件包含对导出函数的调用。 从编码的角度讲,导出函数的函数调用与任何其他函数调用一样。
若要生成调用可执行文件,必须与导入库链接。 如果使用的是外部生成文件,请指定导入库的文件名,此导入库中列出了要链接到的其他对象 (.obj) 文件或库。
操作系统在加载调用可执行文件时,必须能够定位 DLL 文件。
-
-
-
显式链接
-
在显式链接下,应用程序必须进行函数调用以在运行时显式加载 DLL。 为显式链接到 DLL,应用程序必须:
-
调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。
-
调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。 由于应用程序是通过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库链接。
-
使用完 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 的创建者所提供的导入库(.lib 文件)。 使用 DLL 的可执行文件加载时,操作系统加载此 DLL。 客户端可执行文件调用 DLL 的导出函数,就好像这些函数包含在可执行文件内一样。
在显式链接下,使用 DLL 的可执行文件必须进行函数调用以显式加载和卸载该 DLL,并访问该 DLL 的导出函数。 客户端可执行文件必须通过函数指针调用导出函数。
可执行文件对两种链接方法可以使用同一个 DLL。 另外,由于一个可执行文件可隐式链接到某个 DLL,而另一个可显式附加到此 DLL,故这些机制不是互斥的。
确定使用所使用的链接方法:
隐式链接应用程序的代码调用导出 DLL 函数时发生 隐式链接。 当调用可执行文件的源代码被编译或被汇编时,DLL 函数调用在对象代码中生成一个外部函数引用。 若要解析此外部引用,应用程序必须与 DLL 的创建者所提供的导入库(.LIB 文件)链接。
导入库仅包含加载 DLL 的代码和实现 DLL 函数调用的代码。 在导入库中找到外部函数后,会通知链接器此函数的代码在 DLL 中。 要解析对 DLL 的外部引用,链接器只需向可执行文件中添加信息,通知系统在进程启动时应在何处查找 DLL 代码。
系统启动包含动态链接引用的程序时,它使用程序的可执行文件中的信息定位所需的 DLL。 如果系统无法定位 DLL,它将终止进程并显示一个对话框来报告错误。 否则,系统将 DLL 模块映射到进程的地址空间中。
如果任何 DLL 具有(用于初始化代码和终止代码的)入口点函数,操作系统将调用此函数。 在传递到入口点函数的参数中,有一个指定用以指示 DLL 正在附带到进程的代码。 如果入口点函数没有返回 TRUE,系统将终止进程并报告错误。
最后,系统修改进程的可执行代码以提供 DLL 函数的起始地址。
与程序代码的其余部分一样,DLL 代码在进程启动时映射到进程的地址空间中,且仅当需要时才加载到内存中。 因此,由 .def 文件用来在 Windows 的早期版本中控制加载的 PRELOAD 和 LOADONCALL 代码特性不再具有任何意义。
显式链接大部分应用程序使用隐式链接,因为这是最易于使用的链接方法。 但是有时也需要 显式链接。 下面是一些使用显式链接的常见原因:
-
直到运行时,应用程序才知道需要加载的 DLL 的名称。 例如,应用程序可能需要从配置文件获取 DLL 的名称和导出函数名。
-
如果在进程启动时未找到 DLL,操作系统将终止使用隐式链接的进程。 同样是在此情况下,使用显式链接的进程则不会被终止,并可以尝试从错误中恢复。 例如,进程可通知用户所发生的错误,并让用户指定 DLL 的其他路径。
-
如果使用隐式链接的进程所链接到的 DLL 中有任何 DLL 具有失败的 DllMain 函数,该进程也会被终止。 同样是在此情况下,使用显式链接的进程则不会被终止。
-
因为 Windows 在应用程序加载时加载所有的 DLL,故隐式链接到许多 DLL 的应用程序启动起来会比较慢。 为提高启动性能,应用程序可隐式链接到那些加载后立即需要的 DLL,并等到在需要时显式链接到其他 DLL。
-
显式链接下不需将应用程序与导入库链接。 如果 DLL 中的更改导致导出序号更改,使用显式链接的应用程序不需重新链接(假设它们是用函数名而不是序号值调用 GetProcAddress),而使用隐式链接的应用程序必须重新链接到新的导入库。
下面是需要注意的显式链接的两个缺点:
-
如果 DLL 具有 DllMain 入口点函数,则操作系统在调用 LoadLibrary 的线程上下文中调用此函数。 如果由于以前调用了 LoadLibrary 但没有相应地调用 FreeLibrary 函数而导致 DLL 已经附加到进程,则不会调用此入口点函数。 如果 DLL 使用 DllMain 函数为进程的每个线程执行初始化,显式链接会造成问题,因为调用 LoadLibrary(或 AfxLoadLibrary)时存在的线程将不会初始化。
-
如果 DLL 将静态作用域数据声明为 __declspec(thread),则在显式链接时 DLL 会导致保护错误。 用 LoadLibrary 加载 DLL 后,每当代码引用此数据时 DLL 就会导致保护错误。 (静态作用域数据既包括全局静态项,也包括局部静态项。)因此,创建 DLL 时应避免使用线程本地存储区,或者应(在用户尝试动态加载时)告诉 DLL 用户潜在的缺陷。
-
- 初始化 DLL
-
讨论当 DLL 加载时必须执行的 DLL 初始化代码(如分配内存)。
- 运行库行为
-
描述运行库如何执行 DLL 启动序列。
- LoadLibrary 和 AfxLoadLibrary
-
讨论如何使用 LoadLibrary 和 AfxLoadLibrary 显式链接到 DLL。
- GetProcAddress
-
讨论如何使用 GetProcAddress 获取 DLL 中导出函数的地址。
- FreeLibrary 和 AfxFreeLibrary
-
讨论当不再需要 DLL 模块时如何使用 FreeLibrary 和 AfxFreeLibrary。
- Windows 用来定位 DLL 的搜索路径:
-
描述 Windows 操作系统用来定位系统上的 DLL 的搜索路径。
通过隐式和显式链接,Windows 首先搜索“已知 DLL”,如 Kernel32.dll 和 User32.dll。 Windows 然后按下列顺序搜索 DLL:
-
当前进程的可执行模块所在的目录。
-
当前目录。
-
Windows 系统目录。 GetSystemDirectory 函数检索此目录的路径。
-
Windows 目录。 GetWindowsDirectory函数检索此目录的路径。
-
PATH 环境变量中列出的目录。
-
- 动态链接到 MFC 的规则 DLL 的模块状态
-
描述动态链接到 MFC 的规则 DLL 的模块状态。
- 扩展 DLL
-
解释通常实现从现有 Microsoft 基础类库类派生的可重用类的 DLL。
- 创建纯资源 DLL
-
讨论只包含资源(如图标、位图、字符串和对话框等)的纯资源 DLL。
- 在 MFC 应用程序的本地化资源:附属 DLL
-
提供对附属 DLL 的增强支持,该功能有助于创建针对多种语言进行本地化的应用程序。
- 导入和导出
-
描述如何将公共符号导入应用程序或从 DLL 导出函数。
- Active 技术和 DLL
-
使对象服务器得以在 DLL 内完全实现。
- DLL 中的自动化
-
描述“MFC DLL 向导”中的“自动化”选项提供的内容。
- MFC DLL 命名约定
-
讨论 MFC 中包含的 DLL 和库如何遵循结构化命名约定。
- 从 Visual Basic 应用程序调用 DLL 函数
-
描述如何从 Visual Basic 应用程序中调用 DLL 函数。