C#顺序完成动态挪用DLL的研讨
一、 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”要领体内增加以下代码:
MessageBox.Show(" 用 DllImport 挪用 DLL 中的 count 函数, /n 传入的实参为 0 ,获得的效果是: "+count(0).ToString()," 挑战杯 "); MessageBox.Show(" 用 DllImport 挪用 DLL 中的 count 函数, /n 传入的实参为 10 ,获得的效果是: "+count(10).ToString()+"/n 效果可不是想要的 11 哦!!! "," 挑战杯 "); MessageBox.Show(" 所得效果表明: /n 用 DllImport 挪用 DLL 中的非托管 /n 函数是全局的、静态的函数!!! "," 挑战杯 "); |
5) 把Count.dll复制到项目“Tzb”的bin/Debug文件夹中,按“F5”运转该顺序,并点击按钮B2,便弹出以下三个提示框:
第1个提示框显现的是挪用“count(0)”的效果,第2个提示框显现的是挪用“count(10)”的效果,由所得效果能够证实“用DllImport挪用DLL中的非托管函数是全局的、静态的函数”。所以,有时刻并不能到达我们目标,因而我们须要运用下面所引见的要领:C#动态挪用DLL中的函数。
3. C#动态挪用DLL中的函数
因为C#中运用DllImport是不能像动态load/unload assembly那样,所以只能借助API函数了。在kernel32.dll中,与动态库挪用有关的函数包含[3]:
①LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。
②GetProcAddress,猎取要引入的函数,将标记名或标识号转换为DLL内部地点。
③FreeLibrary(或MFC的AfxFreeLibrary),开释动态链接库。
它们的原型分别是:
HMODULE LoadLibrary(LPCTSTR lpFileName); FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName); BOOL FreeLibrary(HMODULE hModule);
如今,我们能够用IntPtr hModule=LoadLibrary(“Count.dll”);来获得Dll的句柄,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);来获得函数的进口地点。
然则,晓得函数的进口地点后,怎样挪用这个函数呢?因为在C#中是没有函数指针的,没有像C++那样的函数指针挪用体式格局来挪用函数,所以我们得借助别的要领。经由研讨,发明我们能够经由过程连系运用System.Reflection.Emit及System.Reflection.Assembly里的类和函数到达我们的目标。为了今后运用轻易及完成代码的复用,我们能够编写一个类。
1) dld类的编写:
1. 翻开项目“Tzb”,翻开类视图,右击“Tzb”,挑选“增加”-->“类”,类名设置为“dld”,即dynamic loading dll 的每一个单词的开首字母。
2. 增加所需的定名空间及声明参数通报体式格局罗列:
using System.Runtime.InteropServices; // 用 DllImport 需用此 定名空间 using System.Reflection; // 运用 Assembly 类需用此 定名空间 using System.Reflection.Emit; // 运用 ILGenerator 需用此 定名空间
|
在“public class dld”上面增加以下代码声明参数通报体式格局罗列:
/// <summary> /// 参数通报体式格局罗列 ,ByValue 示意值通报 ,ByRef 示意址通报 /// </summary> public enum ModePass { ByValue = 0x0001, ByRef = 0x0002 } |
3. 声明LoadLibrary、GetProcAddress、FreeLibrary及私有变量hModule和farProc:
/// <summary> /// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName); /// </summary> /// <param name="lpFileName">DLL 文件名 </param> /// <returns> 函数库模块的句柄 </returns> [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); /// <summary> /// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName); /// </summary> /// <param name="hModule"> 包含需挪用函数的函数库模块的句柄 </param> /// <param name="lpProcName"> 挪用函数的称号 </param> /// <returns> 函数指针 </returns> [DllImport("kernel32.dll")] static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); /// <summary> /// 原型是 : BOOL FreeLibrary(HMODULE hModule); /// </summary> /// <param name="hModule"> 需开释的函数库模块的句柄 </param> /// <returns> 是不是已开释指定的 Dll</returns> [DllImport("kernel32",EntryPoint="FreeLibrary",SetLastError=true)] static extern bool FreeLibrary(IntPtr hModule); /// <summary> /// Loadlibrary 返回的函数库模块的句柄 /// </summary> private IntPtr hModule=IntPtr.Zero; /// <summary> /// GetProcAddress 返回的函数指针 /// </summary> private IntPtr farProc=IntPtr.Zero; |
4. 增加LoadDll要领,并为了挪用时轻易,重载了这个要领:
/// <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(" 所传入的函数库模块的句柄 HMODULE 为空 ." )); hModule=HMODULE; } |
5. 增加LoadFun要领,并为了挪用时轻易,也重载了这个要领,要领的细致代码及解释以下:
/// <summary> /// 获得函数指针 /// </summary> /// <param name="lpProcName"> 挪用函数的称号 </param> public void LoadFun(string lpProcName) { // 若函数库模块的句柄为空,则抛出非常 if(hModule==IntPtr.Zero) throw(new Exception(" 函数库模块的句柄为空 , 请确保已举行 LoadDll 操纵 !")); // 获得函数指针 farProc = GetProcAddress(hModule,lpProcName); // 若函数指针,则抛出非常 if(farProc==IntPtr.Zero) throw(new Exception(" 没有找到 :"+lpProcName+" 这个函数的进口点 ")); } /// <summary> /// 获得函数指针 /// </summary> /// <param name="lpFileName"> 包含需挪用函数的 DLL 文件名 </param> /// <param name="lpProcName"> 挪用函数的称号 </param> public void LoadFun(string lpFileName,string lpProcName) { // 获得函数库模块的句柄 hModule=LoadLibrary(lpFileName); // 若函数库模块的句柄为空,则抛出非常 if(hModule==IntPtr.Zero) throw(new Exception(" 没有找到 :"+lpFileName+"." )); // 获得函数指针 farProc = GetProcAddress(hModule,lpProcName); // 若函数指针,则抛出非常 if(farProc==IntPtr.Zero) throw(new Exception(" 没有找到 :"+lpProcName+" 这个函数的进口点 ")); } |
6. 增加UnLoadDll及Invoke要领,Invoke要领也举行了重载:
/// <summary> /// 卸载 Dll /// </summary> public void UnLoadDll() { FreeLibrary(hModule); hModule=IntPtr.Zero; farProc=IntPtr.Zero; } |
Invoke要领的第一个版本:
/// <summary> /// 挪用所设定的函数 /// </summary> /// <param name="ObjArray_Parameter"> 实参 </param> /// <param name="TypeArray_ParameterType"> 实参范例 </param> /// <param name="ModePassArray_Parameter"> 实参传送体式格局 </param> /// <param name="Type_Return"> 返回范例 </param> /// <returns> 返回所挪用函数的 object</returns> public object Invoke(object[] ObjArray_Parameter,Type[] TypeArray_ParameterType,ModePass[] ModePassArray_Parameter,Type Type_Return) { // 下面 3 个 if 是举行平安搜检 , 若不能经由过程 , 则抛出非常 if(hModule==IntPtr.Zero) throw(new Exception(" 函数库模块的句柄为空 , 请确保已举行 LoadDll 操纵 !")); if(farProc==IntPtr.Zero) throw(new Exception(" 函数指针为空 , 请确保已举行 LoadFun 操纵 !" ) ); if(ObjArray_Parameter.Length!=ModePassArray_Parameter.Length) throw(new Exception(" 参数个数及其通报体式格局的个数不婚配 ." ) ); // 下面是竖立 MyAssemblyName 对象并设置其 Name 属性 AssemblyName MyAssemblyName = new AssemblyName(); MyAssemblyName.Name = "InvokeFun"; // 生成单模块配件 AssemblyBuilder MyAssemblyBuilder =AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName,AssemblyBuilderAccess.Run); ModuleBuilder MyModuleBuilder =MyAssemblyBuilder.DefineDynamicModule("InvokeDll"); // 定义要挪用的要领 , 要领名为“ MyFun ”,返回范例是“ Type_Return ”参数范例是“ TypeArray_ParameterType ” MethodBuilder MyMethodBuilder =MyModuleBuilder.DefineGlobalMethod("MyFun",MethodAttributes.Public| MethodAttributes.Static,Type_Return,TypeArray_ParameterType); // 猎取一个 ILGenerator ,用于发送所需的 IL ILGenerator IL = MyMethodBuilder.GetILGenerator(); int i; for (i = 0; i < ObjArray_Parameter.Length; i++) {// 用轮回将参数顺次压入客栈 switch (ModePassArray_Parameter[i]) { 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() + " 个参数没有给定准确的通报体式格局 ." ) ); } } if (IntPtr.Size == 4) {// 推断处置惩罚器范例 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要领的第二个版本,它是挪用了第一个版本的:
/// <summary> /// 挪用所设定的函数 /// </summary> /// <param name="IntPtr_Function"> 函数指针 </param> /// <param name="ObjArray_Parameter"> 实参 </param> /// <param name="TypeArray_ParameterType"> 实参范例 </param> /// <param name="ModePassArray_Parameter"> 实参传送体式格局 </param> /// <param name="Type_Return"> 返回范例 </param> /// <returns> 返回所挪用函数的 object</returns> public object Invoke(IntPtr IntPtr_Function,object[] ObjArray_Parameter,Type[] TypeArray_ParameterType,ModePass[] ModePassArray_Parameter,Type Type_Return) { // 下面 2 个 if 是举行平安搜检 , 若不能经由过程 , 则抛出非常 if(hModule==IntPtr.Zero) throw(new Exception(" 函数库模块的句柄为空 , 请确保已举行 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类的运用:
1. 翻开项目“Tzb”,向“Form1”窗体中增加三个按钮。Name 和Text属性分别为 “B3”、“用LoadLibrary要领装载Count.dll”,“B4”、“挪用count要领”,“B5”、“卸载Count.dll”,并调整到恰当的大小及位置。
2. 在“Form1.cs[设想]”视图中双击按钮B3,在“B3_Click”要领体上面增加代码,竖立一个dld类实例:
/// <summary> /// 竖立一个 dld 类对象 /// </summary> private dld myfun=new dld(); |
3. 在“B3_Click”要领体内增加以下代码:
myfun.LoadDll("Count.dll"); // 加载 "Count.dll" myfun.LoadFun("_count@4"); // 调入函数 count, "_count@4" 是它的进口,可经由过程 Depends 检察 |
4. “Form1.cs[设想]”视图中双击按钮B4,在“B4_Click”要领体内增加以下代码:
object[] Parameters = new object[]{(int)0}; // 实参为 0 Type[] ParameterTypes = new Type[]{typeof(int)}; // 实参范例为 int ModePass[] themode=new ModePass[]{ModePass.ByValue}; // 传送体式格局为值传 Type Type_Return = typeof(int); // 返回范例为 int // 弹出提示框,显现挪用 myfun.Invoke 要领的效果,即挪用 count 函数 MessageBox.Show(" 这是您装载该 Dll 后第 "+myfun.Invoke(Parameters,ParameterTypes,themode,Type_Return).ToString() +" 次点击此按钮。 "," 挑战杯 "); |
5. “Form1.cs[设想]”视图中双击按钮B5,在“B5_Click”要领体内增加以下代码:
myfun.UnLoadDll(); |
6. 按“F5”运转该顺序,并先点击按钮B3以加载“Count.dll”,接着点击按钮B4三次以挪用3次“count(0)”,前后弹出的提示框以下:
这三个提示框所得出的效果申清楚明了静态变量S 经初始化后,再传入实参“0”也不会转变其值为“0”。
7. 点击按钮B5以卸载“Count.dll”,再点击按钮B3举行装载“Count.dll”,再点击按钮B4检察挪用了“count(0)”的效果:
从弹出的提示框所显现的效果能够看到又最先从新计数了,也就是完成了DLL的动态装载与卸载了。
(三) 挪用托管DLL平常要领
C# 挪用托管DLL是很简单的,只需在“解决方案资本管理器”中的须要挪用DLL的项面前目今用鼠标右击“援用”,并挑选“增加援用”,然后挑选已列出的DLL或经由过程阅读来挑选DLL文件,末了须要用using 导入相干的定名空间。
(四) 动态挪用托管DLL
C# 动态挪用托管DLL也须要借助System.Reflection.Assembly里的类和要领,重要运用了Assembly.LoadFrom。如今,用例子申明:
起首,启动VS.NET,新建一个Visual C# 项目,运用的模板为“类库”,称号为“CsCount”,并在类“Class1”中增加静态整型变量S及要领count:
// 因为 static 不能润饰要领体内的变量,所以需放在这里,且初始化值为 int.MinValue static int S=int.MinValue; public int count(int init) {// 推断 S 是不是即是 int.MinValue ,是的话把 init 赋值给 S if(S==int.MinValue) S=init; S++; //S 自增 1 return S; // 返回 S } |
然后,翻开项目“Tzb”,向“Form1”窗体中增加一个按钮,Name属性为“B6”,Text属性为“用Assembly类来动态挪用托管DLL”,调整到恰当大小和位置,双击按钮B6,转入代码视图,先导入定名空间:using System.Reflection; 接着增加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) {// 查找要挪用的定名空间及类 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 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()+" 次点击此按钮。 "," 挑战杯 "); |
末了,把项目“CsCount”的bin/Debug文件夹中的CsCount.dll复制到项目“Tzb”的bin/Debug文件夹中,按“F5”运转该顺序,并点击按钮B6三次,将会弹出3个提示框,内容分别是“这是您第 1次点击此按钮。”、“这是您第 2次点击此按钮。”、“这是您第 3次点击此按钮。”,由此晓得了静态变量S在这里的作用。
(五) C#顺序嵌入DLL的挪用
DLL文件作为资本嵌入在C#顺序中,我们只需读取该资本文件并以“byte[]”返回,然后就用“Assembly Load(byte[]);”获得DLL中的顺序集,末了就能够像上面的Invoke要领那样对DLL中的要领举行挪用。固然不必上面要领也能够,如用接口完成动态挪用,但DLL中必需有该接口的定义而且顺序中也要有该接口的定义;也可用反射发送完成动态挪用[4]。如今我只对像上面的Invoke要领那样对DLL中的要领举行挪用举行议论,为了今后运用轻易及完成代码的复用,我们能够连系上一个编写一个类。
1) ldfs类的编写:
在项目“Tzb”中新建一个名为ldfs的类,意为“load dll from resource”,请注重,在这个类中“resource”不只是嵌入在EXE顺序中的资本,它也能够是硬盘上恣意一个DLL文件,这是因为ldfs的类中的要领LoadDll有些迥殊,就是先从顺序的内嵌的资本中查找需加载的DLL,假如找不到,就查找硬盘上的。
起首导入所需的定名空间:
using System.IO; // 对文件的读写须要用到此定名空间 using System.Reflection; // 运用 Assembly 类需用此定名空间 using System.Reflection.Emit; // 运用 ILGenerator 需用此定名空间 |
声明一静态变量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 {// 假如资本没有所需的 DLL ,就检察硬盘上有没有,有的话就读取 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要领来卸载DLL:
public void UnLoadDll() {// 使 MyAssembly 指空 MyAssembly=null; } |
增加Invoke要领来举行对DLL中要领的挪用,其道理大体上和“Form1.cs”中的要领Invoke雷同,不过这里用的是“Assembly.Load”,而且用了静态变量MyAssembly来保留已加载的DLL,假如已加载的话就不再加载,假如还没加载或许已加载的差别如今要加载的DLL就举行加载,其代码以下所示:
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类的运用:
1. 把CsCount.dll作为“嵌入的资本”增加到项目“Tzb”中。
2. 向“Form1”窗体中增加两个按钮,Name和Text属性分别为“B7”、“ldfs.Invoke挪用count”;“B8”、“UnLoadDll”,并将它们调整到恰当大小和位置。
3. 翻开“Form1.cs”代码视图,增加一个ldfs实例:
// 增加一个 ldfs 实例 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()+" 次点击此按钮。 "," 挑战杯 "); |
5. 在“Form1.cs[设想]”视图中双击按钮B7,在“B1_Click”要领体内增加以下代码:
// 卸载 DLL tmp.UnLoadDll(); |
6. “F5”运转该顺序,并先点击按钮B7三次,接着点击按钮B8,末了再点击按钮B7,此时发明又最先从新计数了,状况和“dld类的运用”相似,也就是也完成了DLL的动态装载与卸载了。
申明:以上所用到的一切源代码详见附件1:Form1.cs、附件2:dld.cs、附件3:ldfs.cs、附件4:Count.cpp、附件5:Class1.cs。
三、 结 论
运用DLL有许多长处,如:节约内存和削减交流操纵;开辟大型顺序时能够把某些模块分配给顺序员,顺序员能够用任何一门他所熟习的言语把该模块编译成DLL文件,如许能够进步代码的复用,大大减轻顺序员的工作量。固然DLL也有一些不足,如在提纲中说起的题目。所以,怎样天真地挪用DLL应该是每位顺序员所熟知的。
C# 言语有许多长处,越来越多的人最先运用它来编程。然则,C#另有一些不足,如对不少的底层操纵是无计可施的,只能经由过程挪用Win32 DLL 或C++等编写的DLL;别的,平常以为C#顺序的保密性不够强,因为它轻易被Reflector 反编译而获得部份源码,所以须要运用夹杂编程增强C#顺序的保密性,而把DLL嵌入C#顺序并完成动态挪用的要领是比较抱负的要领,因为能够把DLL文件先用某一算法举行加密以至紧缩后再作为资本文件增加到C#顺序中,在顺序运转时才用某一算法举行解压解密后才举行加载,所以即运用反编译软件,也只能获得一个资本文件,且这个资本文件是用一个庞杂算法举行加密过的,不可能再次对资本文件中的内容举行反编译,从而大大增强了代码的保密性。
以上就是C#顺序完成动态挪用DLL的概况引见的细致内容,更多请关注ki4网别的相干文章!