在Windows C++开发中,动态加载DLL是一种常见需求,例如插件系统或按需功能模块。手动调用LoadLibrary和FreeLibrary不仅繁琐,而且在函数提前返回或抛出异常时极易忘记释放模块句柄,造成资源泄漏。使用RAII(资源获取即初始化)思想封装DLL加载过程,可以让编译器自动在对象生命周期结束时释放资源。

RAII包装类设计思路
核心目标是将HMODULE句柄绑定到栈对象的生命周期:构造时加载库,析构时释放库。同时提供类型安全的函数导出接口,避免外部重复书写GetProcAddress转换逻辑。
类的基本结构
- 私有成员保存HMODULE句柄
- 构造函数接收DLL路径并调用LoadLibrary
- 析构函数判断句柄有效性后调用FreeLibrary
- 删除拷贝构造与拷贝赋值,允许移动语义
完整源码示例
以下代码展示了一个简单的DLL加载包装类实现,支持移动语义与函数获取。
#include <windows.h>
#include <string>
#include <stdexcept>
class DllLoader {
public:
// 构造时加载动态库
explicit DllLoader(const std::string& path) {
m_module = LoadLibraryA(path.c_str());
if (!m_module) {
throw std::runtime_error("Failed to load DLL: " + path);
}
}
// 析构时自动释放
~DllLoader() {
if (m_module) {
FreeLibrary(m_module);
}
}
// 禁止拷贝
DllLoader(const DllLoader&) = delete;
DllLoader& operator=(const DllLoader&) = delete;
// 允许移动
DllLoader(DllLoader&& other) noexcept : m_module(other.m_module) {
other.m_module = nullptr;
}
DllLoader& operator=(DllLoader&& other) noexcept {
if (this != &other) {
if (m_module) FreeLibrary(m_module);
m_module = other.m_module;
other.m_module = nullptr;
}
return *this;
}
// 获取导出函数指针
template<typename FuncType>
FuncType getFunction(const std::string& name) const {
if (!m_module) throw std::runtime_error("DLL not loaded");
auto addr = GetProcAddress(m_module, name.c_str());
if (!addr) throw std::runtime_error("Function not found: " + name);
return reinterpret_cast<FuncType>(addr);
}
bool isLoaded() const { return m_module != nullptr; }
private:
HMODULE m_module = nullptr;
};
// 使用示例
typedef int (*AddFunc)(int, int);
int main() {
try {
DllLoader dll("example.dll");
auto add = dll.getFunction<AddFunc>("add");
int result = add(1, 2);
return result;
} catch (const std::exception& e) {
// 处理异常,dll析构会自动释放
return -1;
}
}
使用注意事项
上述代码中,若DLL路径错误会抛出异常,但由于dll对象尚未成功构造完成,析构函数不会被调用,这属于预期行为。在函数getFunction里使用模板将地址转换为具体函数类型,调用方无需手动写 reinterpret_cast。
线程安全说明
LoadLibrary与FreeLibrary本身是线程安全的,但同一个DllLoader对象不应被多个线程同时销毁。若需跨线程共享,应配合std::shared_ptr或使用智能指针管理DllLoader实例。
小结
通过RAII包装类,我们将DLL资源生命周期交由C++作用域自动管理,既减少了样板代码,也避免了资源泄漏风险。实际项目中可进一步扩展,比如支持宽字符路径、记录加载日志或增加延迟加载策略。