这是一个在 Windows 平台下进行 动态链接库 编程时至关重要的函数,它的主要作用是:在程序运行时,从一个已经加载到内存中的 DLL 文件中,获取某个特定函数的内存地址。

(图片来源网络,侵删)
为什么需要 GetProcAddress?
在 C 语言中,我们通常使用两种方式来使用 DLL 中的函数:
-
隐式链接(编译时链接 / 静态加载):
- 在代码中通过
#include <dll.h>包含头文件。 - 在项目设置中链接对应的
.lib文件。 - 程序启动时,操作系统会自动加载该 DLL,并解析函数地址,你可以像调用普通函数一样直接调用 DLL 中的函数(
MyFunction())。 - 优点:使用简单,IDE 会帮你处理所有链接细节。
- 缺点:程序启动时必须加载 DLL,DLL 不存在,程序将无法启动,这增加了程序的依赖性。
- 在代码中通过
-
显式链接(运行时链接 / 动态加载):
- 不在编译时链接任何
.lib文件,也不包含头文件(或者只包含一个包含函数指针定义的简单头文件)。 - 在程序运行时,由开发者自己决定何时加载、何时卸载 DLL。
- 使用
GetProcAddress来动态获取函数的地址。 - 优点:极大的灵活性,程序可以在需要时才加载某个功能模块(插件),DLL 不存在,可以优雅地处理错误,而不是直接崩溃,这对于模块化设计和可选功能非常有用。
- 缺点:代码更复杂,需要手动处理加载、函数指针转换和错误检查。
- 不在编译时链接任何
GetProcAddress 是实现 显式链接 的核心。

(图片来源网络,侵删)
函数原型与所需头文件
GetProcAddress 是 Windows API 的一部分,定义在 Windows.h 头文件中。
函数原型:
FARPROC GetProcAddress( [in] HMODULE hModule, // 包含该函数的 DLL 模块的句柄 [in] LPCSTR lpProcName // 函数名或函数的序号 );
参数解释:
-
hModule(HMODULE 类型)
(图片来源网络,侵删)- 这是一个 模块句柄,代表一个已经加载到进程地址空间中的 DLL 或 EXE 文件。
- 你不能直接传入 DLL 的文件名(如
"mylib.dll")。 - 这个句柄通常通过另一个 API 函数
LoadLibrary来获取。 LoadLibrary会加载指定的 DLL 文件,并返回其模块句柄,如果加载失败,则返回NULL。
-
lpProcName(LPCSTR 类型)- 这是要查找的函数的名称,它是一个以
\0结尾的字符串("MyFunction")。 - 也可以传入一个函数的 序号(一个
DWORD类型的值),但使用字符串名称是更常见、更安全、更具可读性的做法。
- 这是要查找的函数的名称,它是一个以
返回值:
- 如果成功,
GetProcAddress返回一个指向该函数的 函数指针,这个指针的类型是FARPROC,它是一个通用的函数指针类型。 - 如果失败(
hModule无效,或者lpProcName指定的函数不存在),则返回NULL。
所需头文件:
#include <windows.h> // 包含了所有 Windows API 的定义
使用步骤(完整流程)
下面是使用 GetProcAddress 的标准步骤:
步骤 1:定义函数指针类型 为了安全地调用获取到的函数,你需要为 DLL 中的每个函数定义一个匹配的函数指针类型,这确保了参数和返回类型的正确性。
// 假设 DLL 中有一个函数原型: int Add(int a, int b); typedef int (*pAddFunc)(int, int); // 假设还有一个函数原型: void PrintMessage(const char* msg); typedef void (*pPrintMessageFunc)(const char*);
步骤 2:使用 LoadLibrary 加载 DLL
HMODULE hDll = LoadLibrary("mylib.dll"); // 替换成你的 DLL 文件名
if (hDll == NULL) {
// DLL 加载失败,处理错误
DWORD error = GetLastError();
printf("Failed to load DLL. Error code: %d\n", error);
return 1; // 或其他错误处理
}
步骤 3:使用 GetProcAddress 获取函数地址
// 获取 Add 函数的地址
pAddFunc pAdd = (pAddFunc)GetProcAddress(hDll, "Add");
if (pAdd == NULL) {
// 函数未找到,处理错误
DWORD error = GetLastError();
printf("Failed to get address of 'Add'. Error code: %d\n", error);
FreeLibrary(hDll); // 清理资源
return 1;
}
// 获取 PrintMessage 函数的地址
pPrintMessageFunc pPrintMessage = (pPrintMessageFunc)GetProcAddress(hDll, "PrintMessage");
if (pPrintMessage == NULL) {
// 函数未找到,处理错误
DWORD error = GetLastError();
printf("Failed to get address of 'PrintMessage'. Error code: %d\n", error);
FreeLibrary(hDll); // 清理资源
return 1;
}
步骤 4:通过函数指针调用函数
// 现在可以像调用普通函数一样使用它们
int result = pAdd(10, 20);
printf("Result from Add: %d\n", result);
pPrintMessage("Hello from the dynamically loaded function!");
步骤 5:使用 FreeLibrary 释放 DLL
当不再需要 DLL 时,应该将其从内存中卸载以释放资源。
FreeLibrary(hDll);
完整代码示例
假设我们有一个名为 mylib.dll 的动态库,它包含两个函数:
// mylib.h
__declspec(dllexport) int Add(int a, int b);
__declspec(dllexport) void PrintMessage(const char* msg);
// mylib.c
#include "mylib.h"
#include <stdio.h>
int Add(int a, int b) {
return a + b;
}
void PrintMessage(const char* msg) {
printf("DLL says: %s\n", msg);
}
下面是一个加载并使用这个 mylib.dll 的 C 程序:
#include <stdio.h>
#include <windows.h>
// 步骤 1: 定义与 DLL 中函数匹配的函数指针类型
typedef int (*pAddFunc)(int, int);
typedef void (*pPrintMessageFunc)(const char*);
int main() {
// 步骤 2: 加载 DLL
HMODULE hDll = LoadLibrary("mylib.dll");
if (hDll == NULL) {
printf("Error: Could not load mylib.dll. Make sure it's in the same directory.\n");
return 1;
}
printf("mylib.dll loaded successfully.\n");
// 步骤 3: 获取函数地址
pAddFunc pAdd = (pAddFunc)GetProcAddress(hDll, "Add");
pPrintMessageFunc pPrintMessage = (pPrintMessageFunc)GetProcAddress(hDll, "PrintMessage");
if (pAdd == NULL || pPrintMessage == NULL) {
printf("Error: Could not find one or more functions in the DLL.\n");
FreeLibrary(hDll); // 清理资源
return 1;
}
printf("Function addresses found.\n");
// 步骤 4: 通过函数指针调用函数
int sum = pAdd(5, 7);
printf("The sum is: %d\n", sum);
pPrintMessage("This is a dynamic function call!");
// 步骤 5: 释放 DLL
FreeLibrary(hDll);
printf("mylib.dll unloaded.\n");
return 0;
}
如何编译和运行:
- 首先编译
mylib.c生成mylib.dll和mylib.lib(使用/LD编译选项)。 - 将
mylib.dll复制到你的 C 程序的同一目录下。 - 编译并运行上面的 C 程序。
重要注意事项
- 错误处理:
LoadLibrary和GetProcAddress都可能失败,返回NULL,每次调用后都应该检查返回值,并用GetLastError()获取详细的错误代码。 - 函数指针类型:将
GetProcAddress的返回值强制转换为正确的函数指针类型是必须的,类型不匹配会导致调用栈混乱,引发程序崩溃。 - DLL 生命周期:确保在调用 DLL 中的函数之前,DLL 已经通过
LoadLibrary加载,在调用FreeLibrary之后,不能再使用之前获取的函数指针。 - 字符编码:
GetProcAddress的第二个参数lpProcName在 Windows 上默认使用 ANSI 编码(LPCSTR),如果你的 DLL 是用 Unicode 编译的(函数名是宽字符),你可能需要使用GetProcAddressW版本,或者确保你的字符串编码匹配,在大多数现代 Windows 系统上,这通常不是问题,但需要注意。 FreeLibrary与引用计数:FreeLibrary会减少 DLL 的引用计数,只有当引用计数降为 0 时,DLL 才会被真正从内存中卸载,如果你多次调用LoadLibrary加载同一个 DLL,也需要相应地多次调用FreeLibrary。
