在可执行文件中嵌入动态链接库
©本文转自网络
原文
作者
由于本文创作时间较早
2006 ( 年 9 月 20 日 所以原文中所使用的技术会显得较为陈旧 ) 本人将此文重新排版 。 作为归档之用 , 目前已有大量动态调用 。 dynamic 、 编程等技术可用 故本文内容只做参考之用 , 。 )
一
动态链接库
动态链接库是不能直接执行的
DLL
下面列出了当程序使用 DLL 时提供的一些优点
1
当多个程序使用同一个函数库时
2
DLL 有助于促进模块式程序的开发
3
当 DLL 中的函数需要更新或修复时
二
每种编程语言调用
1
首先
[DLLImport(“DLL文件”)] 修饰符 extern 返回变量类型 方法名称 (参数列表)
其中
DLL
修饰符
返回变量类型
方法名称
参数列表
注意
注
DLL
返回变量类型
若要使用其它函数名
[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;
此外
C#例子
1
2
3
4
5
[DllImport("user32.dll", EntryPoint = "MessageBoxA")] static extern int MsgBox(int hWnd, string msg, string caption, int type);
然后在“B1_Click”方法体内添加如下代码
MsgBox(0, "这就是用 DllImport 调用 DLL 弹出的提示框哦! ", " 挑战杯 ", 0x30);
6
(二) 动态装载
在上面已经说明了如何用
1
a) 启动
b) 新建一个“Win32 Dynamic-Link Library”工程
c) 在“Dll kind”选择界面中选择“A simple dll project”
d) 打开
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”进行编译
2
a) 打开项目“Tzb”
b) 改变按钮的属性
c) 打开“Form1
[DllImport("Count.dll")] static extern int count(int init);
d) 在“Form1
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) 把
第
3
因为
①LoadLibrary
②GetProcAddress
③FreeLibrary
它们的原型分别是
HMODULE LoadLibrary(LPCTSTR lpFileName);
FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
BOOL FreeLibrary(HMODULE hModule);
现在
IntPtr hModule = LoadLibrary(“Count.dll”);
来获得
IntPtr farProc = GetProcAddress(hModule, ”_count@4”);
来获得函数的入口地址
但是
1) dld
1. 打开项目“Tzb”
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. 声明
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. 添加
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 }
©本文转自网络
若已有已装载
1 public void LoadDll(IntPtr HMODULE) 2 { 3 if(HMODULE == IntPtr.Zero) 4 throw(new Exception(" 所传入的函数库模块的句柄 HMODULE 为空 ." )); 5 hModule = HMODULE; 6 }
5. 添加
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. 添加
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
2
1 /// 2 /// 创建一个 dld 类对象 3 /// 4 private dld myfun=new dld();
3
1 myfun.LoadDll("Count.dll"); 2 // 加载 "Count.dll" 3 myfun.LoadFun(""); 4 // 调入函数 count, "" 是它的入口,可通过 Depends 查看
4
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
1 myfun.UnLoadDll();
6
这三个提示框所得出的结果说明了静态变量
7
从弹出的提示框所显示的结果可以看到又开始重新计数了
(三) 调用托管
C# 调用托管
(四) 动态调用托管
C# 动态调用托管
首先
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 }
然后
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() + " 次点击此按钮。 ", " 挑战杯 ");
最后
©本文转自网络
(五) C#程序嵌入
DLL
1) ldfs
在项目“Tzb”中新建一个名为
首先导入所需的命名空间
1 using System.IO; // 对文件的读写需要用到此命名空间 2 using System.Reflection; // 使用 Assembly 类需用此命名空间 3 using System.Reflection.Emit; // 使用 ILGenerator 需用此命名空间
声明一静态变量
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 }
添加
1 public void UnLoadDll() 2 { 3 // 使 MyAssembly 指空 4 MyAssembly = null; 5 }
添加
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
1
2
3
1 // 添加一个 ldfs 实例 tmp 2 private ldfs tmp = new ldfs();
4
1 // 调用 count(0), 并使用期提示框显示其返回值 2 MessageBox.Show(" 这是您第 " + tmp.Invoke("CsCount.dll", "CsCount", "Class1", "count",new object[]{(int)0}).ToString() + " 次点击此按钮。 "," 挑战杯 ");
5
1 // 卸载 DLL 2 tmp.UnLoadDll();
6
说明
三
使用
C# 语言有很多优点
参考文献
[1] 引自
[2]
网址
[3]
网址
[4]
©本文转自网络
if(jQuery('#no-reward').text() == 'true') jQuery('.bottom-reward').addClass('hidden');
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)