• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

C#程序实现动态调用DLL的详情介绍

c# 搞代码 4年前 (2022-01-09) 25次浏览 已收录 0个评论

第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;

这三个提示框所得出的结果说明了静态变量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.MinValuestatic int S=int.MinValue;public int count(int init){// 判断 S 是否等于 int.MinValue ,是的话把 init 赋值给 Sif(S==int.MinValue) S=init;S++; //S 自增 1return S; // 返回 S}

然后,打开项目“Tzb”,向“Form1”窗体中添加一个按钮,Name属性为“B6”,Text属性为“用Assembly类来动态调用托管DLL”,调整到适当大小和位置,双击按钮B6,转入代码视图,先导入命名空间:using Sy来2源gaodaima#com搞(代@码&网stem.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(" 装载出错 !");}}}//trycatch(System.NullReferenceException e){MessageBox.Show(e.Message);}//catchreturn (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{// 尝试读取资源中的 DLLfs = 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);}elseSystem.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 实例 tmpprivate 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”方法体内添加如下代码:

// 卸载 DLLtmp.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的详情介绍的详细内容,更多请关注搞代码gaodaima其它相关文章!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:C#程序实现动态调用DLL的详情介绍

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址