静态库和动态库
1. 基本概念
在C++中,静态库(Static Library)和动态库(Dynamic Library)是两种常见的库文件形式。
- 链接方式
- 静态库:在编译链接时,静态库的代码会被整合进最终的可执行文件中。这意味着编译后的可执行文件会包含静态库中所有使用到的函数和数据,使得可执行文件相对较大,但运行时不需要外部依赖。
- 动态库:在编译链接时,动态库的代码并不会被整合进最终的可执行文件中。相反,可执行文件在运行时会加载动态库,因此可执行文件相对较小,但运行时需要确保系统中存在相应的动态库文件。
- 文件格式
- 静态库:通常以.lib(Windows)或.a(Linux)作为文件扩展名。静态库包含编译后的目标代码。
- 动态库:通常以.dll(Windows)或.so(Linux)作为文件扩展名。动态库包含已编译的二进制代码,但在运行时需要动态加载到内存中才能执行。
- 内存使用
- 静态库:编译后的可执行文件会包含静态库的代码和数据,因此在内存中占用的空间比较大,但不需要额外加载。
- 动态库:可执行文件在运行时需要加载动态库到内存中,因此在磁盘上占用的空间可能较小,但运行时会增加一些额外的内存开销。
- 更新和部署
- 静态库:一旦编译进可执行文件,如果静态库本身有更新或修复,需要重新编译和链接整个程序才能应用更新。
- 动态库:动态库可以单独更新,不需要重新编译可执行文件。这使得动态库更容易部署和更新。
- 跨平台兼容性
- 静态库:在不同平台上使用静态库可能需要重新编译库本身和可执行文件,因为静态库通常与特定平台和编译器相关。
- 动态库:可以编写跨平台的动态库,并在不同系统上共享同一动态库文件,只要目标系统支持相同的接口和ABI(Application Binary
Interface)。
2. C++ 导入 dll 示例
2.1 Windows 通用 LoadLibrary 、GetProcAddress
这是最基本和通用的方法,适用于所有 Windows 平台的编译器。
#include <Windows.h>
#include <iostream>
int main() {
// 加载 DLL
HMODULE hDLL = LoadLibrary("your_dll.dll");
if (hDLL != NULL) {
// 调用 DLL 中的函数
typedef void (*YourFunctionType)();
YourFunctionType YourFunction = (YourFunctionType) GetProcAddress(hDLL, "YourFunctionName");
if (YourFunction != NULL) {
// 调用函数
YourFunction();
} else {
std::cout << "Failed to load function from DLL." << std::endl;
}
// 卸载 DLL
FreeLibrary(hDLL);
} else {
std::cout << "Failed to load DLL." << std::endl;
}
return 0;
}
2.2 Windows 成员变量的方式(重点)
这通常涉及使用LoadLibrary函数加载DLL文件,然后使用GetProcAddress函数获取DLL中具体函数的地址,并将其赋值给函数指针成员变量
#include <Windows.h>
#include <iostream>
// 定义函数指针类型
typedef int (*DLLFuncPtr)(int);
class MyDLLHandler {
private:
HINSTANCE hDLL; // DLL句柄
DLLFuncPtr myDLLFunction; // 函数指针成员变量
public:
MyDLLHandler() : hDLL(NULL), myDLLFunction(NULL) {}
// 加载DLL并初始化函数指针
bool loadDLL(const char* dllPath) {
hDLL = LoadLibraryA(dllPath);
if (hDLL != NULL) {
myDLLFunction = (DLLFuncPtr)GetProcAddress(hDLL, "myDLLFunctionName");
if (myDLLFunction == NULL) {
std::cerr << "Failed to get function address from DLL.\n";
return false;
}
return true;
} else {
std::cerr << "Failed to load DLL.\n";
return false;
}
}
// 使用DLL函数
int useDLLFunction(int arg) {
if (myDLLFunction != NULL) {
return myDLLFunction(arg);
} else {
std::cerr << "DLL function pointer is not initialized.\n";
return -1;
}
}
// 卸载DLL
void unloadDLL() {
if (hDLL != NULL) {
FreeLibrary(hDLL);
hDLL = NULL;
myDLLFunction = NULL;
}
}
~MyDLLHandler() {
unloadDLL();
}
};
int main() {
MyDLLHandler handler;
const char* dllPath = "path/to/your/dll.dll";
if (handler.loadDLL(dllPath)) {
int result = handler.useDLLFunction(5);
std::cout << "Result from DLL function: " << result << std::endl;
handler.unloadDLL();
} else {
std::cerr << "Failed to load DLL.\n";
}
return 0;
}
在这个例子中:
- MyDLLHandler类处理了加载DLL、获取函数地址、调用DLL函数以及卸载DLL的操作。
- loadDLL 方法负责加载DLL,并通过 GetProcAddress 获取 myDLLFunctionName 函数的地址,然后将其赋值给 myDLLFunction 成员变量。
- useDLLFunction 方法演示了如何使用 myDLLFunction 成员变量来调用DLL中的函数。
- unloadDLL 方法用于释放已加载的DLL,并清空相关的成员变量。
通过这种方式,你可以利用C++中的类和成员变量来管理和使用动态链接库中的函数,提高了代码的封装性和可维护性。
2.3 #pragma comment(lib, “your_dll.lib”)
在源文件中使用 #pragma comment 可以在编译时自动链接 DLL,前提是你有相应的 .lib 文件。示例:
#include <iostream>
// 导入 DLL 的 .lib 文件
#pragma comment(lib, "your_dll.lib")
// 使用 DLL 中的函数
extern "C" void YourFunction();
int main() {
// 调用 DLL 中的函数
YourFunction();
return 0;
}
2.4 使用 extern “C” 声明
如果 DLL 中的函数是用 C 语言编写的,需要使用 extern “C” 声明来确保 C++ 能正确链接并调用这些函数。
extern "C" {
__declspec(dllimport) void YourFunction(); // 声明导入函数
}
int main() {
YourFunction(); // 调用 DLL 中的函数
return 0;
}
注意事项:
- 平台兼容性:以上方法主要适用于 Windows 平台。如果你在 Linux 或 macOS 上工作,通常使用 .so 或 .dylib 文件,并使用 dlopen 和 dlsym 函数来动态加载和获取函数地址。
- 错误处理:在使用 LoadLibrary 和 GetProcAddress 加载 DLL 时,务必检查返回的句柄和函数指针是否为 NULL,以处理加载失败的情况。