動態(tài)鏈接庫(也稱為DLL,即為“Dynamic Link Library”的縮寫)是Microsoft Windows最重要的組成要素之一,打開Windows系統(tǒng)文件夾,你會發(fā)現(xiàn)文件夾中有很多DLL文件,Windows就是將一些主要的系統(tǒng)功能以DLL模塊的形式實現(xiàn)。
動態(tài)鏈接庫是不能直接執(zhí)行的,也不能接收消息,它只是一個獨立的文件,其中包含能被程序或其它DLL調(diào)用來完成一定操作的函數(shù)(方法。注:C#中一般稱為“方法”),但這些函數(shù)不是執(zhí)行程序本身的一部分,而是根據(jù)進程的需要按需載入,此時才能發(fā)揮作用。
DLL只有在應用程序需要時才被系統(tǒng)加載到進程的虛擬空間中,成為調(diào)用進程的一部分,此時該DLL也只能被該進程的線程訪問,它的句柄可以被調(diào)用進程所使用,而調(diào)用進程的句柄也可以被該DLL所使用。在內(nèi)存中,一個DLL只有一個實例,且它的編制與具體的編程語言和編譯器都沒有關系,所以可以通過DLL來實現(xiàn)混合語言編程。DLL函數(shù)中的代碼所創(chuàng)建的任何對象(包括變量)都歸調(diào)用它的線程或進程所有。
下面列出了當程序使用 DLL 時提供的一些優(yōu)點:
1)使用較少的資源
當多個程序使用同一個函數(shù)庫時,DLL 可以減少在磁盤和物理內(nèi)存中加載的代碼的重復量。這不僅可以大大影響在前臺運行的程序,而且可以大大影響其他在 Windows 操作系統(tǒng)上運行的程序。
2)推廣模塊式體系結(jié)構(gòu)
DLL 有助于促進模塊式程序的開發(fā)。這可以幫助您開發(fā)要求提供多個語言版本的大型程序或要求具有模塊式體系結(jié)構(gòu)的程序。模塊式程序的一個示例是具有多個可以在運行時動態(tài)加載的模塊的計帳程序。
3)簡化部署和安裝
當 DLL 中的函數(shù)需要更新或修復時,部署和安裝 DLL 不要求重新建立程序與該 DLL 的鏈接。此外,如果多個程序使用同一個 DLL,那么多個程序都將從該更新或修復中獲益。當您使用定期更新或修復的第三方 DLL 時,此問題可能會更頻繁地出現(xiàn)。
每種編程語言調(diào)用DLL的方法都不盡相同,在此只對用C#調(diào)用DLL的方法進行介紹。首先,您需要了解什么是托管,什么是非托管。一般可以認為:非托管代碼主要是基于win 32平臺開發(fā)的DLL,activeX的組件,托管代碼是基于.net平臺開發(fā)的。如果您想深入了解托管與非托管的關系與區(qū)別,及它們的運行機制,請您自行查找資料,本文件在此不作討論。
(一) 、調(diào)用DLL中的非托管函數(shù)一般方法
首先,應該在C#語言源程序中聲明外部方法,其基本形式是:
[DLLImport(“DLL文件”)]
修飾符 extern 返回變量類型 方法名稱 (參數(shù)列表)
其中:
DLL文件:包含定義外部方法的庫文件。
修飾符: 訪問修飾符,除了abstract以外在聲明方法時可以使用的修飾符。
返回變量類型:在DLL文件中你需調(diào)用方法的返回變量類型。
方法名稱:在DLL文件中你需調(diào)用方法的名稱。
參數(shù)列表:在DLL文件中你需調(diào)用方法的列表。
注意:需要在程序聲明中使用System.Runtime.InteropServices命名空間。
DllImport只能放置在方法聲明上。
DLL文件必須位于程序當前目錄或系統(tǒng)定義的查詢路徑中(即:系統(tǒng)環(huán)境變量中Path所設置的路徑)。
返回變量類型、方法名稱、參數(shù)列表一定要與DLL文件中的定義相一致。
若要使用其它函數(shù)名,可以使用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指示方法的簽名應當被保留還是被轉(zhuǎn)換, 如:PreserveSig=true;
CallingConvention指示入口點的調(diào)用約定, 如:CallingConvention=CallingConvention.Winapi;
此外,關于“數(shù)據(jù)封送處理”及“封送數(shù)字和邏輯標量”請參閱其它一些文章[2]。
C#例子:
1. 啟動VS.NET,新建一個項目,項目名稱為“Tzb”,模板為“Windows 應用程序”。
2. 在“工具箱”的“ Windows 窗體”項中雙擊“Button”項,向“Form1”窗體中添加一個按鈕。
3. 改變按鈕的屬性:Name為 “B1”,Text為 “用DllImport調(diào)用DLL彈出提示框”,并將按鈕B1調(diào)整到適當大小,移到適當位置。
4. 在類視圖中雙擊“Form1”,打開“Form1.cs”代碼視圖,在“namespace Tzb”上面輸入“using System.Runtime.InteropServices;”,以導入該命名空間。
5. 在“Form1.cs[設計]”視圖中雙擊按鈕B1,在“B1_Click”方法上面使用關鍵字 static 和 extern 聲明方法“MsgBox”,將 DllImport 屬性附加到該方法,這里我們要使用的是“user32.dll”中的“MessageBoxA”函數(shù),具體代碼如下:
1
2
|
[DllImport( "user32.dll" , EntryPoint= "MessageBoxA" )] static extern int MsgBox( int hWnd, string msg, string caption, int type); |
然后在“B1_Click”方法體內(nèi)添加如下代碼,以調(diào)用方法“MsgBox”:
MsgBox(0," 這就是用 DllImport 調(diào)用 DLL 彈出的提示框哦! "," 挑戰(zhàn)杯 ",0x30);
6. 按“F5”運行該程序,并點擊按鈕B1,便彈出如下提示框:
7.代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; namespace WindowsFormsApplication1 { public partial class Form1 : Form { [DllImport( "user32.dll" , EntryPoint = "MessageBoxA" )] static extern int MsgBox2( int hWnd, string msg, string caption, int type); public Form1() { InitializeComponent(); } private void Form1_Load( object sender, EventArgs e) { } private void button1_Click( object sender, EventArgs e) { MsgBox2(0, " 這就是用 DllImport 調(diào)用 DLL 彈出的提示框哦! " , " 挑戰(zhàn)杯 " , 0x30); } private void button2_Click( object sender, EventArgs e) { MsgBox2(0, " 這就是用 DllImport 調(diào)用 DLL 彈出的提示框哦222222f! " , " 222222 " , 0x30); } } } |
(二) 動態(tài)裝載、調(diào)用DLL中的非托管函數(shù)
在上面已經(jīng)說明了如何用DllImport調(diào)用DLL中的非托管函數(shù),但是這個是全局的函數(shù),假若DLL中的非托管函數(shù)有一個靜態(tài)變量S,每次調(diào)用這個函數(shù)的時候,靜態(tài)變量S就自動加1。結(jié)果,當需要重新計數(shù)時,就不能得出想要的結(jié)果。下面將用例子說明:
1. DLL的創(chuàng)建
1) 、啟動Visual C++ 6.0;
2) 、新建一個“Win32 Dynamic-Link Library”工程,工程名稱為“Count”;
3) 、在“Dll kind”選擇界面中選擇“A simple dll project”;
4) 、打開Count.cpp,添加如下代碼:
1
2
3
4
5
6
7
8
|
// 導出函數(shù),使用“ _stdcall ” 標準調(diào)用 extern "C" _declspec(dllexport) int _stdcall count( int init); int _stdcall count( int init) { //count 函數(shù),使用參數(shù) init 初始化靜態(tài)的整形變量 S ,并使 S 自加 1 后返回該值 static int S=init; S++; return S; } |
5) 、按“F7”進行編譯,得到Count.dll(在工程目錄下的Debug文件夾中)。
2. 用DllImport調(diào)用DLL中的count函數(shù)
1) 、打開項目“Tzb”,向“Form1”窗體中添加一個按鈕。
2) 、改變按鈕的屬性:Name為 “B2”,Text為 “用DllImport調(diào)用DLL中count函數(shù)”,并將按鈕B1調(diào)整到適當大小,移到適當位置。
3) 、打開“Form1.cs”代碼視圖,使用關鍵字 static 和 extern 聲明方法“count”,并使其具有來自 Count.dll 的導出函數(shù)count的實現(xiàn),代碼如下:
1
2
|
[DllImport( "Count.dll" )] static extern int count( int init); |
4) 、 在“Form1.cs[設計]”視圖中雙擊按鈕B2,在“B2_Click”方法體內(nèi)添加如下代碼:
MessageBox.Show(" 用 DllImport 調(diào)用 DLL 中的 count 函數(shù), n 傳入的實參為 0 ,得到的結(jié)果是: "+count(0).ToString()," 挑戰(zhàn)杯 ");
MessageBox.Show(" 用 DllImport 調(diào)用 DLL 中的 count 函數(shù), n 傳入的實參為 10 ,得到的結(jié)果是: "+count(10).ToString()+"n 結(jié)果可不是想要的 11 哦!!! "," 挑戰(zhàn)杯 ");
MessageBox.Show(" 所得結(jié)果表明: n 用 DllImport 調(diào)用 DLL 中的非托管 n 函數(shù)是全局的、靜態(tài)的函數(shù)!!! "," 挑戰(zhàn)杯 ");
5) 、把Count.dll復制到項目“Tzb”的binDebug文件夾中,按“F5”運行該程序,并點擊按鈕B2,便彈出如下三個提示框:
第1個提示框顯示的是調(diào)用“count(0)”的結(jié)果,第2個提示框顯示的是調(diào)用“count(10)”的結(jié)果,由所得結(jié)果可以證明“用DllImport調(diào)用DLL中的非托管函數(shù)是全局的、靜態(tài)的函數(shù)”。所以,有時候并不能達到我們目的,因此我們需要使用下面所介紹的方法:C#動態(tài)調(diào)用DLL中的函數(shù)。
3. C#動態(tài)調(diào)用DLL中的函數(shù)
因為C#中使用DllImport是不能像動態(tài)load/unload assembly那樣,所以只能借助API函數(shù)了。在kernel32.dll中,與動態(tài)庫調(diào)用有關的函數(shù)包括:
①LoadLibrary(或MFC 的AfxLoadLibrary),裝載動態(tài)庫。
②GetProcAddress,獲取要引入的函數(shù),將符號名或標識號轉(zhuǎn)換為DLL內(nèi)部地址。
③FreeLibrary(或MFC的AfxFreeLibrary),釋放動態(tài)鏈接庫。
它們的原型分別是:
HMODULE LoadLibrary(LPCTSTR lpFileName);
FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
BOOL FreeLibrary(HMODULE hModule);
現(xiàn)在,我們可以用IntPtr hModule=LoadLibrary(“Count.dll”);來獲得Dll的句柄,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);來獲得函數(shù)的入口地址。
但是,知道函數(shù)的入口地址后,怎樣調(diào)用這個函數(shù)呢?因為在C#中是沒有函數(shù)指針的,沒有像C++那樣的函數(shù)指針調(diào)用方式來調(diào)用函數(shù),所以我們得借助其它方法。經(jīng)過研究,發(fā)現(xiàn)我們可以通過結(jié)合使用System.Reflection.Emit及System.Reflection.Assembly里的類和函數(shù)達到我們的目的。為了以后使用方便及實現(xiàn)代碼的復用,我們可以編寫一個類。
1)、 dld類的編寫:
1. 打開項目“Tzb”,打開類視圖,右擊“Tzb”,選擇“添加”-->“類”,類名設置為“dld”,即dynamic loading dll 的每個單詞的開頭字母。
2. 添加所需的命名空間及聲明參數(shù)傳遞方式枚舉:
1
2
3
|
using System.Runtime.InteropServices; // 用 DllImport 需用此 命名空間 using System.Reflection; // 使用 Assembly 類需用此 命名空間 using System.Reflection.Emit; // 使用 ILGenerator 需用此 命名空間 |
在“public class dld”上面添加如下代碼聲明參數(shù)傳遞方式枚舉:
1
2
3
4
5
6
7
8
|
/// <summary> /// 參數(shù)傳遞方式枚舉 ,ByValue 表示值傳遞 ,ByRef 表示址傳遞 /// </summary> public enum ModePass { ByValue = 0x0001, ByRef = 0x0002 } |
3.聲明LoadLibrary、GetProcAddress、FreeLibrary及私有變量hModule和farProc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/// <summary> /// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName); /// </summary> /// <param name="lpFileName">DLL 文件名 </param> /// <returns> 函數(shù)庫模塊的句柄 </returns> [DllImport( "kernel32.dll" )] static extern IntPtr LoadLibrary( string lpFileName); /// <summary> /// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName); /// </summary> /// <param name="hModule"> 包含需調(diào)用函數(shù)的函數(shù)庫模塊的句柄 </param> /// <param name="lpProcName"> 調(diào)用函數(shù)的名稱 </param> /// <returns> 函數(shù)指針 </returns> [DllImport( "kernel32.dll" )] static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); /// <summary> /// 原型是 : BOOL FreeLibrary(HMODULE hModule); /// </summary> /// <param name="hModule"> 需釋放的函數(shù)庫模塊的句柄 </param> /// <returns> 是否已釋放指定的 Dll</returns> [DllImport( "kernel32" ,EntryPoint= "FreeLibrary" ,SetLastError= true )] static extern bool FreeLibrary(IntPtr hModule); /// <summary> /// Loadlibrary 返回的函數(shù)庫模塊的句柄 /// </summary> private IntPtr hModule=IntPtr.Zero; /// <summary> /// GetProcAddress 返回的函數(shù)指針 /// </summary> private IntPtr farProc=IntPtr.Zero; |
4.添加LoadDll方法,并為了調(diào)用時方便,重載了這個方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/// <summary> /// 裝載 Dll /// </summary> /// <param name="lpFileName">DLL 文件名 </param> public void LoadDll( string lpFileName) { hModule=LoadLibrary(lpFileName); if (hModule==IntPtr.Zero) throw ( new Exception( " 沒有找到 :" +lpFileName+ "." )); } 若已有已裝載Dll的句柄,可以使用LoadDll方法的第二個版本: public void LoadDll(IntPtr HMODULE) { if (HMODULE==IntPtr.Zero) throw ( new Exception( " 所傳入的函數(shù)庫模塊的句柄 HMODULE 為空 ." )); hModule=HMODULE; } |
5. 添加LoadFun方法,并為了調(diào)用時方便,也重載了這個方法,方法的具體代碼及注釋如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/// <summary> /// 獲得函數(shù)指針 /// </summary> /// <param name="lpProcName"> 調(diào)用函數(shù)的名稱 </param> public void LoadFun( string lpProcName) { // 若函數(shù)庫模塊的句柄為空,則拋出異常 if (hModule==IntPtr.Zero) throw ( new Exception( " 函數(shù)庫模塊的句柄為空 , 請確保已進行 LoadDll 操作 !" )); // 取得函數(shù)指針 farProc = GetProcAddress(hModule,lpProcName); // 若函數(shù)指針,則拋出異常 if (farProc==IntPtr.Zero) throw ( new Exception( " 沒有找到 :" +lpProcName+ " 這個函數(shù)的入口點 " )); } /// <summary> /// 獲得函數(shù)指針 /// </summary> /// <param name="lpFileName"> 包含需調(diào)用函數(shù)的 DLL 文件名 </param> /// <param name="lpProcName"> 調(diào)用函數(shù)的名稱 </param> public void LoadFun( string lpFileName, string lpProcName) { // 取得函數(shù)庫模塊的句柄 hModule=LoadLibrary(lpFileName); // 若函數(shù)庫模塊的句柄為空,則拋出異常 if (hModule==IntPtr.Zero) throw ( new Exception( " 沒有找到 :" +lpFileName+ "." )); // 取得函數(shù)指針 farProc = GetProcAddress(hModule,lpProcName); // 若函數(shù)指針,則拋出異常 if (farProc==IntPtr.Zero) throw ( new Exception( " 沒有找到 :" +lpProcName+ " 這個函數(shù)的入口點 " )); } |
6. 添加UnLoadDll及Invoke方法,Invoke方法也進行了重載:
1
2
3
4
5
6
7
8
9
|
/// <summary> /// 卸載 Dll /// </summary> public void UnLoadDll() { FreeLibrary(hModule); hModule=IntPtr.Zero; farProc=IntPtr.Zero; } |
以上就是ASP.NET調(diào)用動態(tài)鏈接庫DLL的方法,希望對大家的學習有所幫助。