編譯生成 C++ 庫檔案
我們介紹 Windows 和 Ubuntu 下生成和使用庫檔案的操作。
lib
lib 是 Windows 下的靜態庫,它具有以下特點:
- 執行不存在
- 靜態庫原始碼被連結到呼叫程式中
- 目標程式的歸檔
靜態庫是將程式碼嵌入到使用程式中,多個程式使用時會有多份程式碼,所以程式碼體積會增大。動態庫的程式碼只需要存在一份,其它程式通過函式地址使用,所以程式碼體積小。靜態庫發生變化後,新的程式碼需要重新連結嵌入到執行程式中。
要建立靜態庫,首先在 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 下的動態庫,它具有以下特點:
- 執行時獨立存在
- 原始碼不會連結到執行程式
- 使用時載入(使用動態庫必須使動態庫執行)
動態庫變化後,如果庫中函式的定義(或地址)未變化,其它使用 DLL 的程式不需要重新連結。
動態庫資料存放
- DLL 檔案中存放函式名及其相對於檔案首地址的相對地址,還有函式原始碼資訊
- LIB 檔案中存放函式名及其編號,還有 DLL 檔名
需要注意:使用動態庫需要有 .dll 檔案和對應的 .lib 檔案,其中 .lib 不是靜態庫。
建立動態庫
- 建立動態庫專案
- 新增庫程式
- 匯出庫程式:提供給庫使用者函式資訊
- 宣告匯出:使用 _declspec(dllexport) 匯出函式地址
- 注意:動態庫編譯連結後,也會生成 LIB 檔案作為動態庫函式對映使用,通過 LIB 檔案可以連結到動態庫
- 模組定義檔案 .def 匯出不改名的函式
- 宣告匯出:使用 _declspec(dllexport) 匯出函式地址
//宣告匯出動態庫中的函式
//庫檔案
_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