Lib和Dll的那點事
搞程式開發的朋友應該對Lib和Dll很熟悉,對於這兩個東西,可謂是幾家歡喜幾家憂,喜歡的人覺得它可以封裝程式碼,避免別人剽竊,不喜歡的人覺得它很麻煩,幹嘛不直接用原始檔。而特別是新手對於Lib和Dll的關係和使用完全搞不清楚。
Lib稱為靜態連結庫(static link library),是在編譯的連結期間使用的,他裡面其實就是原始檔的函式實現。
Dll成為動態連結庫(Dynamic link library),是在程式執行時動態呼叫的,runtime時使用,它裡面包含了原始檔的函式實現、DllMain入口函式和.def檔案。
先說說Lib庫吧,相對來說大家對它比dll熟悉一些。
Lib庫
Lib庫有兩種,一種就是常見的普通Lib(static Lib),還有一種大家經常下載的開原始碼編譯後,會產生Lib和dll,其中Lib只是Dll的附帶品,是DLL匯出的函式列表檔案而已,暫且稱之為Dynamic Lib。
兩者都是二進位制檔案,兩者都是在連結是呼叫的,使用static lib的exe可直接執行,使用dynamic lib的exe需要對應的dll才能執行。下來我們來看如何產生並使用一個static lib檔案。
這裡假設我們的工具是VS2005(包含)以上的版本,其他的工具都是大同小異的,就不做介紹了。
1.建立win32控制檯工程
2.在應用程式設定的步驟,選擇”靜態庫 static Library”
3.完成即可 (這裡只是針對最簡單的Dll,Win32 Application的方式稍有不同)
這樣一個靜態Lib庫的工程就建好了。程式碼如下:
////////////////////////////////////////////////////////////////////////// // Function.h ////////////////////////////////////////////////////////////////////////// void Print();
////////////////////////////////////////////////////////////////////////// // Function.cpp ////////////////////////////////////////////////////////////////////////// #include "Function.h" void Print() { std::cout << "Hello world!" << std::endl; }
編譯會生成一個以工程名作為名稱的Lib檔案。
在你的專案工程屬性中包含這個Lib檔案的標頭檔案目錄和Lib檔案目錄。
頭目錄包含方法:專案屬性(Alt + F7) -> 配置屬性 -> C/C++ -> 常規 -> 附加包含目錄,裡面包含你的Lib庫的標頭檔案,你可以使用絕對路徑,也可以使用VS中巨集表示的相對路徑,建議使用相對路徑。
Lib檔案包含方法:專案屬性(Alt + F7) -> 配置屬性 -> 連結器 -> 常規 -> 附加庫目錄,在這裡面填寫你Lib檔案的路徑。專案屬性(Alt + F7) -> 配置屬性 -> 連結器 -> 常規 -> 輸入,在這裡面填寫你Lib檔案的名稱,例如: Function.lib
這樣你在你的程式碼裡就可以這樣使用了:
#include <stdio.h> #include <stdlib.h> #include "Function.h" int _tmain(int argc, _TCHAR* argv[]) { Print(); system("pause"); return 0; }
這樣就是一個完整生成並使用Lib庫的例子。
當然了,你還可以使用#pragma comment(Lib, “LibPath”)的方法來呼叫Lib檔案。
==============================================================
Dynamic Lib的呼叫方法與Static lib完全一致,唯一的區別就是使用Dynamic Lib編譯出來的程式,執行時需要其對應的Dll檔案。前面我們已經說過了。
DLL
下來我們好好談談Dll的問題,相對於Lib來說,Dll使用的頻率應該是非常高的了,因為你的程式執行,系統執行等等都靠它,MS也是因為這個才導致作業系統封裝的越來越好了。
Dll其實和Exe是幾乎完全一樣的,唯一的不一樣就是Exe的入口函式式WinMain函式(console程式是main函式),而Dll是DllMain函式,其他完全是一樣的。所以有人也戲稱Dll是不能自己執行的Exe。
Dll建立的過程也比較簡單,唯一麻煩的就是需要定義匯出函式介面。
建立Dll工程過程很簡單,建立win32控制檯工程,在應用程式設定的步驟,選擇”動態庫 Dynamic Library”,完成即可。(這裡只是針對最簡單的Dll,Win32 Application的方式稍有不同)
定義匯出函式介面有兩種方式:
1.使用__declspec巨集
// dllmain.cpp : 定義 DLL 應用程式的入口點。 #include "stdafx.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
////////////////////////////////////////////////////////////////////////// // Fucntion.h ////////////////////////////////////////////////////////////////////////// extern "C" __declspec(dllexport) void Print(void);
////////////////////////////////////////////////////////////////////////// // Function.cpp ////////////////////////////////////////////////////////////////////////// #include "Function.h" void Print(void) { std::cout << "[Dll] Hello world!" << std::endl; }
這裡假設我們的工程名叫Function,那麼 編譯後會生成一個Function.dll和一個Function.lib(Dynamic lib),Dynamic lib前面已經說過,此處不再贅述了。
Function.h標頭檔案中的
extern "C" __declspec(dllexport) void Print(void);
我們只需要清楚其中函式的名稱,返回值,引數就可以了。
extern “C”表示我們要按照C語言的方式編譯該函式,防止在C++工程中編譯出現函式名錯誤,因為C++中有函式過載,所以函式名編譯後可能會出現[email protected]的形式;而且這樣也可以讓C呼叫C++的動態連結庫;__declspec(dllexport)表示下來的函式是dll的匯出函式介面。
2.使用def檔案,類似於宣告匯出介面的方式,不過卻不需要聲明瞭,因為它專門定義了一個def檔案來說明
// dllmain.cpp : 定義 DLL 應用程式的入口點。 #include "stdafx.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
////////////////////////////////////////////////////////////////////////// // Fucntion.h ////////////////////////////////////////////////////////////////////////// void Print(void);
////////////////////////////////////////////////////////////////////////// // Function.cpp ////////////////////////////////////////////////////////////////////////// #include "Function.h" void Print(void) { std::cout << "[Dll] Hello world!" << std::endl; }
////////////////////////////////////////////////////////////////////////// // Fucntion.def ////////////////////////////////////////////////////////////////////////// EXPORTS Print
同樣,類似於Static Lib的顯式呼叫方法,dll也可以顯式呼叫,前提是我們很清楚函式名、返回值、引數列表。
#include <stdlib.h> #include "Function.h" #include <windows.h> int _tmain(int argc, _TCHAR* argv[]) { HINSTANCE hInstance = LoadLibrary("Function.dll"); typedef void(*_Print)(void); _Print printFunction; if (hInstance != NULL) { printFunction = (_Print)GetProcAddress(hInstance, "Print"); } printFunction(); FreeLibrary(hInstance); system("pause"); return 0; }
當然了,有顯式自然還有隱式呼叫了,這個時候Function.dll的伴生產物Function.lib就可以派上用場了,其使用方法和靜態lib完全相同。
DLL呼叫的兩種方法各有利弊:採用尋找DLL中函式地址的方法,優點是隻要函式形參沒變化,那麼修改了函式實現也沒關係,不需要重新編譯Exe,只需要將新的DLL檔案拷貝過來即可,大型專案上使用比較靈活;缺點是比較麻煩,需要定義例項,函式指標,載入DLL,釋放DLL等過程。而採用Dynamic Lib的方法,優點是容易理解和接受(因為他跟靜態庫的呼叫方法類似);缺點是修改了DLL工程的任何東西都需要使用最新的Dynamic Lib重新能編譯Exe。
總結:
DLL和Lib是各有千秋,使用的情況也是各不相同,不過最終還是需要大家在專案中實踐到底哪種方法好,到底採用哪種型別的庫,總之,一切都要按需求最優。