1. 程式人生 > 其它 >C語言動態連結庫和靜態連結庫

C語言動態連結庫和靜態連結庫

前言:用慣了IDE(整合開發環境),或者說一開始就用的IDE,出了什麼問題大腦一片空白,只能去網上查詢資料,尤其是庫的連結方面的知識,IDE只是簡簡單單的描述:包含對應庫的目錄,匯入包,但這一句話背後IDE究竟做了什麼,越早理解這個底層的東西以後就能省越多的時間.

devc++是一款經典的c/c++IDE,它採用的是MINGW的GCC編譯器,注意這個編譯器是linux上移植到windows中的編譯器,這一點對於理清整個過程很重要.

linux中靜態庫字尾為.a,動態庫字尾為.so, 而windows中靜態庫字尾為.lib,動態庫字尾為.dll

這裡先去介紹在windows環境下用gcc來編譯生成.a 和 .so檔案

我們先在Dev-Cpp\MinGW64\bin資料夾中找到gcc,把它的路徑拷貝下來,然後到F:盤(任意盤都行)建立一個空資料夾a_so中(任意名字都行),然後再在這個資料夾中建立一個.c檔案,全名a.c

檔案內容我們這樣寫,

int fun(int in){
    return in; 
}

接著開啟cmd,先寫對應碟符,然後跳到gcc的所在目錄,命令列如下:

D:
cd D:.../省略一些中間目錄/bin/ 

打上如下命令

gcc -c F:\a_so\a.c -o F:\a_so\a.o

這樣你就得到了一個.o檔案,接下來再輸入一條命令

ar -v -q F:\a_so\libstatic_lib.a F:\a_so\a.o

-v是顯示打包的細節:給你一個被打包的列表,-q是強制覆蓋之前打過的包.

這樣你就把一個.o檔案打包為靜態庫檔案libstatic_lib.a了(靜態庫檔案要遵守前面有lib的規則),接著我們試著呼叫,在a.c同級目錄下建立test.c,裡面這樣寫

#include<stdio.h>
#include"a.h"
int main(){
   int a=fun(6);
   printf("%d",a); 
  getchar();
return 0; }

這裡有一個a.h,其實是因為我們的程式找不到fun函式,所以我們寫個標頭檔案讓它找到,標頭檔案裡面的內容是:

int fun(int in);

這時候如果我們在命令列中執行這一個指令

gcc F:\a_so\test.c -o F:\a_so\test.exe -L"F:\a_so" -lstatic_lib -static-libgcc

-l是指定庫,按照規則,靜態庫都是libname.a的格式,可以簡寫為-lname,當然你也可以把-L"" -lname直接換成F:\a_so\libstatic_lib.a,-L是指定庫目錄,-static-libgcc是強制匯入靜態庫,

然後在a_so的目錄你就會看到test.exe檔案,執行後你就會得到這個結果

 接下來是動態庫

動態庫建立:

在a_so檔案下建立so.c檔案,裡面這樣寫:

int add(int a,int b){
    return a+b;    
}  
int multiplication(int a,int b){
    return a*b;
} 

然後再命令列中:

gcc F:\a_so\so.c -o F:\a_so\libDynamic_lib.so --shared

這樣就得到一個.so檔案,接著我們先動態呼叫它

建立d_d_use.c,檔案中這樣寫:

#include<stdio.h>
#include<windows.h>
int main() {
    typedef int (*pfunc)(int x,int y);//定義函式指標 
    HINSTANCE dllHandle =LoadLibrary(TEXT("libDynamic_lib.so"));//控制代碼例項,載入庫,如果是dll就改so為dll 
    pfunc pf1 = (pfunc)GetProcAddress(dllHandle,TEXT("add"));//宣告函式指標並賦值 
    pfunc pf2 = (pfunc)GetProcAddress(dllHandle,TEXT("multiplication"));
    if(pf1!=NULL) printf("%d\n",pf1(5,6));//呼叫 
    if(pf2!=NULL) printf("%d\n",pf2(5,6));
    FreeLibrary(dllHandle);//釋放 
    return 0; 
} 

然後直接在dev中編譯執行,我們會得到如下結果:

 正好符合預期(低版本windows庫可能不支援so檔案的載入),其實把動態連結庫輸出換成.dll也是同樣的結果,這裡就不重複演示了

接下來是靜態連結動態庫,之所以之後才說這個是因為為了簡化,動態庫建立的時候我沒有去匯出函式

接下來建立d_s_use.c,檔案中這樣寫:

#include<stdio.h>
__declspec(dllimport) int add(int a,int b);
__declspec(dllimport) int multiplication(int a,int b);
int main() {
    printf("%d\n",add(5,6));
    printf("%d\n",multiplication(5,6));
  getchar();
return 0; }
__declspec(dllimport)是匯入函式的意思如果去掉就變成我們在c檔案中宣告函數了

之後,先不慌,靜態匯入還需要.lib/.a檔案,我們用gcc命令列把建立.so檔案的so.c利用一下,建立為.a檔案

gcc F:\a_so\so.c -o F:\a_so\libDynamic_lib.a --shared

然後再把我們測試靜態呼叫.so檔案的c檔案用命令列編譯連結成.exe

gcc F:\a_so\d_s_use.c -o F:\a_so\d_s_use.exe F:\a_so\libDynamic_lib.a F:\a_so\libDynamic_lib.so

把需要的兩個庫檔案連結起來,這兩者缺一不可,最後我們得到.exe檔案,執行一下得到:

看起來符合預期,但真的是這樣嗎?

其實我們呼叫的.a檔案是so.c檔案生成的,而這個檔案已經實現了這兩個函式,所以實際上我們不能確定到底呼叫的是,a裡面的內容還是.so裡面的內容

雖然如果你把.so檔案去掉會顯示缺失so檔案,不過為了嚴謹起見,我們重新再來一遍

這個時候我們的a_so資料夾裡面挺亂的,我們再在這下面建立一個newtmp資料夾來存放重寫的檔案

所以我們重寫一下so.c作為n_so.c內容如下:

__declspec(dllexport) int add(int a,int b){
    return a+b;    
}  
__declspec(dllexport) int multiplication(int a,int b){
    return a*b;
} 
__declspec(dllexport)和__declspec(dllimport)類似,是用來匯出的,import是匯入

然後我們執行gcc命令:

gcc -shared -o F:\a_so\newtmp\n_so.so F:\a_so\newtmp\n_so.c -Wl,--out-implib,F:\a_so\newtmp\n_so_a.a

--out-implib是建立它的匯入庫lib檔案,不可缺少,Wl其實是穿多個引數的指令

然後我們就得到了.so和匯入庫.a檔案然後我們寫個測試檔案tst_so_s.c:
#include<stdio.h>
__declspec(dllimport) int add(int a,int b);
__declspec(dllimport) int multiplication(int a,int b);
int main() {
    printf("%d\n",add(5,6));
    printf("%d\n",multiplication(5,6));
    getchar(); 
    return 0; 
}
接著執行命令列
gcc F:\a_so\newtmp\tst_so_s.c -o F:\a_so\newtmp\tst_so_s.exe F:\a_so\newtmp\n_so_a.a F:\a_so\newtmp\n_so.so

上述命令列是把c檔案處理輸出為.exe,連結.a匯入庫和.so動態庫,其實光連結.a或者.so都能通過,但不能都沒有,說明.a檔案內部連結了.so檔案的內容

然後我們得到了一個exe檔案,執行結果如下:

為了驗證.a檔案裡面沒有實現函式,我們把.so檔案給去掉,再執行exe檔案,你會得到這個結果:

 可以發現最終.a檔案是呼叫了so檔案中函式的實現,

做了一個映象實驗,把.a檔案移除,發現還能執行,說明.a檔案是給開發者使用的,用來得到函式集.

細心的朋友可能發現這裡我沒有用.h檔案,這是為了簡化最初的邏輯,其實原理是這樣的,如果你寫了.h和.c檔案,編譯器的第一步就是預編譯,把兩者結合作為.i檔案輸出,我們通常的一些諸如#define的預編譯也是寫在.c檔案中的不是?