C++動(dòng)態(tài)加載so/dll庫的實(shí)現(xiàn)
在C++使用動(dòng)態(tài)庫,(linux下是.so,windows下是.dll) 比較常見的方式是在編譯時(shí),直接連接到程序中。但是除了這種方式外,還可以使用的動(dòng)態(tài)加載的方式去使用動(dòng)態(tài)庫。
兩種方式的區(qū)別
- 在編譯時(shí)把庫連接到程序:這種方式是在編譯的時(shí)候,就確定了要鏈接的庫文件,然后通過編譯參數(shù)在鏈接時(shí)直接把動(dòng)態(tài)庫的地址空間等等信息連接到程序中。程序在運(yùn)行時(shí),可以直接根據(jù)路徑去尋找動(dòng)態(tài)庫,然后加載到程序中,然后運(yùn)行,這種方式在日常開發(fā)中用的比較多。
- 在程序運(yùn)行時(shí)動(dòng)態(tài)加載庫:這種方式是在程序運(yùn)行時(shí),通過調(diào)用系統(tǒng)函數(shù),把動(dòng)態(tài)庫加載到程序中,然后執(zhí)行動(dòng)態(tài)庫中的代碼。這種方式和編譯時(shí)鏈接的優(yōu)勢(shì)是可以在程序運(yùn)行的過程中動(dòng)態(tài)加載和卸載庫??梢栽诓恍薷脑闯绦虻那疤嵯拢褂眯碌膸?。這種方式,比較常見的應(yīng)用是程序的插件系統(tǒng)。
動(dòng)態(tài)加載庫
不廢話了,直接開始上代碼
在程序運(yùn)行的過程中動(dòng)態(tài)加載庫,需要依賴操作系統(tǒng),所以在不同的系統(tǒng)上有不同的系統(tǒng)調(diào)用函數(shù)。
在linux 上需要用到 dlopen 函數(shù)加載庫,dlclose 函數(shù)釋放庫,dlsym 函數(shù) 查找?guī)旌瘮?shù)
需要的頭文件 #include <dlfcn.h>
在windows 上需要 LoadLibrary 宏加載庫,F(xiàn)reeLibrary 宏釋放庫,GetProcAddress 函數(shù)查找?guī)旌瘮?shù)
需要的頭文件 #include <windows.h>
基類功能
在C++中可以通過定義一個(gè)抽象類來作為所有庫的基類,所有的庫文件都實(shí)現(xiàn)這個(gè)基類,然后重寫基類的純虛函數(shù)。可以在加載到所有庫后,都可以把庫里的類作為抽象類的派生類。
先定義一個(gè)基類 base.h
#ifndef DLOAD_BASE_H #define DLOAD_BASE_H /** ?* 必須實(shí)現(xiàn) moduleName_create 函數(shù),來初始化對(duì)象 ?* extern "C" Base *module1_create() { ?* ? ? return new Module; ?* } ?* ?* //必須實(shí)現(xiàn) moduleName_destroy 函數(shù),來回收對(duì)象 ?* extern "C" void module1_destroy(Base *obj) { ?* ? ? delete obj; ?* } ?*/ class Base { ?public: ? virtual std::string readLine(const std::string &) = 0; ? virtual ~Base() = default; }; #endif //DLOAD_BASE_H
這個(gè)基類的功能很簡(jiǎn)單,只有一個(gè)純虛函數(shù)readLine 這個(gè)函數(shù)會(huì)傳入一個(gè)字符串,然后返回一個(gè)字符串
注釋中的哪兩個(gè)函數(shù),后面會(huì)有詳細(xì)的介紹
實(shí)現(xiàn)一個(gè)模塊
可以把一個(gè)庫看做是一個(gè)模塊,現(xiàn)在實(shí)現(xiàn)一個(gè)模塊
//簡(jiǎn)單的模塊 例子 //轉(zhuǎn)大寫 #include <algorithm> #include <string> #include "../base.h" class Module1 : public Base { ? std::string readLine(const std::string &str) override { ? ? ? std::string str2(str); ? ? ? std::transform(str.begin(), str.end(), str2.begin(), ::toupper); ? ? ? return str2; ? } }; //必須實(shí)現(xiàn) moduleName_create 函數(shù),來初始化對(duì)象 extern "C" Base *module1_create() { ? ? return new Module1; } //必須實(shí)現(xiàn) moduleName_destroy 函數(shù),來回收對(duì)象 extern "C" void module1_destroy(Base *obj) { ? ? delete obj; }
這個(gè)功能非常簡(jiǎn)單,把傳入的字符串轉(zhuǎn)成大寫,然后返回
為什么需要 Base *module1_create() 和 void module1_destroy(Base *obj) 這兩個(gè)函數(shù)
因?yàn)樵诎褞旒虞d完成后,需要使用庫里的函數(shù),但是不能直接查找C++的類,然后再初始化對(duì)象,只能在庫里完成C++對(duì)象的初始化,然后返回對(duì)象的指針。
所以需要在庫里有對(duì)應(yīng)的函數(shù)來初始化對(duì)象和回收對(duì)象,所以就有了這兩個(gè)函數(shù)。
為什么要 extern "C"
因?yàn)镃++有函數(shù)重載的功能,所以編譯器在編譯代碼的時(shí)候,會(huì)對(duì)函數(shù)重命名。但是對(duì)函數(shù)重命名的規(guī)則,沒有統(tǒng)一的標(biāo)準(zhǔn),不同編譯器有不同的規(guī)則。像 module1_create 這個(gè)函數(shù)可能就被重命名成 _Z14module1_create這樣的字符串。這樣后面使用 dlsym 或者 GetProcAddress 函數(shù)查找?guī)炖锏暮瘮?shù)時(shí),就沒法找到對(duì)應(yīng)的函數(shù)了。所以使用extern "C" 讓編譯器使用C的規(guī)則來編譯這段函數(shù)
至于這兩個(gè)函數(shù)的名字 module1_create 和 module1_destroy 沒有強(qiáng)制的要求,但是要有一定的規(guī)范。否則在加載到庫后,沒法根據(jù)函數(shù)名查找到對(duì)應(yīng)的函數(shù)。這里用到的規(guī)則是 模塊名_create 和 模塊名_destroy
加載庫
下面開始加載庫,因?yàn)樵谕南到y(tǒng)下,加載庫調(diào)用的函數(shù)不同,所以使用 宏來完成不用系統(tǒng)下的條件編譯,最終完成加載庫
//聲明創(chuàng)建對(duì)象的函數(shù) typedef Base *(*create)(); //聲明回收對(duì)象的函數(shù) typedef void (*destroy)(Base *); //調(diào)用系統(tǒng)函數(shù),加載動(dòng)態(tài)庫 #ifdef _WIN32 HINSTANCE loadLib(Base **base, const char *path, const char *funName) { ? ? auto handle = LoadLibrary(path); ? ? if (!handle) { ? ? ? ? return nullptr; ? ? } ? ? auto cr = (create) GetProcAddress(handle, funName); ? ? if (cr) { ? ? ? ? *base = cr(); ? ? } ? ? return handle; } //調(diào)用系統(tǒng)函數(shù),卸載動(dòng)態(tài)庫 void freeLib(HINSTANCE handle, Base *obj, const char *funName) { ? ? auto free = (destroy) GetProcAddress(handle, funName); ? ? if (free) { ? ? ? ? free(obj); ? ? } ? ? FreeLibrary(handle); } #else void *loadLib(Base **base, const char *path, const char *funName) { ? ? auto handle = dlopen(path, RTLD_LAZY); ? ? if (!handle) { ? ? ? ? return nullptr; ? ? } ? ? auto cr = (create) dlsym(handle, funName); ? ? if (cr) { ? ? ? ? *base = cr(); ? ? } ? ? return handle; } //調(diào)用系統(tǒng)函數(shù),卸載動(dòng)態(tài)庫 void freeLib(void *handle, Base *obj, const char *funName) { ? ? auto free = (destroy) dlsym(handle, funName); ? ? if (free) { ? ? ? ? free(obj); ? ? } ? ? dlclose(handle); } #endif
在代碼最開始的位置,通過 typedef 聲明了兩個(gè)函數(shù)的指針,在查找到函數(shù)后,把函數(shù)強(qiáng)轉(zhuǎn)成對(duì)應(yīng)的類型,才能在后面使用
使用庫
int main() { ? ? std::string libPath; #ifdef _WIN32 ? ? libPath = std::string("./module/libmodule1" + ".dll"); #else ? ? libPath = std::string("./module/libmodule1" + ".so"); #endif ? ? Base *module = nullptr; ? ? auto handle = loadLib(&module, libPath.c_str(), std::string("module1_create").c_str()); ? ? if (!module) { ? ? ? ? std::cout << "load lib module1" << " fail" << std::endl; ? ? ? ? return 1; ? ? } ? ? std::cout << module->readLine("abc") << std::endl; ? ? return 0; }
現(xiàn)在基本就完成了一個(gè)動(dòng)態(tài)庫的動(dòng)態(tài)加載過程。如果想要拓展,只要再按照這個(gè)規(guī)則,寫一個(gè)新的模塊然后加載上來就可以了。
最后放一個(gè)相對(duì)完整的動(dòng)態(tài)加載的demo,github
到此這篇關(guān)于C++動(dòng)態(tài)加載so/dll庫的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++動(dòng)態(tài)加載so/dll庫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt音視頻開發(fā)之利用ffmpeg實(shí)現(xiàn)倍速播放
這篇文章主要為大家詳細(xì)介紹了在Qt音視頻開發(fā)中如何利用ffmpeg實(shí)現(xiàn)倍速播放功能(半倍速/2倍速/4倍速/8倍速),感興趣的小伙伴可以了解一下2022-11-11C語言實(shí)現(xiàn)簡(jiǎn)單的三子棋小游戲
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)簡(jiǎn)單的三子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03Qt6.0+vs2019環(huán)境配置的實(shí)現(xiàn)教程
這篇文章主要介紹了Qt6.0+vs2019環(huán)境配置的實(shí)現(xiàn)教程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03C語言實(shí)現(xiàn)經(jīng)典掃雷小游戲的示例代碼
掃雷游戲是在一個(gè)指定的二維空間里,隨機(jī)布置雷,把不是雷的位置都找出來,在你點(diǎn)一個(gè)位置的時(shí)候它會(huì)顯示它周圍全部雷的個(gè)數(shù),根據(jù)這個(gè)線索去找 ,會(huì)更容易贏。本文將用C語言實(shí)現(xiàn)這一經(jīng)典游戲,感興趣的可以嘗試一下2022-11-11vs2017智能感知錯(cuò)誤解決代碼標(biāo)紅但編譯通過問題
這篇文章主要介紹了vs2017智能感知錯(cuò)誤代碼標(biāo)紅但編譯通過問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08C語言lidar_align雷達(dá)里程計(jì)校準(zhǔn)功能詳解
這篇文章主要為大家介紹了C語言lidar_align雷達(dá)里程計(jì)校準(zhǔn)功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03