文章目錄
- 動態鏈接庫和靜態鏈接庫
- 什么是鏈接庫?
- 靜態鏈接庫
- 動態鏈接庫
- 動態鏈接庫的倆種鏈接方式
- 加載時動態鏈接
- 運行時動態鏈接
動態鏈接庫和靜態鏈接庫
動態鏈接庫和靜態鏈接庫都是共享代碼的方法,只是二者略有區別。
以C/C++為例,一個可執行文件的生成主要包括預編譯、編譯、匯編和鏈接。而靜態鏈接和動態鏈接就是 在鏈接階段的倆種處理。
什么是鏈接庫?
關于代碼復用,有些文件專門用于存儲可以重復使用的代碼塊,例如功能實用的函數或者類,我們通常將它們稱為庫文件,簡稱“庫”(Library)。將這種庫文件進行打包編譯后得到二進制文件就是鏈接庫。
鏈接庫是一個不能獨立運行的二進制文件,它必須經過其他程序調用,才可以載入內存中。
根據鏈接方式的不同,可以分為靜態鏈接庫和動態鏈接庫
靜態鏈接庫
所謂靜態鏈接,就是在程序執行前,將所有目標文件同靜態鏈接庫一起組織成可執行文件,這樣生成的可執行文件可以獨立運行。
采用靜態鏈接庫的方式共享代碼有一個明顯的缺點,那就是文件的體積會很大,因為可執行文件包含了所有目標文件和靜態鏈接庫的數據。這樣容易造成內存空間的浪費。同時,不利用代碼的模塊化:如果有某個模塊需要更新,整個程序都需要重新連接才能運行。
若是有多個程序調用相同函數,內存中就會存在這個函數的多個拷貝。
動態鏈接庫
動態鏈接是相對靜態鏈接而言的,動態鏈接所調用的代碼并沒有被打包到可執行文件中,被拷貝的往往只是某些函數的描述信息(如重定位信息),只有當程序執行的過程中,需要調用到動態庫中的函數式,動態鏈接庫中的函數才會被載入內存中。
一般情況下,一個程序如果使用了動態鏈接庫,系統會保證內存中只有一份DLL的復制品。
動態鏈接庫可以隨可執行文件一同載入內存,也可以在可執行文件運行過程中載入,即可執行文件什么時候需要,動態鏈接庫才會載入內存。
采用動態鏈接庫方便程序的更新,當程序的某個模塊更新后,只需要將舊的模塊替換掉,程序運行時會自動將所有模板載入內存并動態地鏈接在一起。
但是動態鏈接庫也有一定的缺點,靜態鏈接生成的可執行文件能夠在其他同類操作系統上直接運行。但是如果是動態鏈接生成的文件,在移植到其他操作系統上后,需要連同該可執行文件所調用到的DLL文件一并拷貝過去,不然不能保證程序的正常運行。
動態鏈接庫的倆種鏈接方式
動態鏈接實際上還有倆種不同的連接方式:加載時動態鏈接和運行時動態鏈接(隱式加載和顯式加載)
加載時動態鏈接
在加載時動態鏈接中,應用程序像本地函數一樣顯式調用導出的 DLL 函數。要使用加載時動態鏈接,請在編譯和鏈接應用程序時提供頭文件 (.h) 和導入庫 (.lib) 文件。執行此操作時,鏈接器將為系統提供加載 DLL 所需的信息,并在加載時解析導出的 DLL 函數位置。
使用加載時動態鏈接,同靜態鏈接有一個相同的缺點,那就是如果程序的體積稍大,程序開始時加載的時間就會過長。
加載時動態鏈接和靜態鏈接的區別:
- 鏈接的時機:加載時動態鏈接是在程序加載時程序才會將動態庫載入到內存中,而靜態鏈接則是在編譯的時候就已經將靜態庫的代碼和數據嵌入到可執行文件中了。
正如上面所說的,使用加載時動態鏈接,需要提供頭文件 (.h) 和導入庫 (.lib) 文件。可以直接在源碼中引入.lib文件。
例如:
#pragma comment(lib, "dllDemo.lib")
為了更好的模塊化設計,也可以將lib中所要用到函數聲明放在頭文件中。
例如:
//dllDemo.h
#ifndef _DLLDEMO_H
#DEFINE _DLLDEMO_H#pragma comment(lib,"dllDemo.lib")
_declspec(dllexport) int add(int, int);
_declspec(dllexport) int sub(int, int);#endif
之后的主程序中記得
#include "dllDemo.h"
上述代碼還用了_declspec(dllimport)
標識符聲明函數來自動態鏈接庫。
運行時動態鏈接
在運行時動態鏈接中,應用程序調用LoadLibrary
函數或LoadLibraryEx
函數在運行時加載DLL。 DLL成功加載后,可以使用GetProcAddress
函數獲取要調用的導出DLL函數的地址。當您使用運行時動態鏈接時,不需要導入庫文件。
LoadLibrary
函數的作用是將指定的模塊加載到調用進程的地址空間中。
函數定義:
HMODULE LoadLibraryA([in] LPCSTR lpLibFileName //模塊的名稱。這可以是庫模塊(.dll 文件)也可以是可執行模塊(.exe文件)
);
如果調用成功,將會返回該模塊的句柄。
當得到該模塊的句柄后,可以使用GetProcAddress
函數,它從指定的動態鏈接庫 (DLL) 檢索導出函數(也稱為過程)或變量的地址。
函數原型:
FARPROC GetProcAddress([in] HMODULE hModule, //該模塊的句柄[in] LPCSTR lpProcName //函數或變量的名稱
);
如果調用成功,則返回導出函數或變量的地址。
示例:
#include <windows.h>int main() {// 加載動態鏈接庫HMODULE hLibrary = LoadLibrary("example.dll");if (hLibrary != NULL) {// 獲取函數地址FARPROC functionAddress = GetProcAddress(hLibrary, "exampleFunction");if (functionAddress != NULL) {// 調用動態鏈接庫中的函數typedef void (*FunctionType)();FunctionType myFunction = (FunctionType)functionAddress;myFunction();}// 卸載動態鏈接庫FreeLibrary(hLibrary);}return 0;
}
以上關于運行時動態鏈接的實例是基于Windows的,如果是在linux上使用運行時動態鏈接,則需要通過使用 dlopen
和 dlsym
以及 dlclose
函數。
實例:
#include <dlfcn.h>int main() {// 加載動態鏈接庫void* libraryHandle = dlopen("libexample.so", RTLD_LAZY);if (libraryHandle != NULL) {// 獲取函數地址void (*myFunction)() = (void (*)())dlsym(libraryHandle, "exampleFunction");if (myFunction != NULL) {// 調用動態鏈接庫中的函數myFunction();}// 卸載動態鏈接庫dlclose(libraryHandle);}return 0;
}
參考
https://learn.microsoft.com/en-us/troubleshoot/windows-client/deployment/dynamic-link-library
https://c.biancheng.net/dll/what_is_library.html
https://blog.csdn.net/fuzhongmin05/article/details/54616520
https://blog.csdn.net/u010154760/article/details/45689899?spm=1001.2014.3001.5502