日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux下动态链接库(.so)的显式调用和隐式调用

發布時間:2024/4/11 linux 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux下动态链接库(.so)的显式调用和隐式调用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

進入主題前,先看看兩點預備知識。

一、顯式調用和隱式調用的區別

???? ?? 我們知道,動態庫相比靜態庫的區別是:靜態庫是編譯時就加載到可執行文件中的,而動態庫是在程序運行時完成加載的,所以使用動態庫的程序的體積要比使用靜態庫程序的體積小,并且使用動態庫的程序在運行時必須依賴所使用的動態庫文件(.so文件),而使用靜態庫的程序一旦編譯好,就不再需要依賴的靜態庫文件了(.a文件)。

??????? 動態庫的調用又分為顯示和隱式兩種方式,區別如下:

??????? 1、 隱式調用需要調用者寫的代碼量少,調用起來和使用當前項目下的函數一樣直接;而顯式調用則要求程序員在調用時,指明要加載的動態庫的名稱和要調用的函數名稱。

??????? 2、隱式調用由系統加載完成,對程序員透明;顯式調用由程序員在需要使用時自己加載,不再使用時,自己負責卸載。

??????? 3、由于顯式調用由程序員負責加載和卸載,好比動態申請內存空間,需要時就申請,不用時立即釋放,因此顯式調用對內存的使用更加合理, 大型項目中應使用顯示調用。

??????? 4、當動態鏈接庫中只提供函數接口,而該函數沒有封裝到類里面時,如果使用顯式調用的方式,調用方甚至不許要包含動態鏈接庫的頭文件(需要調用的函數名是通過dlsym函數的參數指明的),而使用隱式調用時,則調用方不可避免要加上動態庫中的頭文件,g++編譯時還需要要用參數-I指明包含的頭文件的位置。需要注意的是,當動態鏈接庫中的接口函數是作為成員函數封裝在類里面時,即使使用顯式調用的方式,調用方也必須包含動態庫中的相應頭文件(詳見五、顯示調用動態鏈接中的類成員函數)。

????????5、顯式調用更加靈活,可以模擬多態效果(具體見后文)。


二、extern "C"的作用

??????? C++程序(或庫、目標文件)中,所有非靜態(non-static)函數在二進制文件中都是以“符號(symbol)”形式出現的。這些符號都是唯一的字符串,從而把各個函數在程序、庫、目標文件中區分開來。在C中,符號名正是函數名,兩者完全一樣。而C++允許重載(不同的函數有相同的名字但不同的參數,甚至const重載),并且有很多C所沒有的特性──比如類、成員函數、異常說明──幾乎不可能直接用函數名作符號名。為了解決這個問題,C++采用了所謂的name mangling。它把函數名和一些信息(如參數數量和大小)雜糅在一起,改造成奇形怪狀,只有編譯器才懂的符號名。例如,被mangle后的foo可能看起來像foo@4%6^,或者,符號名里頭甚至不包括“foo”。

??????? 其中一個問題是,C++標準并沒有定義名字必須如何被mangle,所以每個編譯器都按自己的方式來進行name mangling。有些編譯器甚至在不同版本間更換mangling算法(尤其是g++ 2.x和3.x)。前文說過,在顯示調用動態庫中的函數時,需要指明調用的函數名,即使您搞清楚了您的編譯器到底怎么進行mangling的,從而知道調用的函數名被C++編譯器轉換為了什么形式,,但可能僅僅限于您手頭的這個編譯器而已,而無法在下一版編譯器下工作。

extern "C"即可以解決這個問題。用 extern "C"聲明的函數將使用函數名作符號名,就像C函數一樣。因此,只有非成員函數才能被聲明為extern "C",并且不能被重載。盡管限制多多,extern "C"函數還是非常有用,因為它們可以象C函數一樣被dlopen動態加載。冠以extern "C"限定符后,并不意味著函數中無法使用C++代碼了,相反,它仍然是一個完全的C++函數,可以使用任何C++特性和各種類型的參數。所以extern "C" 只是告訴編譯器編和鏈接的時候都用c的方式的函數名字,函數里的內容可以為c的代碼也可以為c++的。


???????有了上面兩個預備知識后,下面以實際例子來演示兩種不同的動態庫調用方式。例子的結構組織為如下:

????so1.h和so1.cc是第一個動態庫中的文件,會編譯鏈接為libso1.so;so2.h和so2.cc是第一個動態庫中的文件,會編譯鏈接為libso2.so;test.cc是調用兩個動態庫的程序。


三、顯式調用

so1.h:

[cpp]?view plain?copy ?
  • extern?"C"?void?fcn();??
  • so1.cc:

    [cpp]?view plain?copy ?
  • #include?<iostream>??
  • #include?"so1.h"??
  • ??
  • void?fcn()?{??
  • ????std::cout?<<?"this?is?fcn?in?so1"?<<?std::endl;??
  • }??
  • so1的makefile:

    [plain]?view plain?copy ?
  • libso1.so:so1.o??
  • ????g++?so1.o?-shared?-o?libso1.so??
  • so1.o:so1.cc?so1.h??
  • ????g++?-c?so1.cc?-fPIC?-o?so1.o??
  • ??
  • .PHONY:clean??
  • clean:??
  • ????rm?so1.o?libso1.so??
  • make之后,將生成的libso1.so拷貝到test.cc所在目錄下。


    so2.h:

    [cpp]?view plain?copy ?
  • extern?"C"?void?fcn();??
  • so2.cc:
    [cpp]?view plain?copy ?
  • #include?<iostream>??
  • #include?"so2.h"??
  • ??
  • void?fcn()?{??
  • ????std::cout?<<?"this?is?fcn?in?so2"?<<?std::endl;??
  • }??
  • so2的makefile:

    [plain]?view plain?copy ?
  • libso2.so:so2.o??
  • ????g++?so2.o?-shared?-o?libso2.so??
  • so2.o:so2.cc?so2.h??
  • ????g++?-c?so2.cc?-fPIC?-o?so2.o??
  • ??
  • .PHONY:clean??
  • clean:??
  • ????rm?so2.o?libso2.so??
  • make之后,將生成的libso2.so拷貝到test.cc所在目錄下。


    test.cc:

    [cpp]?view plain?copy ?
  • #include?<iostream>??
  • #include?<cstdlib>??
  • #include?<dlfcn.h>??
  • ??
  • using?namespace?std;??
  • ??
  • int?main(int?argc,?char?**argv)?{??
  • ????if(argc?!=?2)?{??
  • ????????cout?<<?"argument?error!"?<<?endl;??
  • ????????exit(1);??
  • ????}??
  • ??
  • ????//pointer?to?function??
  • ????typedef?void?(*pf_t)();??
  • ??????
  • ????char?*err?=?NULL;??
  • ????//open?the?lib??
  • ????void?*handle?=?dlopen(argv[1],?RTLD_NOW);??
  • ??????
  • ????if(!handle)?{??
  • ????????cout?<<?"load?"?<<?argv[1]?<<?"failed!?"?<<?dlerror()?<<?endl;??
  • ????????exit(1);??
  • ????}??
  • ??
  • ????//clear?error?info??
  • ????dlerror();??
  • ??
  • ????pf_t?pf??=?(pf_t)dlsym(handle,?"fcn");??
  • ????err?=?dlerror();??
  • ????if(err)?{??
  • ????????cout?<<?"can't?find?symbol?fcn!?"?<<?err?<<?endl;??
  • ????????exit(1);??
  • ????}??
  • ??
  • ????//call?function?by?pointer??
  • ????pf();??
  • ??
  • ????dlclose(handle);??
  • ??
  • ????return?0;??
  • }??
  • test的makefile:

    [plain]?view plain?copy ?
  • test:test.o??
  • ????g++?test.o?-lso1?-L.?-lso2?-L.?-ldl?-Wl,-rpath=.?-o?test??
  • test.o:test.cc???
  • ????g++?-c?test.cc?-o?test.o??
  • make之后,終端運行結果如下:

    可以看到這里,通過輸入不同的參數,調用了不同的共享庫中的fcn函數,是一種多態的表現,許多軟件的不同插件就是這樣實現的。

    需要注意的是,要使用顯式調用的方式,必須加入頭文件dlfcn.h,makefile中的鏈接命令中要加入參數-ldl,否則報錯。

    dlfcn.h中提供的API說明如下:

    1)??????? dlopen

    函數原型:void *dlopen(const char *libname,int flag);

    功能描述:dlopen必須在dlerror,dlsym和dlclose之前調用,表示要將庫裝載到內存,準備使用。如果要裝載的庫依賴于其它庫,必須首先裝載依賴庫。如果dlopen操作失敗,返回NULL值;如果庫已經被裝載過,則dlopen會返回同樣的句柄。

    參數中的libname一般是庫的全路徑,這樣dlopen會直接裝載該文件;如果只是指定了庫名稱,在dlopen會按照下面的機制去搜尋:

    a.根據環境變量LD_LIBRARY_PATH查找

    b.根據/etc/ld.so.cache查找

    c.查找依次在/lib和/usr/lib目錄查找。

    flag參數表示處理未定義函數的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暫時不去處理未定義函數,先把庫裝載到內存,等用到沒定義的函數再說;RTLD_NOW表示馬上檢查是否存在未定義的函數,若存在,則dlopen以失敗告終。

    2)??????? dlerror

    函數原型:char *dlerror(void);

    功能描述:dlerror可以獲得最近一次dlopen,dlsym或dlclose操作的錯誤信息,返回NULL表示無錯誤。dlerror在返回錯誤信息的同時,也會清除錯誤信息。

    3)??????? dlsym

    函數原型:void *dlsym(void *handle,const char *symbol);

    功能描述:在dlopen之后,庫被裝載到內存。dlsym可以獲得指定函數(symbol)在內存中的位置(指針)。如果找不到指定函數,則dlsym會返回NULL值。但判斷函數是否存在最好的方法是使用dlerror函數,

    4)??????? dlclose

    函數原型:int dlclose(void *);

    功能描述:將已經裝載的庫句柄減一,如果句柄減至零,則該庫會被卸載。如果存在析構函數,則在dlclose之后,析構函數會被調用。


    四、隱式調用

    隱式調用不需要包含頭文件dlfcn.h,只需要包含動態鏈接庫中的頭文件,使用動態庫中的函數也不需要像顯示調用那么復雜。


    五、顯式調用動態鏈接中的類成員函數

    顯示調用動態鏈接庫的類成員函數,有單獨的寫法,但比較少用。推薦的寫法是為每個要被外部調用的類成員函數設計一個普通的借口函數,在接口函數內部使用類的成員函數。當然這就需要將類設計為單例模式,因為不可能在每個接口函數中都構造一個類的對象。

    總結

    以上是生活随笔為你收集整理的linux下动态链接库(.so)的显式调用和隐式调用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。