之前的學習筆記,記錄庫的一點學習心得。主要是Windows下的靜態庫和動態連接庫,動態連接庫只寫了關於非MFC的DLL,比較初級,適合和我同樣的新手看看。有不對的地方請指出,有疏漏的地方請補充,若是您比較閒的話,呵呵,感激涕零。ios
一:靜態連接庫c++
靜態庫(static library)將函數和數據編譯進一個二進制文件,一般能夠命名爲*.lib,編譯器在連接過程當中,將這些二進制數據複製出來,並與調用庫的其餘模塊數據組合在一塊兒,造成最終的可執行文件,等之後使用這個可執行文件時,就不須要這個靜態庫的支持了。windows
1 在Windows下靜態庫的建立和使用api
在vs2010新建一個win32靜態庫工程,添加兩個文件。函數
extern int gVar; extern int add(int lhs, int rhs);//extern能夠省略,由於函數默認做用域是extern class T { public: T(); T(int x, int y); int add(); private: int a; int b; };
#include "../dllTest/staticLib.h" int gVar = 10; int add(int lhs, int rhs) { return lhs + rhs; } T::T():a(0),b(0) {} T::T(int x, int y):a(x),b(y) {} int T::add() { return a+b; }
咱們在庫中聲明瞭一個變量,一個函數,和一個類。而後build會生成一個staticlib.lib文件。爲了測試這個庫文件,咱們新建一個win32控制檯測試工程Test,在main函數中調用庫,有兩種方式:第一,你可使用指令#pragma comment(lib,」staticlib.lib」),第二,能夠在工程屬性裏面添加庫及其路徑,這裏咱們能夠把前面靜態庫工程中的staticLib.h和staticlib.lib拷貝到Test工程目錄下,而後選擇:Property/Linker/General/Additional Library Directories,把staticlib.lib所在目錄添加進來;再選擇Linker/Input/Additional Dependencies,把庫staticlib.lib加進去就能夠了。這裏咱們用第一種方式加入庫,代碼以下:工具
#include <iostream> #include "staticLib.h" #pragma comment(lib,"Debug/staticlib.lib") int main(int argc, char *argv[]) { T t(1,2); std::cout<<gVar<<std::endl; std::cout<<add(1,2)<<std::endl; std::cout<<t.add()<<std::endl; }
輸出結果:
10
3
3
二:動態連接庫學習
故名思義,動態連接庫只有在須要的時候纔會調用,因此相比靜態連接庫來講,有效減小程序運行時佔用的內存空間。咱們先建立一個dll,工程名爲:dlllib。新建兩個文件:dllLibDeclare.h和dllLibImp.cpp。代碼以下:測試
#ifndef _dlllibdeclare_h_ #define _dlllibdeclare_h_ #ifdef DLL_API #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif DLL_API int gVar; DLL_API int add(int lhs, int rhs); class DLL_API T { public: T(); T(int x, int y); int add(); private: int a; int b; }; #endif
實現文件:ui
#include "dlllibdeclare.h" #define DLL_API int gVar = 10; int add(int lhs, int rhs) { return lhs + rhs; } T::T():a(0),b(0) {} T::T(int x, int y):a(x),b(y) {} int T::add() { return a+b; }
編譯以後,會生成一個dlllib.lib文件和一個dlllib.dll文件(此文件雖然也是以.lib結尾,但與上面提到的靜態連接庫仍是有很大差異的,套用一句廣告詞,不是全部的lib都叫靜態庫)。這個文件只是包含了相應的dll的接口說明,並不包含實際的代碼,從文件大小就能夠看出。頭文件裏面定義了DLL_API這個宏,是爲了方便之後調用這個dll的時候,直接利用此頭文件,而不用再次聲明咱們導出的這些數據單元(由於沒有預約義DLL_API這個宏的時候是導入)。說白了,就是方便多個工程重複利用。咱們用vs2010自帶的工具dumpbin查看dll裏面導出的內容是什麼?選擇tool/visual studio command prompt,在命令行窗口輸入以下命令:spa
咱們能夠看到,導出了5個單元,對應這類T的構造函數,拷貝構造函數,類的add成員函數,還有add函數和gVar整型變量。其中因爲有2個同名的add函數,不太好辨認到底哪一個是類的成員,哪個是獨立的類外的函數?沒關係,咱們只要導出函數聲明和最終的dll輸出名之間的映射關係文件就ok了。在工程dlllib的properties/Linker/Debugging裏面設置輸出map信息就能夠查看了:
在Debug模式下從新編譯dlllib,會生成map.txt文件,打開這個文件,在文件最後能夠看到以下內容:
這裏清楚的指明瞭函數的對應關係。好了,下面咱們調用這個dll作一下測試,新建一個工程:dllTest。代碼以下:
#pragma comment(lib,"Debug/dlllib.lib") __declspec(dllimport) int __stdcall add(int lhs, int rhs); int __declspec(dllimport) gVar; class __declspec(dllimport) T { public: T(); T(int x, int y); int add(); private: int a; int b; }; int main(int argc, char *argv[]) { T t(1,2); std::cout<<gVar<<std::endl; std::cout<<add(1,2)<<std::endl; std::cout<<t.add()<<std::endl; }
這裏我沒有用#include 「dllLibDeclare.h」,而是本身聲明瞭dll中的函數和數據,主要是爲了說明__stdcall的問題,這個小地方折磨了我好久,因此拿出來強調一下。注意沒有,我在聲明外部add函數的時候,指明瞭函數的調用方式是__stdcall,若是沒有這個,編譯會報連接錯誤的。由於,在win32中,dll的調用方式默認是WINAPI(也就是__stdcall)的,而對於通常的c/c++工程,函數調用方式是__cdecl的,這二者的差異在於:__stdcall方式與__cdecl對函數名最終生成符號的方式不一樣,若採用C編譯方式(在c++中需將函數聲明爲extern 「C」),__stdcall調用約定在輸出函數名前面加下劃線,後面加」@」符號和參數的字節數,形如_functionname@number;而__cdecl調用約定僅在輸出函數名前面加下劃線,行如_functionname。而對於C++編譯方式,這二者的差異與C的又會有區別,詳見:http://baike.baidu.com/view/1276580.htm
因此,咱們把握一個原則,先後對應。爲了防止本身聲明時的調用類型與dll原型不一致,咱們調用時,最好以包含同一個頭文件的方式進行聲明,並且爲了便於被其它語言程序調用,約定調用方式爲__stdcall。新的」dllLibDeclare.h」中,咱們這樣寫:
DLL_API int __stdcall add(int lhs, int rhs);
而調用dll時,包含這個頭文件便可:
#include "dlllib/dlllibdeclare.h" #pragma comment(lib,"Debug/dlllib.lib")
模塊定義文件:上面的dll中咱們利用__declspec(dllimport)指明導出的數據單元,其實也能夠利用模塊裏定義導出數據。咱們新建一個文件」set.def」,能夠在工程中新建,也能夠手動在別處新建。手動新建的能夠經過在properties/Linker/Input中設定,以下圖:
採用模塊定義這種方式時,Def文件應該是不支持類的導出(有待考證),咱們只導出一個變量和函數:
int gVar = 10; int __stdcall add(int lhs, int rhs) { return lhs + rhs; }
Def的內容爲:
;set dll LIBRARY dlllib EXPORTS add @1 gVar DATA
其中以分號開頭的行表明註釋,且不能與其它內容同行。LIBRARY指明要輸出的庫的名稱,EXPROTS下面是具體要輸出的數據單元,add @1表示輸出的add函數的序號是1,這個序號在顯示調用dll的時候用的上,gVar DATA表示輸出一個全局變量。下面咱們就給出隱式調用這個dll的測試代碼:
#include <iostream> #pragma comment(lib,"Debug/dlllib.lib") extern _declspec(dllimport) int gVar; extern _declspec(dllimport) int __stdcall add(int lhs, int rhs); int main(int argc, char *argv[]) { std::cout<<gVar<<std::endl; std::cout<<add(1,2)<<std::endl; gVar = 2; std::cout<<gVar<<std::endl; }
也能夠顯示調用:
#include <iostream> #include <windows.h> Int main(int argc, char *argv[]) { HMODULE hin; char gvar[] = "gVar"; char add[] = "add"; typedef int (__stdcall *padd)(int, int);//don't forget to set __stdcall hin = LoadLibrary(L"Debug/dlllib.dll"); if(NULL == hin) { std::cout<<"can't load the dll file!"<<std::endl; return 0; } padd addfunc = (padd)GetProcAddress(hin,MAKEINTRESOURCEA(1) /*add*/ /*"add"*/);//three different way to find the function int *pvar = (int*)GetProcAddress(hin,gvar); std::cout<<addfunc(1,2)<<std::endl; std::cout<<*pvar<<std::endl; FreeLibrary(hin); }
Ok,Windows下面的非mfc dll就是這樣的。這樣的dll也能夠被其餘語言調用,這裏給一個在perl裏面調用的例子:
perl調用dll的例子:
use strict; use Win32::API; my $dllPath = "./dlllib.dll"; my $func = new Win32::API($dllPath,"int __stdcall add(int lhs, int rhs)"); if(not defined $func) { die"can't import api\n"; } my $sum = 0; $sum = $func->Call(3,5); print "$sum\n";
好吧,先寫到這裏,之後寫篇Linux下的庫學習心得。