Conmajia

Stop stealing sheep!

导航

< 2025年2月 >
26 27 28 29 30 31 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 1
2 3 4 5 6 7 8

统计

在可执行文件中嵌入动态链接库

©本文转自网络著作权归原作者所有

原文C#程序实现动态调用DLL的研究

作者黄金海岸

由于本文创作时间较早2006920所以原文中所使用的技术会显得较为陈旧本人将此文重新排版作为归档之用目前已有大量动态调用dynamic编程等技术可用故本文内容只做参考之用

化功大法——DLL嵌入EXE2004年第3CSDN开发高手——野比注一文介绍了如何把一个动态链接库作为一个资源嵌入到可执行文件在可执行文件运行时自动从资源中释放出来通过静态加载延迟实现DLL函数的动态加载程序退出后实现临时文件的自动删除从而为解决“DLL Hell”提供了一种解决方案这是一个很好的设计思想而且该作者也用C++实现了Internet上也有相似的VB程序但在某一技术论坛上提起这种设计方法时有网友提出“这种方法好是好但就是启动速度太慢”这是因为程序启动时实现DLL释放然后再加载释放出来的DLL这个过程会耗费一定的时间鉴于此问题经过思索提出另一个设计方案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的方法进行介绍首先,您需要了解什么是托管,什么是非托管一般可以认为非托管代码主要是基于Win32平台开发的DLLactiveX的组件托管代码是基于.net平台开发的如果您想深入了解托管与非托管的关系与区别及它们的运行机制请您自行查找资料本文件在此不作讨论 

1调用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”打开“Form1cs”代码视图在“namespace Tzb”上面输入“using System.Runtime.InteropServices;”以导入该命名空间 

5在“Form1cs设计”视图中双击按钮B1在“B1_Click”方法上面使用关键字 static extern 声明方法“MsgBox”DllImport 属性附加到该方法这里我们要使用的是“user32dll”中的“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结果当需要重新计数时就不能得出想要的结果下面将用例子说明 

1DLL的创建 

a) 启动Visual C++ 6.0 

b) 新建一个“Win32 Dynamic-Link Library”工程工程名称为“Count” 

c) 在“Dll kind”选择界面中选择“A simple dll project” 

d) 打开Count.cpp添加如下代码 

复制代码
1 // 导出函数,使用“ _stdcall ” 标准调用 
2 extern "C" _declspec(dllexport)int _stdcall count(int init); 
3 int _stdcall count(int init) 
4 {
5     //count 函数,使用参数 init 初始化静态的整形变量 S ,并使 S 自加 1 后返回该值 
6     static int S=init; 
7     S++; 
8     return S; 
9 } 
复制代码

e) 按“F7”进行编译得到Count.dll在工程目录下的Debug文件夹中

2DllImport调用DLL中的count函数 

a) 打开项目“Tzb”向“Form1”窗体中添加一个按钮 

b) 改变按钮的属性Name为 “B2”Text为 “用DllImport调用DLLcount函数”并将按钮B1调整到适当大小移到适当位置 

c) 打开“Form1cs”代码视图使用关键字 static extern 声明方法“count”并使其具有来自 Count.dll 的导出函数 count 的实现代码如下

[DllImport("Count.dll")] 
static extern int count(int init); 

d) 在“Form1cs设计”视图中双击按钮B2在“B2_Click”方法体内添加如下代码 

1 MessageBox.Show(" 用 DllImport 调用 DLL 中的 count 函数, 传入的实参为 0 ,得到的结果是: " + count(0).ToString(), " 挑战杯 "); 
2 MessageBox.Show(" 用 DllImport 调用 DLL 中的 count 函数, 传入的实参为 10 ,得到的结果是: " + count(10).ToString() + " 结果可不是想要的 11 哦!!! ", " 挑战杯 "); 
3 MessageBox.Show(" 所得结果表明: 用 DllImport 调用 DLL 中的非托管函数是全局的、静态的函数!!! ", " 挑战杯 ");

e) 把Count.dll复制到项目“Tzb”的binDebug文件夹中按“F5”运行该程序并点击按钮B2便弹出如下三个提示框

 

 

1个提示框显示的是调用“count(0)”的结果2个提示框显示的是调用“count(10)”的结果由所得结果可以证明“用DllImport调用DLL中的非托管函数是全局的静态的函数”所以有时候并不能达到我们目的因此我们需要使用下面所介绍的方法C#动态调用DLL中的函数

3C#动态调用DLL中的函数 

因为C#中使用DllImport时不能像动态load/unload assembly那样所以只能借助API函数了kernel32.dll与动态库调用有关的函数包括[3] 

①LoadLibraryMFC 的AfxLoadLibrary装载动态库 

②GetProcAddress获取要引入的函数将符号名或标识号转换为DLL内部地址 

③FreeLibraryMFCAfxFreeLibrary释放动态链接库 

它们的原型分别是 

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.EmitSystem.Reflection.Assembly里的类和函数达到我们的目的为了以后使用方便及实现代码的复用我们可以编写一个类 

1) dld类的编写 

1. 打开项目“Tzb”打开类视图右击“Tzb”选择“添加”-->“类”类名设置为“dld”dynamic loading dll 的每个单词的开头字母 

2. 添加所需的命名空间及声明参数传递方式枚举 

1 using System.Runtime.InteropServices; // 用 DllImport 需用此 命名空间 
2 using System.Reflection; // 使用 Assembly 类需用此 命名空间 
3 using System.Reflection.Emit; // 使用 ILGenerator 需用此 命名空间

在“public class dld”上面添加如下代码声明参数传递方式枚举 

1 /// 
2 /// 参数传递方式枚举 ,ByValue 表示值传递 ,ByRef 表示址传递 
3 /// 
4 public enum ModePass 
5 { 
6     ByValue = 0x0001, 
7     ByRef = 0x0002 
8 }

3. 声明LoadLibraryGetProcAddressFreeLibrary及私有变量hModulefarProc 

复制代码
 1 /// 
 2 /// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName); 
 3 /// 
 4 /// 
 5 DLL 文件名 
 6 /// 函数库模块的句柄 
 7 [DllImport("kernel32.dll")] 
 8 static extern IntPtr LoadLibrary(string lpFileName); 
 9 /// 
10 /// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName); 
11 /// 
12 /// 
13 包含需调用函数的函数库模块的句柄 
14 /// 
15 调用函数的名称 
16 /// 函数指针 
17 [DllImport("kernel32.dll")] 
18 static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); 
19 /// 
20 /// 原型是 : BOOL FreeLibrary(HMODULE hModule); 
21 /// 
22 /// 
23 需释放的函数库模块的句柄 
24 /// 是否已释放指定的 Dll 
25 [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)] 
26 static extern bool FreeLibrary(IntPtr hModule); 
27 /// 
28 /// Loadlibrary 返回的函数库模块的句柄 
29 /// 
30 private IntPtr hModule = IntPtr.Zero; 
31 /// 
32 /// GetProcAddress 返回的函数指针 
33 /// 
34 private IntPtr farProc = IntPtr.Zero;
复制代码

4. 添加LoadDll方法并为了调用时方便重载了这个方法

复制代码
 1 /// 
 2 /// 装载 Dll 
 3 /// 
 4 /// DLL 文件名 
 5 public void LoadDll(string lpFileName) 
 6 { 
 7     hModule = LoadLibrary(lpFileName); 
 8     if(hModule == IntPtr.Zero) 
 9         throw(new Exception(" 没有找到 :" + lpFileName + "." )); 
10 }
复制代码

©本文转自网络著作权归原作者所有由野比<conmajia@gmail.com>重新排版

若已有已装载Dll的句柄可以使用LoadDll方法的第二个版本 

1 public void LoadDll(IntPtr HMODULE) 
2 { 
3     if(HMODULE == IntPtr.Zero) 
4         throw(new Exception(" 所传入的函数库模块的句柄 HMODULE 为空 ." )); 
5     hModule = HMODULE; 
6 }

5. 添加LoadFun方法并为了调用时方便也重载了这个方法方法的具体代码及注释如下 

复制代码
 1 /// 
 2 /// 获得函数指针 
 3 /// 
 4 /// 调用函数的名称 
 5 public void LoadFun(string lpProcName) 
 6 {
 7     // 若函数库模块的句柄为空,则抛出异常 
 8     if(hModule==IntPtr.Zero) 
 9         throw(new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !")); 
10     // 取得函数指针 
11     farProc = GetProcAddress(hModule, lpProcName); 
12     // 若函数指针,则抛出异常 
13     if(farProc==IntPtr.Zero) 
14         throw(new Exception(" 没有找到 :" + lpProcName + " 这个函数的入口点 ")); 
15 } 
16 /// 
17 /// 获得函数指针 
18 /// 
19 /// 包含需调用函数的 DLL 文件名 
20 /// 调用函数的名称 
21 public void LoadFun(string lpFileName, string lpProcName) 
22 {
23     // 取得函数库模块的句柄 
24     hModule = LoadLibrary(lpFileName); 
25     // 若函数库模块的句柄为空,则抛出异常 
26     if(hModule==IntPtr.Zero) 
27         throw(new Exception(" 没有找到 :" + lpFileName + "." )); 
28     // 取得函数指针 
29     farProc = GetProcAddress(hModule, lpProcName); 
30     // 若函数指针,则抛出异常 
31     if(farProc == IntPtr.Zero) 
32         throw(new Exception(" 没有找到 :" + lpProcName + " 这个函数的入口点 ")); 
33 }
复制代码

6. 添加UnLoadDllInvoke方法Invoke方法也进行了重载 

复制代码
1 /// 
2 /// 卸载 Dll 
3 /// 
4 public void UnLoadDll() 
5 { 
6     FreeLibrary(hModule); 
7     hModule = IntPtr.Zero; 
8     farProc = IntPtr.Zero; 
9 }
复制代码

Invoke方法的第一个版本 

复制代码
 1 /// 
 2 /// 调用所设定的函数 
 3 /// 
 4 /// 实参 
 5 /// 实参类型 
 6 /// 实参传送方式 
 7 /// 返回类型 
 8 /// 返回所调用函数的 object 
 9 public object Invoke(object[] ObjArray_Parameter, Type[] TypeArray_ParameterType, ModePass[] ModePassArray_Parameter, Type Type_Return) 
10 { 
11     // 下面 3 个 if 是进行安全检查 , 若不能通过 , 则抛出异常 
12     if(hModule == IntPtr.Zero) 
13         throw(new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !")); 
14     if(farProc == IntPtr.Zero) 
15         throw(new Exception(" 函数指针为空 , 请确保已进行 LoadFun 操作 !" ) ); 
16     if(ObjArray_Parameter.Length != ModePassArray_Parameter.Length) 
17         throw(new Exception(" 参数个数及其传递方式的个数不匹配 ." ) ); 
18     // 下面是创建 MyAssemblyName 对象并设置其 Name 属性 
19     AssemblyName MyAssemblyName = new AssemblyName(); 
20     MyAssemblyName.Name = "InvokeFun"; 
21     // 生成单模块配件 
22     AssemblyBuilder MyAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName, AssemblyBuilderAccess.Run); 
23     ModuleBuilder MyModuleBuilder = MyAssemblyBuilder.DefineDynamicModule("InvokeDll"); 
24     // 定义要调用的方法 , 方法名为“ MyFun ”,返回类型是“
25     Type_Return ”参数类型是“ TypeArray_ParameterType ” 
26 MethodBuilder MyMethodBuilder = MyModuleBuilder.DefineGlobalMethod("MyFun", MethodAttributes.Public | MethodAttributes.Static, Type_Return, TypeArray_ParameterType); 
27     // 获取一个 ILGenerator ,用于发送所需的 IL 
28     ILGenerator IL = MyMethodBuilder.GetILGenerator(); 
29     int i; 
30     for (i = 0; i 
31     {
32         // 用循环将参数依次压入堆栈 
33         switch (ModePassArray_Parameter) 
34         { 
35             case ModePass.ByValue: 
36                 IL.Emit(OpCodes.Ldarg, i); 
37                 break; 
38             case ModePass.ByRef: 
39                 IL.Emit(OpCodes.Ldarga, i); 
40                 break; 
41             default: 
42                 throw(new Exception("" + (i + 1).ToString() + " 个参数没有给定正确的传递方式 ." ) ); 
43         } 
44     } 
45     if (IntPtr.Size == 4)
46     {
47         // 判断处理器类型 
48         IL.Emit(OpCodes.Ldc_I4, farProc.ToInt32()); 
49     } 
50     else if (IntPtr.Size == 8) 
51     { 
52         IL.Emit(OpCodes.Ldc_I8, farProc.ToInt64()); 
53     } 
54     else 
55     { 
56         throw new PlatformNotSupportedException(); 
57     } 
58     IL.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, Type_Return, TypeArray_ParameterType); 
59     IL.Emit(OpCodes.Ret);
60     // 返回值 
61     MyModuleBuilder.CreateGlobalFunctions(); 
62     // 取得方法信息 
63     MethodInfo MyMethodInfo = MyModuleBuilder.GetMethod("MyFun"); 
64     return MyMethodInfo.Invoke(null, ObjArray_Parameter);
65     // 调用方法,并返回其值 
66 }
复制代码

Invoke方法的第二个版本它是调用了第一个版本的 

复制代码
 1 /// 
 2 /// 调用所设定的函数 
 3 /// 
 4 /// 函数指针 
 5 /// 实参 
 6 /// 实参类型 
 7 /// 实参传送方式 
 8 /// 返回类型 
 9 /// 返回所调用函数的 object 
10 public object Invoke(IntPtr IntPtr_Function, object[] ObjArray_Parameter, Type[] TypeArray_ParameterType, ModePass[] ModePassArray_Parameter, Type Type_Return) 
11 { 
12     // 下面 2 个 if 是进行安全检查 , 若不能通过 , 则抛出异常 
13     if(hModule == IntPtr.Zero) 
14         throw(new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !")); 
15     if(IntPtr_Function == IntPtr.Zero) 
16         throw(new Exception(" 函数指针 IntPtr_Function 为空 !" ) ); 
17     farProc = IntPtr_Function; 
18     return Invoke(ObjArray_Parameter, TypeArray_ParameterType, ModePassArray_Parameter, Type_Return); 
19 }
复制代码

2) dld类的使用 

1 打开项目“Tzb”向“Form1”窗体中添加三个按钮Name 和Text属性分别为 “B3”“用LoadLibrary方法装载Count.dll”“B4”“调用count方法”“B5”“卸载Count.dll”并调整到适当的大小及位置 

2 在“Form1cs设计”视图中双击按钮B3在“B3_Click”方法体上面添加代码创建一个dld类实例 

1 /// 
2 /// 创建一个 dld 类对象 
3 /// 
4 private dld myfun=new dld();

3 在“B3_Click”方法体内添加如下代码 

1 myfun.LoadDll("Count.dll");
2 // 加载 "Count.dll" 
3 myfun.LoadFun("");
4 // 调入函数 count, "" 是它的入口,可通过 Depends 查看

4 “Form1cs设计”视图中双击按钮B4在“B4_Click”方法体内添加如下代码 

1 object[] Parameters = new object[]{(int)0}; // 实参为 0 
2 Type[] ParameterTypes = new Type[]{typeof(int)}; // 实参类型为 int 
3 ModePass[] themode = new ModePass[]{ModePass.ByValue}; // 传送方式为值传 
4 Type Type_Return = typeof(int); // 返回类型为 int 
5 // 弹出提示框,显示调用 myfun.Invoke 方法的结果,即调用 count 函数
6 MessageBox.Show(" 这是您装载该 Dll 后第 " + myfun.Invoke(Parameters,ParameterTypes,themode, Type_Return).ToString() + " 次点击此按钮。 "," 挑战杯 ");

5 “Form1cs设计”视图中双击按钮B5在“B5_Click”方法体内添加如下代码 

1 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 

复制代码
 1 // 由于 static 不能修饰方法体内的变量,所以需放在这里,且初始化值为 int.MinValue 
 2 static int S = int.MinValue; 
 3 public int count(int init) 
 4 {
 5     // 判断 S 是否等于 int.MinValue ,是的话把 init 赋值给 S 
 6     if(S == int.MinValue)
 7         S = init; 
 8     S++; //S 自增 1 
 9     return S; // 返回 S 
10 }
复制代码

然后打开项目“Tzb”向“Form1”窗体中添加一个按钮Name属性为“B6”Text属性为“用Assembly类来动态调用托管DLL”调整到适当大小和位置双击按钮B6转入代码视图先导入命名空间using System.Reflection; 接着添加Invoke方法和B6_Click方法代码 

复制代码
 1 private object Invoke(string lpFileName, string Namespace, string ClassName, string lpProcName, object[] ObjArray_Parameter) 
 2 { 
 3     try { // 载入程序集 
 4         Assembly MyAssembly = Assembly.LoadFrom(lpFileName); 
 5         Type[] type = MyAssembly.GetTypes(); 
 6         foreach(Type t in type) 
 7         {
 8             // 查找要调用的命名空间及类 
 9             if(t.Namespace == Namespace && t.Name == ClassName) 
10             {
11                 // 查找要调用的方法并进行调用 
12                 MethodInfo m = t.GetMethod(lpProcName); 
13                 if(m != null) 
14                 { 
15                     object o = Activator.CreateInstance(t); 
16                     return m.Invoke(o, ObjArray_Parameter); 
17                 } 
18                 else
19                     MessageBox.Show(" 装载出错 !"); 
20             } 
21         } 
22     }//try 
23     catch(System.NullReferenceException e) 
24     { 
25         MessageBox.Show(e.Message); 
26     }//catch 
27     return (object)0; 
28 }// Invoke
复制代码

“B6_Click”方法体内代码如下 

1 // 显示 count(0) 返回的值 
2 MessageBox.Show(" 这是您第 " + Invoke("CsCount.dll", "CsCount", "Class1", "count",new object[]{(int)0}).ToString() + " 次点击此按钮。 ", " 挑战杯 ");

最后把项目“CsCount”的binDebug文件夹中的CsCount.dll复制到项目“Tzb”的binDebug文件夹中按“F5”运行该程序并点击按钮B6三次将会弹出3个提示框内容分别是“这是您第 1次点击此按钮“这是您第 2次点击此按钮“这是您第 3次点击此按钮由此知道了静态变量S在这里的作用

©本文转自网络著作权归原作者所有由野比<conmajia@gmail.com>重新排版

(五) 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如果找不到就查找硬盘上的 
首先导入所需的命名空间 

1 using System.IO; // 对文件的读写需要用到此命名空间 
2 using System.Reflection; // 使用 Assembly 类需用此命名空间 
3 using System.Reflection.Emit; // 使用 ILGenerator 需用此命名空间 

声明一静态变量MyAssembly 

复制代码
 1 // 记录要导入的程序集 
 2 static Assembly MyAssembly; 
 3 添加LoadDll方法: 
 4 private byte[] LoadDll(string lpFileName) 
 5 { 
 6     Assembly NowAssembly = Assembly.GetEntryAssembly(); 
 7     Stream fs = null; 
 8     try 
 9     {
10         // 尝试读取资源中的 DLL 
11         fs =  NowAssembly.GetManifestResourceStream(NowAssembly.GetName().Name + "." + lpFileName); 
12     } 
13     finally 
14     {
15         // 如果资源没有所需的 DLL ,就查看硬盘上有没有,有的话就读取 
16         if (fs == null && !File.Exists(lpFileName))
17             throw(new Exception(" 找不到文件 :" + lpFileName)); 
18         else if(fs == null && File.Exists(lpFileName)) 
19         { 
20             FileStream Fs = new FileStream(lpFileName, FileMode.Open); 
21             fs = (Stream)Fs; 
22         } 
23     } 
24     byte[] buffer = new byte[(int) fs.Length]; 
25     fs.Read(buffer, 0, buffer.Length); 
26     fs.Close(); 
27     return buffer; // 以 byte[] 返回读到的 DLL 
28 } 
复制代码

添加UnLoadDll方法来卸载DLL 

1 public void UnLoadDll() 
2 {
3     // 使 MyAssembly 指空 
4     MyAssembly = null; 
5 } 

添加Invoke方法来进行对DLL中方法的调用其原理大体上和“Form1cs”中的方法Invoke相同不过这里用的是“Assembly.Load”而且用了静态变量MyAssembly来保存已加载的DLL如果已加载的话就不再加载如果还没加载或者已加载的不同现在要加载的DLL就进行加载其代码如下所示 

复制代码
 1 public object Invoke(string lpFileName, string Namespace, string ClassName, string lpProcName, object[] ObjArray_Parameter) 
 2 { 
 3     try 
 4     {
 5         // 判断 MyAssembly 是否为空或 MyAssembly 的命名空间不等于要调用方法的命名空间,如果条件为真,就用 Assembly.Load 加载所需 DLL 作为程序集 
 6         if(MyAssembly== null||MyAssembly.GetName().Name !=  Namespace) 
 7             MyAssembly = Assembly.Load(LoadDll(lpFileName)); 
 8         Type[] type  = MyAssembly.GetTypes(); 
 9         foreach(Type t in type) 
10         { 
11             if(t.Namespace == Namespace && t.Name == ClassName) 
12             { 
13                 MethodInfo m = t.GetMethod(lpProcName); 
14                 if(m!=null) 
15                 {
16                     // 调用并返回 
17                     object o = Activator.CreateInstance(t); 
18                     return m.Invoke(o, ObjArray_Parameter); 
19                 } 
20                 else 
21                     System.Windows.Forms.MessageBox.Show(" 装载出错 !"); 
22             } 
23         } 
24     } 
25     catch(System.NullReferenceException e) 
26     { 
27         System.Windows.Forms.MessageBox.Show(e.Message); 
28     } 
29     return (object)0; 
30 }
复制代码

2) ldfs类的使用 

1CsCount.dll作为“嵌入的资源”添加到项目“Tzb”中 

2 向“Form1”窗体中添加两个按钮NameText属性分别为“B7”“ldfs.Invoke调用count”“B8”“UnLoadDll”并将它们调整到适当大小和位置 

3 打开“Form1cs”代码视图添加一个ldfs实例 

1 // 添加一个 ldfs 实例 tmp 
2 private ldfs tmp = new ldfs(); 

4 在“Form1cs设计”视图中双击按钮B7在“B1_Click”方法体内添加如下代码 

1 // 调用 count(0), 并使用期提示框显示其返回值 
2 MessageBox.Show(" 这是您第 " + tmp.Invoke("CsCount.dll", "CsCount", "Class1", "count",new object[]{(int)0}).ToString() + " 次点击此按钮。 "," 挑战杯 "); 

5 在“Form1cs设计”视图中双击按钮B7在“B1_Click”方法体内添加如下代码 

1 // 卸载 DLL 
2 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#程序中在程序运行时才用某一算法进行解压解密后才进行加载所以即使用反编译软件也只能得到一个资源文件且这个资源文件是用一个复杂算法进行加密过的不可能再次对资源文件中的内容进行反编译从而大大加强了代码的保密性

参考文献

[1]  引自什么是 DLL?网址 http://support.microsoft.com/default.aspx?scid=kb;zh-cn;815065

[2] 在 C# 中通过 P/Invoke 调用Win32 DLL Jason Clark

网址http://www.microsoft.com/china/msdn/library/langtool/vcsharp/ousNET.mspx

[3] 深入分析WindowsLinux动态库应用异同刘世栋 杨林

网址http://tech.ccidnet.com/art/302/20050919/336005_1.html

[4] C# 程序设计 Jesse Liberty 著 刘基诚 译,中国电力出版社

©本文转自网络著作权归原作者所有

posted on2012-08-13   Conmajia  阅读(5086)  评论(0编辑  收藏  举报

编辑推荐:
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示