windows中dll和linux中so的動態鏈接庫的詳解

1 生成windows中靜態鏈接的靜態庫

和2中linux的完全相同。

2 生成和使用linux中的.a靜態鏈接庫
如下例:

/* hellos.h */
#ifndef _HELLO_S_H
#define _HELLO_S_H

#include <stdio.h>
void printS(char* str);

#endif
輸入命令:
gcc -c -o hellos.o hellos.c
ar cqs libhellos.a hellos.o

linux中.a
文件的名字是有規則的lib[name].a

於是得到了libhellos.a這麼一個靜態鏈接庫

2:主程序
/* main.c */
#include "hellos.h"

void main() {
char* text = "Hello World!\n";
printS(text);
}
編譯鏈接:
gcc -o hello main.c -L. -lhellos  
注意-main.c要放在-L. -lhellos前面,否則出錯。

然後運行hello可以看到輸出
print in static way: Hello World!

刪除libhellos.a和hellos.*後, 程序仍然正常運行。

不知道g++爲什麼不行。


3 生成windows中的dll,並進行隱式鏈接使用

在上面hellos.h hellos.c hellos 三個文件的基礎上。

生成dll:gcc  -shared -o helloc.dll helloc.c
使用dll:gcc -o hello main.c -L. -lhellos

-----------------------------------------------------------------------------------------------------------------------------------------------------------

這樣就將hellos.dll和hello鏈接上了。刪除hellos.dll時候會報錯找不到dll。
感覺這算是gcc提供的一種機制了?

由編譯系統完成對DLL的加載和應用程序結束時DLL卸載的編碼,此處屬於其中的一種方式把。

注意:這裏並沒有進行windows中下面的各種複雜聲明。


windows中的兩種鏈接方法的原理性解釋

鏈接庫分爲靜態鏈接庫和動態鏈接庫,而動態鏈接庫在使用時,又進一步分爲裝載時鏈接和運行時鏈接。裝載時鏈接是指該動態鏈接庫是在程序裝入時進行加載鏈接的,而運行時鏈接是指該動態鏈接庫是在程序運行時執行LoadLibrary(或LoadLibraryEx,下同)函數動態加載的。因此,由於動態鏈接庫有這兩種鏈接方式,所以在編寫使用DLL的程序時,就有了兩種可選方案。

 
    可能有人會問“爲什麼需要裝載時鏈接?直接靜態鏈接不就行了嗎?”,這是模塊化程序設計的需要。試想,如果你開發一個很大的程序,並且經常需要更新。如果你選擇靜態鏈接,那麼每次更新就必須更新整個exe文件,而如果你把需要經常更新的模塊做成dll,那麼只需要更新這個文件即可,每次程序運行時加載這個更新的文件即可。


另兩個重要的、需要區分的概念是:對象庫(Object Library)和導入庫(Import Library)。對象庫是指普通的庫文件,比如C運行時庫libc.lib;而導入庫是一種比較特殊的對象庫文件,與一個動態鏈接庫相對應。它們都有後綴.lib,並且都僅在程序編譯鏈接時使用,被鏈接器用來解析函數調用。然而,導入庫不包含代碼,它只爲鏈接器提供動態鏈接庫的信息,以便於鏈接器對動態鏈接庫中的對象作恰當地鏈接。


關於__stdcall:如果通過VC++編寫的DLL欲被其他語言編寫的程序調用,應將函數的調用約定聲明爲__stdcall方式,WINAPI、CALLBACK都採用這種方式,而C/C++缺省的調用方式卻爲__cdecl。__stdcall方式與__cdecl對函數名最終生成符號的方式不同。若採用C編譯方式(在C++中需將函數聲明爲extern "C"),__stdcall調用約定在輸出函數名前面加下劃線,後面加“@”符號和參數的字節數,形如_functionName@number ,而__cdecl調用約定僅在輸出函數名前面加下劃線,形如_functionName。

這些調用約定是給編譯器使用的,用來確定生成最終生成的符號的。

這個對象庫(Object Library至今不知道怎麼生成)囧了,暫時先不管他。

更細可參考:http://blog.csdn.net/ljx0305/article/details/4513074

---------------------------------------------------------------------------------------------------------------------------------------------------------


4  生成windows中的dll,並進行顯示加載

先寫一個最簡單的顯示動態加載:

dlltest.h:

 #ifndef _DLLTEST_H_
 #define _DLLTEST_H_

 #include <iostream.h>
 #include <stdio.h>
 #include <windows.h>

  extern "C" __declspec(dllexport) void NumberList();
 extern "C" __declspec(dllexport) void LetterList();


 #endif

dlltest.cpp

 #include "dlltest.h"

 extern "C" __declspec(dllexport) 
 void NumberList() {

     cout << "This in in NumberList" << endl;
 }

 extern "C" __declspec(dllexport)
 void LetterList() {

       cout << "This is in LetterList!" << endl;
 }

usedll.cpp

 #include <windows.h>
 #include <iostream.h>
 #include <stdio.h>
 #include <conio.h>

 typedef void (*cfunc)();

 cfunc NumberList;
 cfunc LetterList;

int main() {
   
       HINSTANCE hLib=LoadLibrary("DLLTEST.DLL");


       if(hLib==NULL) {

            cout << "Unable to load library!" << endl;
            return 0;
       }


       NumberList=(cfunc)GetProcAddress((HMODULE)hLib, "NumberList");
       LetterList=(cfunc)GetProcAddress((HMODULE)hLib, "LetterList");

       if((NumberList==NULL) || (LetterList==NULL)) {

            cout << "Unable to load function(s)." << endl;
            FreeLibrary((HMODULE)hLib);
            return 0;
       }

       NumberList();
       LetterList();

       FreeLibrary((HMODULE)hLib);
	   return 0;

 }


 編譯選項:
g++ -shared -o dlltest.dll dlltest.cpp   //生成動態dll。使用了__declspec(dllexport)關鍵字將函數導出。

g++ -o usedll usedll.cpp

運行即有結果

This in in NumberList!

This is in LetterList!



下面解釋一下:extern “C” 和__stdcall ,__cdecl存在的原因
說的簡單點就是爲了讓名字清晰,可以被使用dll的人正確找到函數。可以使用VC帶的Dependency工具查看dll中的export的函數名字

1  默認c語言在編譯時候,使用__cdecl方式進行編譯,這個convention包括很多東西,寄存器,堆棧,命名等。這裏編譯時候c不改變函數的名字。
所以在動態尋找函數名字時候能夠正確的找到。

2  Win32中都使用__stdcall,這樣的話命名方式與__cdecl有些不同,如果不使用def文件,而使用__declspec(dllexport)導出的話,上面dlltest.cpp中的2個函數
會變成

NumberList@0
LetterList@0
這樣如果在usedll的代碼中仍然使用NumberList去找的話,就會找不到。

3 extern “C” 這是爲了避免c++對c中的命名的更改。
因爲c++中支持
作爲一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函數的原型爲:  void foo( int x, int y );  該函數被C編譯器編譯後在符號庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱爲“mangledname”)。_foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y)編譯生成的符號是不相同的,後者爲_foo_int_float。

所以名字也會發生變化,這樣我們也會找不到對應的函數,於是加上extern “C”告訴c++編譯器按照c語言的方式去編譯!


然後其實使用的編譯器g++/gcc,是否定義def文件,dll中的調用方式,caller中的調用方式都會影響到調用是否成功。
水木上總結的是:

測試環境 vc6.0綠色版
新建工程 testdll(win32 dynamic lib)用來生成dll
新建工程 testmydll(win32 console app),顯示調用dll

生成dll文件testdll.cpp
測試dll文件testmydll.cpp
採用depends觀察dll的輸出函數名

1 使用_declspec(dllexport)關鍵字,C編譯,_cdecl
導出函數名  fnTestdll  _cdecl調用成功, __stdcall調用失敗 

2 使用_declspec(dllexport)關鍵字,C編譯,_stdcall
導出函數名  _fnTestdll@4 _cdecl調用失敗, __stdcall調用失敗

3 使用_declspec(dllexport)關鍵字,C++編譯,_cdecl
導出函數名  ?fnTestdll@@YAHH@Z _cdecl調用失敗, __stdcall調用失敗

4.使用_declspec(dllexport)關鍵字,C++編譯,_stdcall
導出函數名  ?fnTestdll@@YGHH@Z _cdecl調用失敗, __stdcall調用失敗

5.使用DEF文件 , C編譯, _cdecl
導出函數名 fnTestdll, _cdecl調用成功, __stdcall調用失敗

6.使用DEF文件,  C編譯, _stdcall
導出函數名 fnTestdll, _cdecl調用失敗, __stdcall調用成功

7.使用DEF文件 , C++編譯, _cdecl
導出函數名 fnTestdll,  _cdecl調用成功, __stdcall調用失敗

8.使用DEF文件,  C++編譯, _stdcall
導出函數名 fnTestdll, _cdecl調用失敗, __stdcall調用成功

結論: 儘可能還是用DEF文件定義輸出函數吧:) 
特別是需要給非C++程序調用(使用__stdcall)的時候.;


5  linux中隱式和顯式調用SO共享文件(未完待續...累了,睡覺去)



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章