1. 程式人生 > 其它 >編譯生成 C++ 庫檔案

編譯生成 C++ 庫檔案

目錄

 

我們介紹 Windows 和 Ubuntu 下生成和使用庫檔案的操作。

 

lib

lib 是 Windows 下的靜態庫,它具有以下特點:

  1. 執行不存在
  2. 靜態庫原始碼被連結到呼叫程式中
  3. 目標程式的歸檔

靜態庫是將程式碼嵌入到使用程式中,多個程式使用時會有多份程式碼,所以程式碼體積會增大。動態庫的程式碼只需要存在一份,其它程式通過函式地址使用,所以程式碼體積小。靜態庫發生變化後,新的程式碼需要重新連結嵌入到執行程式中。

 

要建立靜態庫,首先在 VS 中建立庫專案,這樣會產生一些預設框架,如果不想要的話可以先生成控制檯專案,然後設定“專案屬性-配置型別”為靜態庫即可;然後直接新增原始碼和標頭檔案,右擊專案選擇“生成”即可產生 .lib 檔案

 

生成靜態庫後,將 .lib 檔案和標頭檔案複製到指定檔案目錄下匯入

#include "../lib/head.h" 					// 匯入靜態庫標頭檔案
#pragma comment(lib, "../lib/clib.lib") 	 // 使用 pragma 關鍵字設定庫路徑

需要注意,如果使用專案建立靜態庫檔案,之後新建專案測試庫檔案時,要將新建的專案設為啟動專案,否則程式會將庫檔案錯認為可執行檔案,導致報錯

 

有時我們需要使用 C 語言的靜態庫,但由於 C++ 編譯器會對函式進行“換名”,而 C 編譯器則不會,則當呼叫 C 語言靜態庫時會導致函式名無法識別,這時就需要使用 extern 宣告

extern "C" int add(int, int); 			// 以 C 編譯器方式編譯函式宣告
#pragma comment(lib, "../lib/clib.lib")  // 使用 pragma 關鍵字設定庫路徑

 

dll

dll 是 Windows 下的動態庫,它具有以下特點:

  1. 執行時獨立存在
  2. 原始碼不會連結到執行程式
  3. 使用時載入(使用動態庫必須使動態庫執行)

動態庫變化後,如果庫中函式的定義(或地址)未變化,其它使用 DLL 的程式不需要重新連結。

 

動態庫資料存放

  • DLL 檔案中存放函式名及其相對於檔案首地址的相對地址,還有函式原始碼資訊
  • LIB 檔案中存放函式名及其編號,還有 DLL 檔名

需要注意:使用動態庫需要有 .dll 檔案和對應的 .lib 檔案,其中 .lib 不是靜態庫。

 

建立動態庫

  • 建立動態庫專案
  • 新增庫程式
  • 匯出庫程式:提供給庫使用者函式資訊
    • 宣告匯出:使用 _declspec(dllexport) 匯出函式地址
      • 注意:動態庫編譯連結後,也會生成 LIB 檔案作為動態庫函式對映使用,通過 LIB 檔案可以連結到動態庫
    • 模組定義檔案 .def 匯出不改名的函式
//宣告匯出動態庫中的函式
//庫檔案
_declspec(dllexport) int add(int a, int b)
{
    return a + b;
}
//宣告匯出會匯出改名的函式

//模組定義檔案匯出函式 .def
LIBRARY dll_name 	 //匯出的庫名
EXPORTS 			//庫匯出表
func_name1	@1		//匯出的函式1
func_name2	@2		//匯出的函式2
func_name3	@3		//匯出的函式3
   	...
//通過這種方式匯出函式,能夠得到不改名的函式

 

連結動態庫

隱式連結:作業系統負責使動態庫執行

  • 標頭檔案和函式原型:在函式原型宣告前增加 _declspec(dllimport) ;如果宣告在標頭檔案中,需要使用 DLLCLASS_EXPORTS 巨集方法
  • 匯入動態庫的 LIB 檔案
  • 在程式中使用函式
  • dll 存放路徑:
    • 與執行檔案同目錄(推薦)
    • 當前工作目錄
    • Windows 目錄
    • Windows/System32 目錄
    • Windows/System
    • 環境變數 PATH 指定目錄
// 匯入動態庫中的函式
_declspec(dllimport) int add(int, int);

// 通知連結器到 .lib 檔案中獲取函式編號和 .dll 檔名
#pragma comment(lib, "../lib/clib.lib")

 

顯式連結:程式設計師負責使動態庫執行

  • 定義函式指標型別 typedef
  • 載入動態庫 LoadLibrary
  • 獲取函式地址 GetProcAddress
  • 使用函式
  • 解除安裝動態庫 FreeLibrary

我們需要通過以下函式實現

// 返回 DLL 例項控制代碼
HMODULE LoadLibrary(
    LPCTSTR lpFileName	// 動態庫檔名或全路徑
);

// 獲取函式地址
FARPROC GetProcAddress(
    HMODULE hModule,	// DLL 控制代碼
    LPCSTR lpProcName	// 函式名
);

//解除安裝動態庫
BOOL FreeLibrary(
    HMODULE hModule,	// DLL 控制代碼
);

最後給出顯式連結動態庫的示例:

//定義函式指標
typedef int (*ADD)(int, int);

int main()
{
    HINSTANCE hDll = LoadLibrary("CPPdll.dll");		// 載入動態庫
    ADD myAdd = (ADD)GetProcAddress(hDll, "add");	// 這裡使用模組檔案匯出方法,才能使用不改名的函式名為引數
    
    int sum = myAdd(5, 4);
    cout << "sum = " << sum << endl;
    
    FreeLibrary(hDll);							  // 解除安裝動態庫
    return 0;
}

 

動態庫中封裝類

在類名前增加 _declspec(dllexport) 定義,匯出類成員函式的相對地址,通常使用預編譯開關切換類的匯入匯出定義,例如

#ifdef DLLCLASS_EXPORTS
#define EXT_CLASS _declspec(dllexport)
#else
#define EXT_CLASS _declspec(dllimport)
#endif

如果定義了這一巨集,則定義匯出,否則定義匯入。通過這種方式,程式設計師在庫檔案中定義 DLLCLASS_EXPORTS 巨集,就可以使用匯出類;使用者只需要包含標頭檔案,就可以使用匯入類

// 匯入/匯出類
class EXT_CLASS CMath
{
	//...
    int sum(int a, int b);
    int sub(int a, int b);
};

 

a

a 檔案是 Ubuntu 下的靜態庫,其在主函式編譯時就將庫匯入

# 生成 .o 檔案
g++ swap.cpp -Iinclude -c

# 生成靜態庫 libSwap.a
ar rs libswap.a swap.o

# 連結靜態庫生成 main
g++ -o main.cpp -Lsrc -lswap main

# 執行main
./main

注意生成的靜態庫要有 lib 字首,使用時去掉 lib 字首

我們也可以生成 Windows 下的靜態庫檔案

ar rs swap.lib swap.o

 

so

so 檔案是 Ubuntu 下的動態庫,它不會直接編譯進主函式,需要在呼叫時新增。其中使用了 -fPIC 引數,這在 LAPACK 編譯過程中提到,它表示以相對地址來實現程式碼,從而使其可以載入到任意位置

# 生成動態庫 swap.so
g++ swap.cpp -Iinclude -fPIC -shared -o libswap.so
# 以上指令等價於
# gcc swap.cpp -Iinclude -c -fPIC
# gcc -shared -o libswap.so swap.o

# 連結動態庫生成main
g++ -o main.cpp -Lsrc -lswap main

# 執行 main,要將動態庫所在路徑新增到引數
LD_LIBRARY_PATH=src ./main

同樣可以生成 Windows 下的動態庫檔案

gcc -shared -o swap.dll swap.c