1. 程式人生 > >windows 下C++動態庫的封裝以及呼叫

windows 下C++動態庫的封裝以及呼叫

1、一個程式從原始檔編譯生成可執行檔案的步驟:
預編譯 -->  編譯 -->  彙編 --> 連結
(1)預編譯,即預處理,主要處理在原始碼檔案中以“#”開始的預編譯指令,如巨集展開、處理條件編譯指令、處理#include指令等。
(2)編譯過程就是把預處理完的檔案進行一系列詞法分析、語法分析、語義分析以及優化後生成相應的彙編程式碼檔案。
(3)彙編是將彙編程式碼轉變成二進位制檔案。
(4)連結將二進位制檔案連結成一個可執行的命令,主要是把分散的資料和程式碼收集併合成一個單一的可載入並可執行的的檔案。連結可以發生在程式碼靜態編譯、程式被載入時以及程式執行時。連結過程的主要工作是符號解析和重定位。
2、庫
庫是一組目標檔案的包,就是一些最常用的程式碼編譯成目標檔案後打包存放。而最常見的庫就是執行時庫(Runtime Library),如C執行庫CRT.
庫一般分為兩種:靜態庫(.a 、.lib)動態庫(.so 、.dll )所謂靜態、動態是指連結過程。

3、靜態庫與動態庫 靜態庫: 函式和資料被編譯進一個二進位制檔案(通常副檔名為.LIB)。在使用靜態庫的情況下,在編譯連結可執行檔案時,連結器從庫中複製這些函式和資料並把它們和應用程式的其它模組組合起來建立最終的可執行檔案(.EXE檔案)。
靜態庫有兩個重大缺點: 1)空間浪費
2)靜態連結對程式的更新、部署和釋出會帶來很多麻煩。一旦程式中有任何模組更新,整個程式就要重新連結,釋出給使用者。   動態庫: 把程式按照模組拆分成各個相對獨立的部分,在程式執行時才將它們連結在一起形成一個完整的程式,而不是想靜態連結一樣把所有的程式模組都連結成一個單獨的可執行檔案。 在使用動態庫的時候,往往提供兩個檔案:一個引入庫和一個DLL。 引入庫包含被DLL匯出的函式和變數的符號名,DLL包含實際的函式和資料。在編譯連結可執行檔案時,只需要連結引入庫,DLL中的函式程式碼和資料並不複製到可執行檔案中,在執行的時候,再去載入DLL,訪問DLL中匯出的函式。
特點:
1)程式碼共享,所有引用該動態庫的可執行目標檔案共享一份相同的程式碼與資料。
2)程序升級方便,應用程式不需要重新連結新版本的動態庫來升級,理論上只要簡單地將舊的目標檔案覆蓋掉。
3)在執行時可以動態地選擇載入各種應用程式模組
區別:
(1)lib是編譯時用到的,dll是執行時用到的。如果要完成原始碼的編譯,只需要lib;如果要使動態連結的程式執行起來,只需要dll。 (2)如果有dll檔案,那麼lib一般是一些索引資訊,記錄了dll中函式的入口和位置,dll中是函式的具體內容;如果只有lib檔案,那麼這個lib檔案是靜態編譯出來的,索引和實現都在其中。使用靜態編譯的lib檔案,在執行程式時不需要再掛動態庫,缺點是導致應用程式比較大,而且失去了動態庫的靈活性,釋出新版本時要釋出新的應用程式才行。 (3)動態連結的情況下,有兩個檔案:一個是LIB檔案,一個是DLL檔案。LIB包含被DLL匯出的函式名稱和位置,DLL包含實際的函式和資料,應用程式使用LIB檔案連結到DLL檔案。在應用程式的可執行檔案中,存放的不是被呼叫的函式程式碼,而是DLL中相應函式程式碼的地址,從而節省了記憶體資源。DLL和LIB檔案必須隨應用程式一起發行,否則應用程式會產生錯誤。如果不想用lib檔案或者沒有lib檔案,可以用WIN32 API函式LoadLibrary、GetProcAddress裝載。   下面重點介紹Windows下動態連結庫DLL.
DLL即動態連結庫(Dynamic-Link Libaray)的縮寫,相當於Linux下的共享物件。 Windows系統中大量採用了DLL機制,甚至核心的結構很大程度依賴與DLL機制。 Windows下的DLL檔案和EXE檔案實際上是一個概念,都是PE格式的二進位制檔案。一般的動態庫程式有lib檔案和dll檔案,lib檔案是編譯時期連線到應用程式中的,而dll檔案是執行時才會被呼叫的。 為了更好的理解DLL,首先介紹一下匯出和匯入的概念。
(1)匯出與匯入
在ELF(Linux下動態庫的格式),共享庫中所有的全域性函式和變數在預設情況下都可以被其他模組使用,即ELF預設匯出所有的全域性符號。DLL不同,需要顯式地“告訴”編譯器需要匯出某個符號,否則編譯器預設所有的符號都不匯出。
程式使用DLL的過程其實是引用DLL中匯出函式和符號的過程,即匯入過程。 對於從其他DLL匯入的符號,需要使用“__declspec(dllimport)”顯式宣告某個符號為匯入符號。在ELF中,使用外部符號時,不需要額外宣告該符號是從其他共享物件匯入的。
指定符號的匯入匯出一般有如下兩種方法:
1)MSVC編譯器提供了一系列C/C++的擴充套件來指定符號的匯入匯出,即__declspec屬性關鍵字。
__declspec(dllexport) 表示該符號是從本DLL匯出的
__declspec(dllimport) 表示該符號是從別的DLL中匯入的
2)使用“.def”檔案來宣告匯入到匯出符號,詳細參考《程式設計師的自我修養--連結、裝載與庫》。   4、DLL建立 新建一個Win32專案,命名為myDll--->選擇DLL型別,完成後專案結構如下:

 修改myDll.h

// 下列 ifdef 塊是建立使從 DLL 匯出更簡單的
// 巨集的標準方法。此 DLL 中的所有檔案都是用命令列上定義的 MYDLL_EXPORTS
// 符號編譯的。在使用此 DLL 的
// 任何其他專案上不應定義此符號。這樣,原始檔中包含此檔案的任何其他專案都會將
// MYDLL_API 函式視為是從 DLL 匯入的,而此 DLL 則將用此巨集定義的
// 符號視為是被匯出的。
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
// 此類是從 myDll.dll 匯出的 class __declspec(dllexport) CmyDll { public: CmyDll(void); // TODO: 在此新增您的方法。 }; extern __declspec(dllexport) int nmyDll; __declspec(dllexport) int fnmyDll(void); extern "C" __declspec(dllexport) double seekArea(int r, int h);

myDll.cpp

// myDll.cpp : 定義 DLL 應用程式的匯出函式。
//

#include "stdafx.h"
#include "myDll.h"
#include "stdio.h"



// 這是匯出變數的一個示例
__declspec(dllexport) int nmyDll = 0;

// 這是匯出函式的一個示例。
__declspec(dllexport) int fnmyDll(void)
{
    return 42;
}

// 這是已匯出類的建構函式。
// 有關類定義的資訊,請參閱 myDll.h
CmyDll::CmyDll()
{
    return;
}


void show()
{ 
    printf("Call the library function.\n");    
    printf("***************************\n");
}
double area(int r)
{ 
    return 3.14*r*r;
} 
__declspec(dllexport) double seekArea(int r, int h)
{ 
    show();    
    double under = 3.14*r*r;    
    double v = under*h;    
    return v;
}

編譯就會生成對應的dll檔案,同時也會生成對應的lib檔案。

注意:a.DLL中匯出函式的宣告有兩種方式:在函式宣告中加上__declspec(dllexport);採用模組定義(.def)檔案宣告。詳見:http://www.cnblogs.com/enterBeijingThreetimes/archive/2010/08/04/1792099.html   b.對於C檔案建立dll時或者想使用C編譯器建立dll時,建議使用 extern “C” 標誌

5.DLL的隱式呼叫
隱式連結採用靜態載入的方式,比較簡單,需要.h、.lib、.dll三件套。新建“控制檯應用程式”或“空專案”。配置如下:
專案->屬性->配置屬性->VC++ 目錄-> 在“包含目錄”裡新增標頭檔案testdll.h所在的目錄
專案->屬性->配置屬性->VC++ 目錄-> 在“庫目錄”裡新增標頭檔案testdll.lib所在的目錄

專案->屬性->配置屬性->連結器->輸入-> 在“附加依賴項”裡新增“testdll.lib”(若有多個 lib 則以空格隔開)。//也可以在專案屬性中設定庫的連結,#pragma comment(lib, "DLLSample.lib")

專案結構:

CallMyDll.cpp

// CallmyDll.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include "stdlib.h"
#include "myDll.h"

#pragma comment(lib, "myDll.lib")

extern "C" _declspec(dllimport)  double seekArea(int r, int h);  

int _tmain(int argc, _TCHAR* argv[])
{ 
    int r = 1, h = 5;    
    double area = seekArea(r, h);    
    printf("Area is:%f\n", area);    
    system("pause");    
    return 0;
}

執行前:將動態庫檔案myDll.dll拷貝到可執行檔案目錄下,否則會報錯。執行結果:

 原文: https://blog.csdn.net/zhangfuliang123/article/details/71515796