C++使用宏实现动态库加载
CodeOfCC 人气:0前言
开发的时候,有些项目不能静态链接动态库,需要程序运行时加载动态库,这个时候根据不同平台我们通常使用LoadLibrary或dlopen将动态库加载到程序中,并且还需要定义函数指针然后在获取函数地址,这一系列的操作其实时比较麻烦的,尤其是方法较多的情况,基本没法这么做,这时候就需要将这一过程进行适当的简化。让动态加载在方法较多的情况下尽量减少工作量。
一、为什么使用宏
一般的动态库加载流程
1、Windows加载
#include<Windows.h> extern "C" { #include "libavformat/avformat.h" } //定义方法类型 typedef AVFormatContext*(*delegate_avformat_alloc_context)(void); //定义方法指针 delegate_avformat_alloc_context dl_avformat_alloc_context; HMODULE _hLibrary = nullptr; //加载动态库 void ImportMethod() { auto _hLibrary = LoadLibraryA("avformat-58.dll"); if (!_hLibrary) { printf("error:load %s failed!\n", "avformat-58.dll"); return; } dl_avformat_alloc_context=(delegate_avformat_alloc_context)GetProcAddress(_hLibrary, "avformat_alloc_context"); if (!dl_avformat_alloc_context) { printf("error:%s load %s method failed!\n","avformat-58.dll", "avformat_alloc_context"); } } //卸载动态库 void UnImport() { if (_hLibrary) { FreeLibrary(_hLibrary); _hLibrary = nullptr; } } //使用方法 void UseMethod() { auto ic = dl_avformat_alloc_context(); }
2、Linux加载
#include <dlfcn.h> extern "C" { #include "libavformat/avformat.h" } //定义方法类型 typedef AVFormatContext*(*delegate_avformat_alloc_context)(void); //定义方法指针 delegate_avformat_alloc_context dl_avformat_alloc_context; void* _hLibrary = nullptr; //加载动态库 void ImportMethod() { auto _hLibrary = dlopen("libavformat.so", RTLD_LAZY); if (!_hLibrary) { printf("error:load %s failed!\n", "libavformat.so"); return; } dl_avformat_alloc_context=(delegate_avformat_alloc_context)dlsym(_hLibrary, "avformat_alloc_context"); if (!dl_avformat_alloc_context) { printf("error:%s load %s method failed!\n","libavformat.so", "avformat_alloc_context"); } } //卸载动态库 void UnImport() { if (_hLibrary) { dlclose(_hLibrary); _hLibrary = nullptr; } } //使用方法 void UseMethod() { auto ic = dl_avformat_alloc_context(); }
3、宏加载
很明显上述流程对于加载一个方法来说流程过于复杂,在遇到库比较多以及方法比较多的情况下,这种方法是很影响开发效率的。但是如果我们对上述流程进行宏包装,事情将会变得简单很多。比如上述引入ffmpeg的avformat_alloc_context方法只需要两行即可:
extern "C" { #include "libavformat/avformat.h" } //使用宏动态加载方法 DLL_IMPORT("libavformat.so", avformat_alloc_context); #define avformat_alloc_context DLL_IMPORT_NAME(avformat_alloc_context) //使用方法 void UseMethod() { //与原方法名称一致,直接调用 auto ic = avformat_alloc_context(); }
二、具体实现
我们通过宏包装上述两个流程即可,同时还需要结合c++11的decltype关键字。
DllImportUtils.h
// // Created by xin on 2022/6/15. // #ifndef DLLIMPORTUTILS_H #define DLLIMPORTUTILS_H void* GetDllMethodPtr(const char*dll,const char*method); #define DLL_IMPORT(dll,method) decltype (method)* dllImport_##method; \ namespace { \ class A##method{ \ public: A##method() { \ dllImport_##method = (decltype(dllImport_##method))GetDllMethodPtr(dll, #method); \ } \ }; \ A##method a##method; \ } #define DLL_IMPORT_NAME(name) dllImport_##name #endif
DllImportUtils.cpp
#include"DllImportUtils.h" #include<map> #include<string> #include<stdio.h> #ifdef _WIN32 #include<Windows.h> #define ACLoadLibrary(name) LoadLibraryA(name) #define ACGetProcAdress(dll,name) GetProcAddress((HMODULE)dll,name) #else #include <dlfcn.h> #define ACLoadLibrary(name) dlopen(name,RTLD_LAZY) #define ACGetProcAdress(dll,name) dlsym(dll,name) #endif // _Win32 std::map<std::string, void*>* _dllMap = nullptr; class DllMapDisposer { public: ~DllMapDisposer() { if (_dllMap) delete _dllMap; } }; static DllMapDisposer _disposer; void* GetDllMethodPtr(const char* dll, const char* method) { if (!_dllMap) _dllMap = new std::map<std::string, void*>; auto iter = _dllMap->find(dll); void* hm; if (iter == _dllMap->end()) { hm = (void*)ACLoadLibrary(dll); if (hm) { (*_dllMap)[dll] = hm; } else { printf("warnning:load %s failed!\n", dll); } } else { hm = iter->second; } if (hm) { auto methodPtr = ACGetProcAdress(hm, method); if (!methodPtr) { printf("error:%s load %s method failed!\n", dll, method); } return methodPtr; } return nullptr; }
三、如何使用
1、引用头文件
引用需要导入方法的头文件
extern "C" { //需要导入方法的头文件 #include "libavformat/avformat.h" } #include"DllImportUtils.h"
2、添加导入宏
//参数为库的名称和需要导入的方法 DLL_IMPORT("libavformat.so", avformat_alloc_context); #define avformat_alloc_context DLL_IMPORT_NAME(avformat_alloc_context)
3、直接调用
void UseMethod() { //与原方法名称一致,直接调用 auto ic = avformat_alloc_context(); }
注:当前版本不支持卸载库,程序启动时方法就会被立刻加载。支持跨平台,Windows、Linux都可以使用
总结
以上就是今天要讲的内容,本文讲述的方法很大程度的减少了工作量,而且可以不需要改代码原有逻辑和方法名称,适合需要动态加载不同版本的库或者依赖库的glibc不相同时的场景使用。
加载全部内容