摘 要: 在《 csdn 開發高手》 2004 年第 03 期中的《化功大法——將 DLL 嵌入 EXE 》一文,介紹了如何把一個動態鏈接庫作為一個資源嵌入到可執行文件,在可執行文件運行時,自動從資源中釋放出來,通過靜態加載延遲實現DLL函數的動態加載,程序退出後實現臨時文件的自動刪除,從而為解決“ DLL Hell ”提供了一種解決方案。這是一個很好的設計思想,而且該作者也用 C++ 實現了,在 Internet 上也有相似的 VB 程序,但在某一技術論壇上提起這種設計方法時,有網友提出:“這種方法好是好,但就是啟動速度太慢”。這是因為程序啟動時實現DLL 釋放,然後再加載釋放出來的 DLL ,這個過程會耗費一定的時間。鑒於此問題,經過思索,提出另一個設計方案: DLL 作為資源文件嵌入程序,但不需進行 DLL 釋放及其重新加載。本文就是對該設計方案的原理分析及使用 C# 編程來實現該設計方案。
關鍵詞: 動態調用 DLL ,嵌入 DLL , C#
正 文:
一、 DLL 與應用程序
動態鏈接庫(也稱為 DLL ,即為“ Dynamic Link Library ”的縮寫)是 Microsoft Windows 最重要的組成要素之一,打開 Windows 系統文件夾,你會發現文件夾中有很多 DLL 文件, Windows 就是將一些主要的系統功能以 DLL 模塊的形式實現。
動態鏈接庫是不能直接執行的,也不能接收消息,它只是一個獨立的文件,其中包含能被程序或其它 DLL 調用來完成一定操作的函數 ( 方法。注: C# 中一般稱為“方法” ) ,但這些函數不是執行程序本身的一部分,而是根據進程的需要按需載入,此時才能發揮作用。
DLL 只有在應用程序需要時才被系統加載到進程的虛擬空間中,成為調用進程的一部分,此時該 DLL 也只能被該進程的線程訪問,它的句柄可以被調用進程所使用,而調用進程的句柄也可以被該 DLL 所使用。在內存中,一個 DLL 只有一個實例,且它的編制與具體的編程語言和編譯器都沒有關系,所以可以通過 DLL 來實現混合語言編程。 DLL 函數中的代碼所創建的任何對象(包括變量)都歸調用它的線程或進程所有。
下面列出了當程序使用 DLL 時提供的一些優點: [1]
1) 使用較少的資源
當多個程序使用同一個函數庫時, DLL 可以減少在磁盤和物理內存中加載的代碼的重復量。這不僅可以大大影響在前台運行的程序,而且可以大大影響其他在 Windows 操作系統上運行的程序。
2) 推廣模塊式體系結構
DLL 有助於促進模塊式程序的開發。這可以幫助您開發要求提供多個語言版本的大型程序或要求具有模塊式體系結構的程序。模塊式程序的一個示例是具有多個可以在運行時動態加載的模塊的計帳程序。
3) 簡化部署和安裝
當 DLL 中的函數需要更新或修復時,部署和安裝 DLL 不要求重新建立程序與該 DLL 的鏈接。此外,如果多個程序使用同一個 DLL ,那麼多個程序都將從該更新或修復中獲益。當您使用定期更新或修復的第三方 DLL 時,此問題可能會更頻繁地出現。
二、 DLL 的調用
每種編程語言調用 DLL 的方法都不盡相同,在此只對用 C# 調用 DLL 的方法進行介紹。首先 , 您需要了解什麼是托管 , 什麼是非托管。一般可以認為:非托管代碼主要是基於 win 32 平台開發的 DLL , activeX 的組件,托管代碼是基於 .net 平台開發的。如果您想深入了解托管與非托管的關系與區別,及它們的運行機制,請您自行查找資料,本文件在此不作討論。
(一) 調用 DLL 中的非托管函數一般方法
首先 ,應該在 C# 語言源程序中聲明外部方法,其基本形式是:
[DLLImport(“DLL 文件 ”)]
修飾符 extern 返回變量類型 方法名稱 (參數列表)
其中 :
DLL 文件:包含定義外部方法的庫文件。
修飾符: 訪問修飾符,除了 abstract 以外在聲明方法時可以使用的修飾符。
返回變量類型:在 DLL 文件中你需調用方法的返回變量類型。
方法名稱:在 DLL 文件中你需調用方法的名稱。
參數列表:在 DLL 文件中你需調用方法的列表。
注意 :需要在程序聲明中使用 System.Runtime.InteropServices 命名空間。
DllImport 只能放置在方法聲明上。
DLL 文件必須位於程序當前目錄或系統定義的查詢路徑中(即:系統環境變量中 Path 所設置的路徑)。
返回變量類型、方法名稱、參數列表一定要與 DLL 文件中的定義相一致。
若要使用其它函數名,可以使用 EntryPoint 屬性設置,如:
[DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);
其它可選的 DllImportAttribute 屬性:
CharSet 指示用在入口點中的字符集,如: CharSet=CharSet.Ansi ;
SetLastError 指示方法是否保留 Win32" 上一錯誤 " ,如: SetLastError=true ;
ExactSpelling 指示 EntryPoint 是否必須與指示的入口點的拼寫完全匹配,如: ExactSpelling=false ;
PreserveSig 指示方法的簽名應當被保留還是被轉換, 如: PreserveSig=true ;
CallingConvention 指示入口點的調用約定, 如: CallingConvention=CallingConvention.Winapi ;
此外,關於“數據封送處理”及“封送數字和邏輯標量”請參閱其它一些文章 [2] 。
C# 例子:
1. 啟動 VS.NET ,新建一個項目,項目名稱為“ Tzb ”,模板為“ Windows 應用程序”。
2. 在“工具箱”的“ Windows 窗體”項中雙擊“ Button ”項,向“ Form1 ”窗體中添加一個按鈕。
3. 改變按鈕的屬性: Name 為 “B1” , Text 為 “ 用 DllImport 調用 DLL 彈出提示框 ” ,並將按鈕 B1 調整到適當大小,移到適當位置。
4. 在類視圖中雙擊“ Form1 ”,打開“ Form1 . cs ”代碼視圖,在“ namespace Tzb ”上面輸入“ using System.Runtime.InteropServices; ”,以導入該命名空間。
5. 在“ Form1 . cs [設計]”視圖中雙擊按鈕 B1 ,在“ B1_Click ”方法上面使用關鍵字 static 和 extern 聲明方法“ MsgBox ”,將 DllImport 屬性附加到該方法,這裡我們要使用的是“ user32 . dll ”中的“ MessageBoxA ”函數,具體代碼如下:
[DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);
然後在“ B1_Click ”方法體內添加如下代碼,以調用方法“ MsgBox ”:
MsgBox(0," 這就是用 DllImport 調用 DLL 彈出的提示框哦! "," 挑戰杯 ",0x30);
6. 按“ F5”運行該程序,並點擊按鈕B1 ,便彈出如下提示框:
(二) 動態裝載、調用 DLL 中的非托管函數
在上面已經說明了如何用 DllImport 調用 DLL 中的非托管函數,但是這個是全局的函數,假若 DLL 中的非托管函數有一個靜態變量 S ,每次調用這個函數的時候,靜態變量 S 就自動加1 。結果,當需要重新計數時,就不能得出想要的結果。下面將用例子說明:
1. DLL 的創建
1) 啟動 Visual C++ 6.0 ;
2) 新建一 個“ Win32 Dynamic-Link Library ”工程,工程名稱為“ Count ”;
3) 在“ Dll kind ”選擇界面中選擇“ A simple dll project ”;
4) 打開 Count.cpp ,添加如下代碼:
// 導出函數,使用“ _stdcall ” 標准調用
extern "C" _declspec(dllexport)int _stdcall count(int init);
int _stdcall count(int init)
{//count 函數,使用參數 init 初始化靜態的整形變量 S ,並使 S 自加 1 後返回該值
static int S=init;
S++;
return S;
}
5) 按“ F7”進行編譯,得到Count.dll (在工程目錄下的 Debug 文件夾中)。
2. 用DllImport調用DLL中的count函數
1) 打開項目“ Tzb ”,向“ Form1”窗體中添加一個按鈕。
2) 改變按鈕的屬性: Name 為 “ B2 ”, Text 為 “用 DllImport 調用 DLL 中 count 函數”,並將按鈕 B1 調整到適當大小,移到適當位置。
3) 打開“ Form1 . cs ”代碼視圖,使用關鍵字 static 和 extern 聲明方法“ count ”,並使其具有來自 Count.dll 的導出函數 count 的實現,代碼如下:
[DllImport("Count.dll")]
static extern int count(int init);
4) 在“ Form1 . cs [設計]”視圖中雙擊按鈕 B2 ,在“ B2_Click ”方法體內添加如下代碼: