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
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.*後, 程序仍然正常運行。
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的程序時,就有了兩種可選方案。
另兩個重要的、需要區分的概念是:對象庫(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 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共享文件(未完待續...累了,睡覺去)