C 语言建立和使用 DLL

基础知识

生成 DLL 时会生成一个与之匹配的 .lib 文件(导入库文件),用于存放调用 DLL 所需的信息。若是你想调用这个 DLL 则必须将 lib 连接进程序。(导入库 lib 文件和静态库的 lib 虽然都会被连接进程序,但二者的内容彻底不一样)函数

在 VS 上建立 DLL 须要使用 Microsoft 专用 C\C++ 扩展 —— dllexport 和 dllimport 存储类特性。可使用它们从 DLL 中导出或向其中导入函数、数据和对象。语法以下:性能

__declspec( dllimport ) declarator
__declspec( dllexport ) declarator

提示: 若是不使用 __declspec(dllexport) 关键字导出 DLL 的函数,则 DLL 须要 .def 文件。模块定义 (.def) 文件是包含一个或多个描述 DLL 各类特性的 Module 语句的文本文件。操作系统

建立动态连接库

建立一个动态连接库项目。(Visual Studio:新建 C++ 项目-->Win32控制台应用,在 应用程序设置 页面的 应用程序类型 下选择 DLL)线程

// dll.h
#pragma once

#ifdef FUNCTION_EXPORTS
#define FUNCTIONDLL_API __declspec(dllexport) // 导出,导出用于生成 DLL
#else
#define FUNCTIONDLL_API __declspec(dllimport) // 导入,用于导入 dllexport 导出的内容
#endif

FUNCTIONDLL_API int a(int num1, int num2);
FUNCTIONDLL_API int b(int num1, int num2);
// dll.c
#define FUNCTION_EXPORTS
#include "dll.h"

int a(int num1, int num2)
{
	return num1 + num2;
}

int b(int num1, int num2)
{
	return num1 + num2;
}

生成 DLL ,会获得 DLL 和 lib 文件。指针

调用/连接 DLL 有两种方法,一,隐式连接;二,显示连接。code

为隐式连接到 DLL,应用程序必须:对象

  • 包含导出函数声明的头文件(.h 文件)。函数和数据均应具备 __declspec(dllimport)。
  • 要连接的导入库(.LIB 文件)。(生成 DLL 时连接器建立的导入库)
  • 实际的 DLL(.dll 文件)。
  • 操做系统在加载调用可执行文件时,必须可以定位 DLL 文件。

隐式连接后,程序操做 DLL 中函数的方法同本地函数并没有区别。进程

为显式连接到 DLL,应用程序必须:作用域

  • 调用 LoadLibrary(或类似的函数)以加载 DLL 和获取模块句柄。
  • 调用 GetProcAddress,以获取指向应用程序要调用的每一个导出函数的函数指针。因为应用程序是经过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库连接(lib 文件)。
  • 使用完 DLL 后调用 FreeLibrary 函数卸载。

我建立的调用程序使用隐式连接:编译器

#include "dll.h"

int main(void)
{
	printf("%d\n", a(1, 2));
	printf("%d\n", b(3, 4));
	return 0;
}

首先添加引用(两个项目在同一解决方案下才可引用),项目 --> 添加引用,将 DLL 项目添加进去(添加引用的目的是为了添加 lib 文件,效果与在 (若是不在同一解决方案,应使用下述方法:) 配置属性 --> 连接器 --> 输入 --> 附加依赖项 添加 lib 相同)。

而后添加附加包含目录,配置属性 --> C\C++ --> 常规 --> 附加包含目录,将 dll.h 文件的地址添加进去便可。

若是你的 DLL 和 调用程序再也不同一目录,能够将 DLL 所在目录添加至系统环境变量或将 DLL 同调用程序放至同一目录。

编译,执行结果以下:

3
7

关于显示连接

大部分应用程序使用隐式连接,由于这是最易于使用的连接方法。可是有时也须要显式连接。下面是一些使用显式连接的常见缘由:

  • 直到运行时,应用程序才知道须要加载的 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 用户潜在的缺陷。
相关文章
相关标签/搜索