C 語言建立和使用 DLL

基礎知識

生成 DLL 時會生成一個與之匹配的 .lib 文件(導入庫文件),用於存放調用 DLL 所需的信息。若是你想調用這個 DLL 則必須將 lib 連接進程序。(導入庫 lib 文件和靜態庫的 lib 雖然都會被連接進程序,但二者的內容徹底不一樣)函數

在 VS 上建立 DLL 須要使用 Microsoft 專用 C\C++ 擴展 —— dllexport 和 dllimport 存儲類特性。可使用它們從 DLL 中導出或向其中導入函數、數據和對象。語法以下:性能

__declspec( dllimport ) declarator
__declspec( dllexport ) declarator

提示: 若是不使用 __declspec(dllexport) 關鍵字導出 DLL 的函數,則 DLL 須要 .def 文件。模塊定義 (.def) 文件是包含一個或多個描述 DLL 各類特性的 Module 語句的文本文件。操作系統

建立動態連接庫

建立一個動態連接庫項目。(Visual Studio:新建 C++ 項目-->Win32控制檯應用,在 應用程序設置 頁面的 應用程序類型 下選擇 DLL)線程

// dll.h
#pragma once

#ifdef FUNCTION_EXPORTS
#define FUNCTIONDLL_API __declspec(dllexport) // 導出,導出用於生成 DLL
#else
#define FUNCTIONDLL_API __declspec(dllimport) // 導入,用於導入 dllexport 導出的內容
#endif

FUNCTIONDLL_API int a(int num1, int num2);
FUNCTIONDLL_API int b(int num1, int num2);
// dll.c
#define FUNCTION_EXPORTS
#include "dll.h"

int a(int num1, int num2)
{
	return num1 + num2;
}

int b(int num1, int num2)
{
	return num1 + num2;
}

生成 DLL ,會獲得 DLL 和 lib 文件。指針

調用/連接 DLL 有兩種方法,一,隱式連接;二,顯示連接。code

爲隱式連接到 DLL,應用程序必須:對象

  • 包含導出函數聲明的頭文件(.h 文件)。函數和數據均應具備 __declspec(dllimport)。
  • 要連接的導入庫(.LIB 文件)。(生成 DLL 時連接器建立的導入庫)
  • 實際的 DLL(.dll 文件)。
  • 操做系統在加載調用可執行文件時,必須可以定位 DLL 文件。

隱式連接後,程序操做 DLL 中函數的方法同本地函數並沒有區別。進程

爲顯式連接到 DLL,應用程序必須:作用域

  • 調用 LoadLibrary(或類似的函數)以加載 DLL 和獲取模塊句柄。
  • 調用 GetProcAddress,以獲取指向應用程序要調用的每一個導出函數的函數指針。因爲應用程序是經過指針調用 DLL 的函數,編譯器不生成外部引用,故無需與導入庫連接(lib 文件)。
  • 使用完 DLL 後調用 FreeLibrary 函數卸載。

我建立的調用程序使用隱式連接:編譯器

#include "dll.h"

int main(void)
{
	printf("%d\n", a(1, 2));
	printf("%d\n", b(3, 4));
	return 0;
}

首先添加引用(兩個項目在同一解決方案下才可引用),項目 --> 添加引用,將 DLL 項目添加進去(添加引用的目的是爲了添加 lib 文件,效果與在 (若是不在同一解決方案,應使用下述方法:) 配置屬性 --> 連接器 --> 輸入 --> 附加依賴項 添加 lib 相同)。

而後添加附加包含目錄,配置屬性 --> C\C++ --> 常規 --> 附加包含目錄,將 dll.h 文件的地址添加進去便可。

若是你的 DLL 和 調用程序再也不同一目錄,能夠將 DLL 所在目錄添加至系統環境變量或將 DLL 同調用程序放至同一目錄。

編譯,執行結果以下:

3
7

關於顯示連接

大部分應用程序使用隱式連接,由於這是最易於使用的連接方法。可是有時也須要顯式連接。下面是一些使用顯式連接的常見緣由:

  • 直到運行時,應用程序才知道須要加載的 DLL 的名稱。例如,應用程序可能須要從配置文件獲取 DLL 的名稱和導出函數名。
  • 若是在進程啓動時未找到 DLL,操做系統將終止使用隱式連接的進程。一樣是在此狀況下,使用顯式連接的進程則不會被終止,並能夠嘗試從錯誤中恢復。例如,進程可通知用戶所發生的錯誤,並讓用戶指定 DLL 的其餘路徑。
  • 若是使用隱式連接的進程所連接到的 DLL 中有任何 DLL 具備失敗的 DllMain 函數,該進程也會被終止。一樣是在此狀況下,使用顯式連接的進程則不會被終止。 由於 Windows 在應用程序加載時加載全部的 DLL,故隱式連接到許多 DLL 的應用程序啓動起來會比較慢。爲提升啓動性能,應用程序可隱式連接到那些加載後當即須要的 DLL,並等到在須要時顯式連接到其餘 DLL。
  • 顯式連接下不需將應用程序與導入庫連接。若是 DLL 中的更改致使導出序號更改,使用顯式連接的應用程序不需從新連接(假設它們是用函數名而不是序號值調用 GetProcAddress),而使用隱式連接的應用程序必須從新連接到新的導入庫。

下面是須要注意的顯式連接的兩個缺點:

  • 若是 DLL 具備 DllMain 入口點函數,則操做系統在調用 LoadLibrary 的線程上下文中調用此函數。若是因爲之前調用了 LoadLibrary 但沒有相應地調用 FreeLibrary 函數而致使 DLL 已經附加到進程,則不會調用此入口點函數。若是 DLL 使用 DllMain 函數爲進程的每一個線程執行初始化,顯式連接會形成問題,由於調用 LoadLibrary(或 AfxLoadLibrary)時存在的線程將不會初始化。
  • 若是 DLL 將靜態做用域數據聲明爲 __declspec(thread),則在顯式連接時 DLL 會致使保護錯誤。用 LoadLibrary 加載 DLL 後,每當代碼引用此數據時 DLL 就會致使保護錯誤。(靜態做用域數據既包括全局靜態項,也包括局部靜態項。)所以,建立 DLL 時應避免使用線程本地存儲區,或者應(在用戶嘗試動態加載時)告訴 DLL 用戶潛在的缺陷。
相關文章
相關標籤/搜索