摘 要:在《csdn開(kāi)發(fā)高手》2004年第03期中的《化功大法——將DLL嵌入EXE》一文,介紹了如何把一個(gè)動(dòng)態(tài)鏈接庫作為一個(gè)資源嵌入到可執行文件,在可執行文件運行時(shí),自動(dòng)從資源中釋放出來(lái),通過(guò)靜態(tài)加載延遲實(shí)現DLL函數的動(dòng)態(tài)加載,程序退出后實(shí)現臨時(shí)文件的自動(dòng)刪除,從而為解決“DLL Hell”提供了一種解決方案。這是一個(gè)很好的設計思想,而且該作者也用C++實(shí)現了,在Internet上也有相似的VB程序,但在某一技術(shù)論壇上提起這種設計方法時(shí),有網(wǎng)友提出:“這種方法好是好,但就是啟動(dòng)速度太慢”。這是因為程序啟動(dòng)時(shí)實(shí)現DLL釋放,然后再加載釋放出來(lái)的DLL,這個(gè)過(guò)程會(huì )耗費一定的時(shí)間。鑒于此問(wèn)題,經(jīng)過(guò)思索,提出另一個(gè)設計方案:DLL作為資源文件嵌入程序,但不需進(jìn)行DLL釋放及其重新加載。本文就是對該設計方案的原理分析及使用C#編程來(lái)實(shí)現該設計方案。關(guān)鍵詞:動(dòng)態(tài)調用DLL,嵌入DLL,C#正 文:一、 DLL與應用程序動(dòng)態(tài)鏈接庫(也稱(chēng)為DLL,即為“Dynamic Link Library”的縮寫(xiě))是Microsoft Windows最重要的組成要素之一,打開(kāi)Windows系統文件夾,你會(huì )發(fā)現文件夾中有很多DLL文件,Windows就是將一些主要的系統功能以DLL模塊的形式實(shí)現。 動(dòng)態(tài)鏈接庫是不能直接執行的,也不能接收消息,它只是一個(gè)獨立的文件,其中包含能被程序或其它DLL調用來(lái)完成一定操作的函數(方法。注:C#中一般稱(chēng)為“方法”),但這些函數不是執行程序本身的一部分,而是根據進(jìn)程的需要按需載入,此時(shí)才能發(fā)揮作用。DLL只有在應用程序需要時(shí)才被系統加載到進(jìn)程的虛擬空間中,成為調用進(jìn)程的一部分,此時(shí)該DLL也只能被該進(jìn)程的線(xiàn)程訪(fǎng)問(wèn),它的句柄可以被調用進(jìn)程所使用,而調用進(jìn)程的句柄也可以被該DLL所使用。在內存中,一個(gè)DLL只有一個(gè)實(shí)例,且它的編制與具體的編程語(yǔ)言和編譯器都沒(méi)有關(guān)系,所以可以通過(guò)DLL來(lái)實(shí)現混合語(yǔ)言編程。DLL函數中的代碼所創(chuàng )建的任何對象(包括變量)都歸調用它的線(xiàn)程或進(jìn)程所有。下面列出了當程序使用 DLL 時(shí)提供的一些優(yōu)點(diǎn):[1]1) 使用較少的資源當多個(gè)程序使用同一個(gè)函數庫時(shí),DLL 可以減少在磁盤(pán)和物理內存中加載的代碼的重復量。這不僅可以大大影響在前臺運行的程序,而且可以大大影響其他在 Windows 操作系統上運行的程序。 2) 推廣模塊式體系結構DLL 有助于促進(jìn)模塊式程序的開(kāi)發(fā)。這可以幫助您開(kāi)發(fā)要求提供多個(gè)語(yǔ)言版本的大型程序或要求具有模塊式體系結構的程序。模塊式程序的一個(gè)示例是具有多個(gè)可以在運行時(shí)動(dòng)態(tài)加載的模塊的計帳程序。 3) 簡(jiǎn)化部署和安裝當 DLL 中的函數需要更新或修復時(shí),部署和安裝 DLL 不要求重新建立程序與該 DLL 的鏈接。此外,如果多個(gè)程序使用同一個(gè) DLL,那么多個(gè)程序都將從該更新或修復中獲益。當您使用定期更新或修復的第三方 DLL 時(shí),此問(wèn)題可能會(huì )更頻繁地出現。二、 DLL的調用每種編程語(yǔ)言調用DLL的方法都不盡相同,在此只對用C#調用DLL的方法進(jìn)行介紹。首先,您需要了解什么是托管,什么是非托管。一般可以認為:非托管代碼主要是基于win 32平臺開(kāi)發(fā)的DLL,activeX的組件,托管代碼是基于.net平臺開(kāi)發(fā)的。如果您想深入了解托管與非托管的關(guān)系與區別,及它們的運行機制,請您自行查找資料,本文件在此不作討論。(一) 調用DLL中的非托管函數一般方法首先,應該在C#語(yǔ)言源程序中聲明外部方法,其基本形式是:[DLLImport(“DLL文件”)]修飾符 extern 返回變量類(lèi)型 方法名稱(chēng) (參數列表)其中:DLL文件:包含定義外部方法的庫文件。修飾符: 訪(fǎng)問(wèn)修飾符,除了abstract以外在聲明方法時(shí)可以使用的修飾符。返回變量類(lèi)型:在DLL文件中你需調用方法的返回變量類(lèi)型。方法名稱(chēng):在DLL文件中你需調用方法的名稱(chēng)。參數列表:在DLL文件中你需調用方法的列表。注意:需要在程序聲明中使用System.Runtime.InteropServices命名空間。 DllImport只能放置在方法聲明上。DLL文件必須位于程序當前目錄或系統定義的查詢(xún)路徑中(即:系統環(huán)境變量中Path所設置的路徑)。返回變量類(lèi)型、方法名稱(chēng)、參數列表一定要與DLL文件中的定義相一致。若要使用其它函數名,可以使用EntryPoint屬性設置,如:[DllImport("user32.dll", EntryPoint="MessageBoxA")]static extern int MsgBox(int hWnd, string msg, string caption, int type);其它可選的 DllImportAttribute 屬性:CharSet 指示用在入口點(diǎn)中的字符集,如:CharSet=CharSet.Ansi;SetLastError 指示方法是否保留 Win32"上一錯誤",如:SetLastError=true;ExactSpelling 指示 EntryPoint 是否必須與指示的入口點(diǎn)的拼寫(xiě)完全匹配,如:ExactSpelling=false;PreserveSig指示方法的簽名應當被保留還是被轉換, 如:PreserveSig=true;CallingConvention指示入口點(diǎn)的調用約定, 如:CallingConvention=CallingConvention.Winapi;此外,關(guān)于“數據封送處理”及“封送數字和邏輯標量”請參閱其它一些文章[2]。C#例子:1. 啟動(dòng)VS.NET,新建一個(gè)項目,項目名稱(chēng)為“Tzb”,模板為“Windows 應用程序”。2. 在“工具箱”的“ Windows 窗體”項中雙擊“Button”項,向“Form1”窗體中添加一個(gè)按鈕。3. 改變按鈕的屬性:Name為 “B1”,Text為 “用DllImport調用DLL彈出提示框”,并將按鈕B1調整到適當大小,移到適當位置。4. 在類(lèi)視圖中雙擊“Form1”,打開(kāi)“Form1.cs”代碼視圖,在“namespace Tzb”上面輸入“using System.Runtime.InteropServices;”,以導入該命名空間。5. 在“Form1.cs[設計]”視圖中雙擊按鈕B1,在“B1_Click”方法上面使用關(guān)鍵字 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”運行該程序,并點(diǎn)擊按鈕B1,便彈出如下提示框:(二) 動(dòng)態(tài)裝載、調用DLL中的非托管函數在上面已經(jīng)說(shuō)明了如何用DllImport調用DLL中的非托管函數,但是這個(gè)是全局的函數,假若DLL中的非托管函數有一個(gè)靜態(tài)變量S,每次調用這個(gè)函數的時(shí)候,靜態(tài)變量S就自動(dòng)加1。結果,當需要重新計數時(shí),就不能得出想要的結果。下面將用例子說(shuō)明:1. DLL的創(chuàng )建1) 啟動(dòng)Visual C++ 6.0;2) 新建一個(gè)“Win32 Dynamic-Link Library”工程,工程名稱(chēng)為“Count”;3) 在“Dll kind”選擇界面中選擇“A simple dll project”;4) 打開(kāi)Count.cpp,添加如下代碼:// 導出函數,使用“ _stdcall ” 標準調用 extern "C" _declspec(dllexport)int _stdcall count(int init); int _stdcall count(int init) {//count 函數,使用參數 init 初始化靜態(tài)的整形變量 S ,并使 S 自加 1 后返回該值 static int S=init; S++; return S; } 5) 按“F7”進(jìn)行編譯,得到Count.dll(在工程目錄下的Debug文件夾中)。2. 用DllImport調用DLL中的count函數1) 打開(kāi)項目“Tzb”,向“Form1”窗體中添加一個(gè)按鈕。2) 改變按鈕的屬性:Name為 “B2”,Text為 “用DllImport調用DLL中count函數”,并將按鈕B1調整到適當大小,移到適當位置。3) 打開(kāi)“Form1.cs”代碼視圖,使用關(guān)鍵字 static 和 extern 聲明方法“count”,并使其具有來(lái)自 Count.dll 的導出函數count的實(shí)現,代碼如下:[DllImport("Count.dll")] static extern int count(int init); 4) 在“Form1.cs[設計]”視圖中雙擊按鈕B2,在“B2_Click”方法體內添加如下代碼:MessageBox.Show(" 用 DllImport 調用 DLL 中的 count 函數, 傳入的實(shí)參為 0 ,得到的結果是: "+count(0).ToString()," 挑戰杯 "); MessageBox.Show(" 用 DllImport 調用 DLL 中的 count 函數, 傳入的實(shí)參為 10 ,得到的結果是: "+count(10).ToString()+"結果可不是想要的 11 哦?。?! "," 挑戰杯 "); MessageBox.Show(" 所得結果表明: 用 DllImport 調用 DLL 中的非托管 函數是全局的、靜態(tài)的函數?。?! "," 挑戰杯 "); 5) 把Count.dll復制到項目“Tzb”的binDebug文件夾中,按“F5”運行該程序,并點(diǎn)擊按鈕B2,便彈出如下三個(gè)提示框:第1個(gè)提示框顯示的是調用“count(0)”的結果,第2個(gè)提示框顯示的是調用“count(10)”的結果,由所得結果可以證明“用DllImport調用DLL中的非托管函數是全局的、靜態(tài)的函數”。所以,有時(shí)候并不能達到我們目的,因此我們需要使用下面所介紹的方法:C#動(dòng)態(tài)調用DLL中的函數。 3. C#動(dòng)態(tài)調用DLL中的函數因為C#中使用DllImport是不能像動(dòng)態(tài)load/unload assembly那樣,所以只能借助API函數了。在kernel32.dll中,與動(dòng)態(tài)庫調用有關(guān)的函數包括[3]:①LoadLibrary(或MFC 的AfxLoadLibrary),裝載動(dòng)態(tài)庫。 ②GetProcAddress,獲取要引入的函數,將符號名或標識號轉換為DLL內部地址。③FreeLibrary(或MFC的AfxFreeLibrary),釋放動(dòng)態(tài)鏈接庫。它們的原型分別是:HMODULE LoadLibrary(LPCTSTR lpFileName);FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);BOOL FreeLibrary(HMODULE hModule);現在,我們可以用IntPtr hModule=LoadLibrary(“Count.dll”);來(lái)獲得Dll的句柄,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);來(lái)獲得函數的入口地址。但是,知道函數的入口地址后,怎樣調用這個(gè)函數呢?因為在C#中是沒(méi)有函數指針的,沒(méi)有像C++那樣的函數指針調用方式來(lái)調用函數,所以我們得借助其它方法。經(jīng)過(guò)研究,發(fā)現我們可以通過(guò)結合使用System.Reflection.Emit及System.Reflection.Assembly里的類(lèi)和函數達到我們的目的。為了以后使用方便及實(shí)現代碼的復用,我們可以編寫(xiě)一個(gè)類(lèi)。1) dld類(lèi)的編寫(xiě):1. 打開(kāi)項目“Tzb”,打開(kāi)類(lèi)視圖,右擊“Tzb”,選擇“添加”-->“類(lèi)”,類(lèi)名設置為“dld”,即dynamic loading dll 的每個(gè)單詞的開(kāi)頭字母。2. 添加所需的命名空間及聲明參數傳遞方式枚舉:using System.Runtime.InteropServices; // 用 DllImport 需用此 命名空間 using System.Reflection; // 使用 Assembly 類(lèi)需用此 命名空間 using System.Reflection.Emit; // 使用 ILGenerator 需用此 命名空間 在“public class dld”上面添加如下代碼聲明參數傳遞方式枚舉:/// /// 參數傳遞方式枚舉 ,ByValue 表示值傳遞 ,ByRef 表示址傳遞 /// public enum ModePass { ByValue = 0x0001, ByRef = 0x0002 } 3. 聲明LoadLibrary、GetProcAddress、FreeLibrary及私有變量hModule和farProc:/// /// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName); /// /// DLL 文件名 /// 函數庫模塊的句柄 [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); /// /// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName); /// /// 包含需調用函數的函數庫模塊的句柄 /// 調用函數的名稱(chēng) /// 函數指針 [DllImport("kernel32.dll")] static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); /// /// 原型是 : BOOL FreeLibrary(HMODULE hModule); /// /// 需釋放的函數庫模塊的句柄 /// 是否已釋放指定的 Dll [DllImport("kernel32",EntryPoint="FreeLibrary",SetLastError=true)] static extern bool FreeLibrary(IntPtr hModule); /// /// Loadlibrary 返回的函數庫模塊的句柄 /// private IntPtr hModule=IntPtr.Zero; /// /// GetProcAddress 返回的函數指針 /// private IntPtr farProc=IntPtr.Zero; 4. 添加LoadDll方法,并為了調用時(shí)方便,重載了這個(gè)方法:/// /// 裝載 Dll /// /// DLL 文件名 public void LoadDll(string lpFileName) { hModule=LoadLibrary(lpFileName); if(hModule==IntPtr.Zero) throw(new Exception(" 沒(méi)有找到 :"+lpFileName+"." )); } 若已有已裝載Dll的句柄,可以使用LoadDll方法的第二個(gè)版本:public void LoadDll(IntPtr HMODULE) { if(HMODULE==IntPtr.Zero) throw(new Exception(" 所傳入的函數庫模塊的句柄 HMODULE 為空 ." )); hModule=HMODULE; } 5. 添加LoadFun方法,并為了調用時(shí)方便,也重載了這個(gè)方法,方法的具體代碼及注釋如下:/// /// 獲得函數指針 /// /// 調用函數的名稱(chēng) public void LoadFun(string lpProcName) { // 若函數庫模塊的句柄為空,則拋出異常 if(hModule==IntPtr.Zero) throw(new Exception(" 函數庫模塊的句柄為空 , 請確保已進(jìn)行 LoadDll 操作 !")); // 取得函數指針 farProc = GetProcAddress(hModule,lpProcName); // 若函數指針,則拋出異常 if(farProc==IntPtr.Zero) throw(new Exception(" 沒(méi)有找到 :"+lpProcName+" 這個(gè)函數的入口點(diǎn) ")); } /// /// 獲得函數指針 /// /// 包含需調用函數的 DLL 文件名 /// 調用函數的名稱(chēng) public void LoadFun(string lpFileName,string lpProcName) { // 取得函數庫模塊的句柄 hModule=LoadLibrary(lpFileName); // 若函數庫模塊的句柄為空,則拋出異常 if(hModule==IntPtr.Zero) throw(new Exception(" 沒(méi)有找到 :"+lpFileName+"." )); // 取得函數指針 farProc = GetProcAddress(hModule,lpProcName); // 若函數指針,則拋出異常 if(farProc==IntPtr.Zero) throw(new Exception(" 沒(méi)有找到 :"+lpProcName+" 這個(gè)函數的入口點(diǎn) ")); } 6. 添加UnLoadDll及Invoke方法,Invoke方法也進(jìn)行了重載:/// /// 卸載 Dll /// public void UnLoadDll() { FreeLibrary(hModule); hModule=IntPtr.Zero; farProc=IntPtr.Zero; } Invoke方法的第一個(gè)版本:/// /// 調用所設定的函數 /// /// 實(shí)參 /// 實(shí)參類(lèi)型 /// 實(shí)參傳送方式 /// 返回類(lèi)型 /// 返回所調用函數的 object public object Invoke(object[] ObjArray_Parameter,Type[] TypeArray_ParameterType,ModePass[] ModePassArray_Parameter,Type Type_Return) { // 下面 3 個(gè) if 是進(jìn)行安全檢查 , 若不能通過(guò) , 則拋出異常 if(hModule==IntPtr.Zero) throw(new Exception(" 函數庫模塊的句柄為空 , 請確保已進(jìn)行 LoadDll 操作 !")); if(farProc==IntPtr.Zero) throw(new Exception(" 函數指針為空 , 請確保已進(jìn)行 LoadFun 操作 !" ) ); if(ObjArray_Parameter.Length!=ModePassArray_Parameter.Length) throw(new Exception(" 參數個(gè)數及其傳遞方式的個(gè)數不匹配 ." ) ); // 下面是創(chuàng )建 MyAssemblyName 對象并設置其 Name 屬性 AssemblyName MyAssemblyName = new AssemblyName(); MyAssemblyName.Name = "InvokeFun"; // 生成單模塊配件 AssemblyBuilder MyAssemblyBuilder =AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName,AssemblyBuilderAccess.Run); ModuleBuilder MyModuleBuilder =MyAssemblyBuilder.DefineDynamicModule("InvokeDll"); // 定義要調用的方法 , 方法名為“ MyFun ”,返回類(lèi)型是“ Type_Return ”參數類(lèi)型是“ TypeArray_ParameterType ” MethodBuilder MyMethodBuilder =MyModuleBuilder.DefineGlobalMethod("MyFun",MethodAttributes.Public| MethodAttributes.Static,Type_Return,TypeArray_ParameterType); // 獲取一個(gè) ILGenerator ,用于發(fā)送所需的 IL ILGenerator IL = MyMethodBuilder.GetILGenerator(); int i; for (i = 0; i {// 用循環(huán)將參數依次壓入堆棧 switch (ModePassArray_Parameter) { case ModePass.ByValue: IL.Emit(OpCodes.Ldarg, i); break; case ModePass.ByRef: IL.Emit(OpCodes.Ldarga, i); break; default: throw(new Exception(" 第 " +(i+1).ToString() + " 個(gè)參數沒(méi)有給定正確的傳遞方式 ." ) ); } } if (IntPtr.Size == 4) {// 判斷處理器類(lèi)型 IL.Emit(OpCodes.Ldc_I4, farProc.ToInt32()); } else if (IntPtr.Size == 8) { IL.Emit(OpCodes.Ldc_I8, farProc.ToInt64()); } else { throw new PlatformNotSupportedException(); } IL.EmitCalli(OpCodes.Calli,CallingConvention.StdCall,Type_Return,TypeArray_ParameterType); IL.Emit(OpCodes.Ret); // 返回值 MyModuleBuilder.CreateGlobalFunctions(); // 取得方法信息 MethodInfo MyMethodInfo = MyModuleBuilder.GetMethod("MyFun"); return MyMethodInfo.Invoke(null, ObjArray_Parameter);// 調用方法,并返回其值 } Invoke方法的第二個(gè)版本,它是調用了第一個(gè)版本的:/// /// 調用所設定的函數 /// /// 函數指針 /// 實(shí)參 /// 實(shí)參類(lèi)型 /// 實(shí)參傳送方式 /// 返回類(lèi)型 /// 返回所調用函數的 object public object Invoke(IntPtr IntPtr_Function,object[] ObjArray_Parameter,Type[] TypeArray_ParameterType,ModePass[] ModePassArray_Parameter,Type Type_Return) { // 下面 2 個(gè) if 是進(jìn)行安全檢查 , 若不能通過(guò) , 則拋出異常 if(hModule==IntPtr.Zero) throw(new Exception(" 函數庫模塊的句柄為空 , 請確保已進(jìn)行 LoadDll 操作 !")); if(IntPtr_Function==IntPtr.Zero) throw(new Exception(" 函數指針 IntPtr_Function 為空 !" ) ); farProc=IntPtr_Function; return Invoke(ObjArray_Parameter,TypeArray_ParameterType,ModePassArray_Parameter,Type_Return); } 2) dld類(lèi)的使用:1. 打開(kāi)項目“Tzb”,向“Form1”窗體中添加三個(gè)按鈕。Name 和Text屬性分別為 “B3”、“用LoadLibrary方法裝載Count.dll”,“B4”、“調用count方法”,“B5”、“卸載Count.dll”,并調整到適當的大小及位置。2. 在“Form1.cs[設計]”視圖中雙擊按鈕B3,在“B3_Click”方法體上面添加代碼,創(chuàng )建一個(gè)dld類(lèi)實(shí)例:/// /// 創(chuàng )建一個(gè) dld 類(lèi)對象 /// private dld myfun=new dld(); 3. 在“B3_Click”方法體內添加如下代碼:myfun.LoadDll("Count.dll"); // 加載 "Count.dll" myfun.LoadFun("_count@4"); // 調入函數 count, "_count@4" 是它的入口,可通過(guò) Depends 查看 4. “Form1.cs[設計]”視圖中雙擊按鈕B4,在“B4_Click”方法體內添加如下代碼:object[] Parameters = new object[]{(int)0}; // 實(shí)參為 0 Type[] ParameterTypes = new Type[]{typeof(int)}; // 實(shí)參類(lèi)型為 int ModePass[] themode=new ModePass[]{ModePass.ByValue}; // 傳送方式為值傳 Type Type_Return = typeof(int); // 返回類(lèi)型為 int // 彈出提示框,顯示調用 myfun.Invoke 方法的結果,即調用 count 函數 MessageBox.Show(" 這是您裝載該 Dll 后第 "+myfun.Invoke(Parameters,ParameterTypes,themode,Type_Return).ToString() +" 次點(diǎn)擊此按鈕。 "," 挑戰杯 "); 5. “Form1.cs[設計]”視圖中雙擊按鈕B5,在“B5_Click”方法體內添加如下代碼:myfun.UnLoadDll(); 6. 按“F5”運行該程序,并先點(diǎn)擊按鈕B3以加載“Count.dll”,接著(zhù)點(diǎn)擊按鈕B4三次以調用3次“count(0)”,先后彈出的提示框如下: 這三個(gè)提示框所得出的結果說(shuō)明了靜態(tài)變量S 經(jīng)初始化后,再傳入實(shí)參“0”也不會(huì )改變其值為“0”。7. 點(diǎn)擊按鈕B5以卸載“Count.dll”,再點(diǎn)擊按鈕B3進(jìn)行裝載“Count.dll”,再點(diǎn)擊按鈕B4查看調用了“count(0)”的結果:從彈出的提示框所顯示的結果可以看到又開(kāi)始重新計數了,也就是實(shí)現了DLL的動(dòng)態(tài)裝載與卸載了。(三) 調用托管DLL一般方法C# 調用托管DLL是很簡(jiǎn)單的,只要在“解決方案資源管理器”中的需要調用DLL的項目下用鼠標右擊“引用”,并選擇“添加引用”,然后選擇已列出的DLL或通過(guò)瀏覽來(lái)選擇DLL文件,最后需要用using 導入相關(guān)的命名空間。(四) 動(dòng)態(tài)調用托管DLLC# 動(dòng)態(tài)調用托管DLL也需要借助System.Reflection.Assembly里的類(lèi)和方法,主要使用了Assembly.LoadFrom?,F在,用例子說(shuō)明: 首先,啟動(dòng)VS.NET,新建一個(gè)Visual C# 項目,使用的模板為“類(lèi)庫”,名稱(chēng)為“CsCount”,并在類(lèi)“Class1”中添加靜態(tài)整型變量S及方法count:// 由于 static 不能修飾方法體內的變量,所以需放在這里,且初始化值為 int.MinValue static int S=int.MinValue; public int count(int init) {// 判斷 S 是否等于 int.MinValue ,是的話(huà)把 init 賦值給 S if(S==int.MinValue) S=init; S++; //S 自增 1 return S; // 返回 S } 然后,打開(kāi)項目“Tzb”,向“Form1”窗體中添加一個(gè)按鈕,Name屬性為“B6”,Text屬性為“用Assembly類(lèi)來(lái)動(dòng)態(tài)調用托管DLL”,調整到適當大小和位置,雙擊按鈕B6,轉入代碼視圖,先導入命名空間:using System.Reflection; 接著(zhù)添加Invoke方法和B6_Click方法代碼:private object Invoke(string lpFileName,string Namespace,string ClassName,string lpProcName,object[] ObjArray_Parameter) { Try { // 載入程序集 Assembly MyAssembly=Assembly.LoadFrom(lpFileName); Type[] type=MyAssembly.GetTypes(); foreach(Type t in type) {// 查找要調用的命名空間及類(lèi) if(t.Namespace==Namespace&&t.Name==ClassName) {// 查找要調用的方法并進(jìn)行調用 MethodInfo m=t.GetMethod(lpProcName); if(m!=null) { object o=Activator.CreateInstance(t); return m.Invoke(o,ObjArray_Parameter); } else MessageBox.Show(" 裝載出錯 !"); } } }//try catch(System.NullReferenceException e) { MessageBox.Show(e.Message); }//catch return (object)0; }// Invoke “B6_Click”方法體內代碼如下:// 顯示 count(0) 返回的值 MessageBox.Show(" 這是您第 "+Invoke("CsCount.dll","CsCount","Class1","count",new object[]{(int)0}).ToString()+" 次點(diǎn)擊此按鈕。 "," 挑戰杯 "); 最后,把項目“CsCount”的binDebug文件夾中的CsCount.dll復制到項目“Tzb”的binDebug文件夾中,按“F5”運行該程序,并點(diǎn)擊按鈕B6三次,將會(huì )彈出3個(gè)提示框,內容分別是“這是您第 1次點(diǎn)擊此按鈕。”、“這是您第 2次點(diǎn)擊此按鈕。”、“這是您第 3次點(diǎn)擊此按鈕。”,由此知道了靜態(tài)變量S在這里的作用。(五) C#程序嵌入DLL的調用 DLL文件作為資源嵌入在C#程序中,我們只要讀取該資源文件并以“byte[]”返回,然后就用“Assembly Load(byte[]);”得到DLL中的程序集,最后就可以像上面的Invoke方法那樣對DLL中的方法進(jìn)行調用。當然不用上面方法也可以,如用接口實(shí)現動(dòng)態(tài)調用,但DLL中必須有該接口的定義并且程序中也要有該接口的定義;也可用反射發(fā)送實(shí)現動(dòng)態(tài)調用[4]?,F在我只對像上面的Invoke方法那樣對DLL中的方法進(jìn)行調用進(jìn)行討論,為了以后使用方便及實(shí)現代碼的復用,我們可以結合上一個(gè)編寫(xiě)一個(gè)類(lèi)。1) ldfs類(lèi)的編寫(xiě):在項目“Tzb”中新建一個(gè)名為ldfs的類(lèi),意為“load dll from resource”,請注意,在這個(gè)類(lèi)中“resource”不只是嵌入在EXE程序中的資源,它也可以是硬盤(pán)上任意一個(gè)DLL文件,這是因為ldfs的類(lèi)中的方法LoadDll有些特別,就是先從程序的內嵌的資源中查找需加載的DLL,如果找不到,就查找硬盤(pán)上的。首先導入所需的命名空間:using System.IO; // 對文件的讀寫(xiě)需要用到此命名空間 using System.Reflection; // 使用 Assembly 類(lèi)需用此命名空間 using System.Reflection.Emit; // 使用 ILGenerator 需用此命名空間 聲明一靜態(tài)變量MyAssembly:// 記錄要導入的程序集 static Assembly MyAssembly; 添加LoadDll方法:private byte[] LoadDll(string lpFileName) { Assembly NowAssembly = Assembly.GetEntryAssembly(); Stream fs=null; try {// 嘗試讀取資源中的 DLL fs = NowAssembly.GetManifestResourceStream(NowAssembly.GetName().Name+"."+lpFileName); } finally {// 如果資源沒(méi)有所需的 DLL ,就查看硬盤(pán)上有沒(méi)有,有的話(huà)就讀取 if (fs==null&&!File.Exists(lpFileName)) throw(new Exception(" 找不到文件 :"+lpFileName)); else if(fs==null&&File.Exists(lpFileName)) { FileStream Fs = new FileStream(lpFileName, FileMode.Open); fs=(Stream)Fs; } } byte[] buffer = new byte[(int) fs.Length]; fs.Read(buffer, 0, buffer.Length); fs.Close(); return buffer; // 以 byte[] 返回讀到的 DLL } 添加UnLoadDll方法來(lái)卸載DLL:public void UnLoadDll() {// 使 MyAssembly 指空 MyAssembly=null; } 添加Invoke方法來(lái)進(jìn)行對DLL中方法的調用,其原理大體上和“Form1.cs”中的方法Invoke相同,不過(guò)這里用的是“Assembly.Load”,而且用了靜態(tài)變量MyAssembly來(lái)保存已加載的DLL,如果已加載的話(huà)就不再加載,如果還沒(méi)加載或者已加載的不同現在要加載的DLL就進(jìn)行加載,其代碼如下所示:public object Invoke(string lpFileName,string Namespace,string ClassName,string lpProcName,object[] ObjArray_Parameter) { try {// 判斷 MyAssembly 是否為空或 MyAssembly 的命名空間不等于要調用方法的命名空間,如果條件為真,就用 Assembly.Load 加載所需 DLL 作為程序集 if(MyAssembly==null||MyAssembly.GetName().Name!=Namespace) MyAssembly=Assembly.Load(LoadDll(lpFileName)); Type[] type=MyAssembly.GetTypes(); foreach(Type t in type) { if(t.Namespace==Namespace&&t.Name==ClassName) { MethodInfo m=t.GetMethod(lpProcName); if(m!=null) {// 調用并返回 object o=Activator.CreateInstance(t); return m.Invoke(o,ObjArray_Parameter); } else System.Windows.Forms.MessageBox.Show(" 裝載出錯 !"); } } } catch(System.NullReferenceException e) { System.Windows.Forms.MessageBox.Show(e.Message); } return (object)0; } 2) ldfs類(lèi)的使用:1. 把CsCount.dll作為“嵌入的資源”添加到項目“Tzb”中。2. 向“Form1”窗體中添加兩個(gè)按鈕,Name和Text屬性分別為“B7”、“ldfs.Invoke調用count”;“B8”、“UnLoadDll”,并將它們調整到適當大小和位置。3. 打開(kāi)“Form1.cs”代碼視圖,添加一個(gè)ldfs實(shí)例:// 添加一個(gè) ldfs 實(shí)例 tmp private ldfs tmp=new ldfs(); 4. 在“Form1.cs[設計]”視圖中雙擊按鈕B7,在“B1_Click”方法體內添加如下代碼:// 調用 count(0), 并使用期提示框顯示其返回值 MessageBox.Show(" 這是您第 "+tmp.Invoke("CsCount.dll","CsCount","Class1","count",new object[]{(int)0}).ToString()+" 次點(diǎn)擊此按鈕。 "," 挑戰杯 "); 5. 在“Form1.cs[設計]”視圖中雙擊按鈕B7,在“B1_Click”方法體內添加如下代碼:// 卸載 DLL tmp.UnLoadDll(); 6. “F5”運行該程序,并先點(diǎn)擊按鈕B7三次,接著(zhù)點(diǎn)擊按鈕B8,最后再點(diǎn)擊按鈕B7,此時(shí)發(fā)現又開(kāi)始重新計數了,情況和“dld類(lèi)的使用”類(lèi)似,也就是也實(shí)現了DLL的動(dòng)態(tài)裝載與卸載了。 說(shuō)明:以上所用到的所有源代碼詳見(jiàn)附件1:Form1.cs、附件2:dld.cs、附件3:ldfs.cs、附件4:Count.cpp、附件5:Class1.cs。三、 結 論使用DLL有很多優(yōu)點(diǎn),如:節省內存和減少交換操作;開(kāi)發(fā)大型程序時(shí)可以把某些模塊分配給程序員,程序員可以用任何一門(mén)他所熟悉的語(yǔ)言把該模塊編譯成DLL文件,這樣可以提高代碼的復用,大大減輕程序員的工作量。當然DLL也有一些不足,如在提要中提及的問(wèn)題。所以,如何靈活地調用DLL應該是每位程序員所熟知的。C# 語(yǔ)言有很多優(yōu)點(diǎn),越來(lái)越多的人開(kāi)始使用它來(lái)編程。但是,C#還有一些不足,如對不少的底層操作是無(wú)能為力的,只能通過(guò)調用Win32 DLL 或C++等編寫(xiě)的DLL;另外,一般認為C#程序的保密性不夠強,因為它容易被Reflector 反編譯而得到部分源碼,所以需要使用混合編程加強C#程序的保密性,而把DLL嵌入C#程序并實(shí)現動(dòng)態(tài)調用的方法是比較理想的方法,因為可以把DLL文件先用某一算法進(jìn)行加密甚至壓縮后再作為資源文件添加到C#程序中,在程序運行時(shí)才用某一算法進(jìn)行解壓解密后才進(jìn)行加載,所以即使用反編譯軟件,也只能得到一個(gè)資源文件,且這個(gè)資源文件是用一個(gè)復雜算法進(jìn)行加密過(guò)的,不可能再次對資源文件中的內容進(jìn)行反編譯,從而大大加強了代碼的保密性。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。