C#调用非托管程序5种方式
1、COM调用
COM应该是非托管组件重用最重要的方式,特别是调用微软的COM组件。
可以用VS添加引用的方式自动生成包装类,也可以用Tlbimp.exe实用工具包装COM对象生成包装类。
COM对象需要在本机注册,这个程序部署带来一定的麻烦,如果调用简单的功能,包装COM有点大材小用。
如果只简单的调用非托管函数,可以用接下来介绍的DllImprot等方式。
using System; using OLEPRNLib; namespace PrinterStatus { class Class1 { [STAThread] static void Main(string[] args) { string[] ErrorMessageText = new string[8]; ErrorMessageText[0] = "service requested"; ErrorMessageText[1] = "offline"; ErrorMessageText[2] = "paper jammed"; ErrorMessageText[3] = "door open"; ErrorMessageText[4] = "no toner"; ErrorMessageText[5] = "toner low"; ErrorMessageText[6] = "out of paper"; ErrorMessageText[7] = "low paper"; int DeviceID = 1; int Retries = 1; int TimeoutInMS = 2000; string CommunityString = "public"; string IPAddressOfPrinter = "10.3.0.93"; // Create instance of COM object OLEPRNLib.SNMP snmp = new OLEPRNLib.SNMP(); // Open the SNMP connect to the printer snmp.Open(IPAddressOfPrinter, CommunityString, Retries, TimeoutInMS); // The actual Warning/Error bits uint WarningErrorBits = snmp.GetAsByte(String.Format("25.3.5.1.2.{0}", DeviceID)); // The actual Status uint StatusResult = snmp.GetAsByte(String.Format("25.3.2.1.5.{0}", DeviceID)); // uint Result2 = snmp.GetAsByte(String.Format("25.3.5.1.1.{0}", DeviceID)); string Result1Str = ""; switch (StatusResult) { case 2: Result1Str = "OK"; break; case 3: Result1Str = "Warning: "; break; case 4: Result1Str = "Being Tested: "; break; case 5: Result1Str = "Unavailable for any use: "; break; default: Result1Str = "Unknown Status Code : " + StatusResult; break; } string Str = ""; if ((StatusResult == 3 || StatusResult == 5)) { int Mask = 1; int NumMsg = 0; for (int i = 0; i < 8; i++) { if ((WarningErrorBits & Mask) == Mask) { if (Str.Length > 0) Str += ", "; Str += ErrorMessageText[i]; NumMsg = NumMsg + 1; } Mask = Mask * 2; } } Console.WriteLine(Result1Str + Str); } } }
2、DllImport
DllImport是在"System.Runtime.InteropServices"命名空间中定义的特性。
[DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "MessageBox")] public static extern int InvokeMessageBox(IntPtr hWnd, String text, String caption, uint type); static void Main() { InvokeMessageBox(new IntPtr(0), "对话框内容", "对话框标题", 0); }
3、加载非托管动态链接库
Win32中,有个LoadLibrary(string file)函数,加载动态链接库;GetProcAddress函数动态调用导出函数。
.NET类库的 Marshal.GetDelegateForFunctionPointer 方法能将非托管函数指针转换为委托。
public delegate int MsgBox(int hwnd,string msg,string cpp,int ok); [DllImport("Kernel32")] public static extern int GetProcAddress(int handle, String funcname); [DllImport("Kernel32")] public static extern int LoadLibrary(String funcname); [DllImport("Kernel32")] public static extern int FreeLibrary(int handle); private static Delegate GetAddress(int dllModule, string functionname, Type t) { int addr = GetProcAddress(dllModule, functionname); if (addr == 0) return null; else return Marshal.GetDelegateForFunctionPointer(new IntPtr(addr), t); } private void button1_Click(object sender, EventArgs e) { int huser32 = 0; huser32 = LoadLibrary("user32.dll"); MsgBox mymsg = (MsgBox)GetAddress(huser32, "MessageBoxA", typeof(MsgBox)); mymsg(this.Handle.ToInt32(), txtmsg.Text, txttitle.Text , 64); FreeLibrary(huser32); }
4、DynamicMethod
可以使用 DynamicMethod 类在运行时生成和执行方法,而不必生成动态程序集和动态类型来包含该方法。动态方法是生成和执行少量代码的最有效方式。
using System; using System.Collections.Generic; using System.Text; using Zealic.Windows; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //测试1 DynamicLibrary hilib = new DynamicLibrary("hi.dll"); NativeMethodBuilder hiBuilder = new NativeMethodBuilder(); NativeMethod method = hiBuilder.MakeMethod(hilib, "func"); Console.WriteLine("请关闭弹出的对话框 'Hille'"); method.Invoke(); hilib.Free(); //测试2 DynamicLibrary krnlib = new DynamicLibrary("kernel32.dll"); NativeMethodBuilder beepBuilder = new NativeMethodBuilder(); beepBuilder.ParameterLength = 2; beepBuilder.SetParameterInfo(0, typeof(int)); beepBuilder.SetParameterInfo(1, typeof(int)); method = beepBuilder.MakeMethod(krnlib,"Beep"); Console.WriteLine("听,你的机器在尖叫!"); method.Invoke(1000, 1000); Console.WriteLine("按任意键退出!"); Console.ReadKey(true); } } }
5、直接调用执行机器码
机器码是最原始的程序代码,或称指令,把这些指令装载到内存,Marshal.GetDelegateForFunctionPointer方法转换为对应的委托,调用即可。
/* 执行调用本机代码、汇编代码 shell Native Code 解释 本例中 IntPtr 其实是相当于 C 语言中的 (void *) 指向任何类型的指针, 就是一个地址 32 位系统就是个 Int32,本例相当与一个函数指针 核心技术流程 变量: 【本机代码字节数组】 byte[] codeBytes ; 一段加法本机代码 【函数指针】 IntPtr handle ; 指向本机函数开始地址的变量 流程: >> 给 【函数指针】划分非托管内存 ; 使用 Marshal.AllocHGlobal(codeBytes.Length) 划分大小等同于本机代码字节总数 因为函数本身还不存在于内存中所以先开辟一块内存; 以便放置函数。 >> 将 【本机代码字节数组】中的字节写入 【函数指针】 ; Marshal.Copy(codeBytes,0,handle,codeBytes.Length); >> 使用 Marshal.GetDelegateForFunctionPointer 【函数指针】 强制转换为托管委托 DelegateAdd; 因为这时 handle 内的字节数组已经是内存中的一段本机方法的代码 handle 的“起始地址”就相当于一个“本机函数的入口地址” 所以可以成功转换为对应的委托 >> 调用 委托 ; >> 释放本机句柄;Marshal.FreeHGlobal(this._handle); 修改记录 2008-5-11 8:07 曲滨 >> 基本实现预期功能 [!] 明天进行优化 2008-5-12 15:54 曲滨 [E] 优化完成 [N] 加入 NativeCodeHelper 类便于使用 */ namespace NShellNativeCode { using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.IO; using System.Diagnostics; using System.Reflection; delegate int AddProc(int p1, int p2); class Program { static void Main(string[] args) { //一段加法函数本机代码;后面注释是给会 asm 看官看的 //笔者本身也不是太明白汇编,简单的 10行8行的还可以 byte[] codeBytes = { 0x8B, 0x44, 0x24, 0x08 // mov eax,[esp+08h] , 0x8B, 0x4C, 0x24, 0x04 // mov ecx,[esp+04h] , 0x03, 0xC1 // add eax,ecx , 0xC3 // ret }; /* 上面的字节数组,就是下面函数的本机代码; int add(int x,int y) { return x+y; } */ IntPtr handle = IntPtr.Zero; handle = Marshal.AllocHGlobal(codeBytes.Length); try { Marshal.Copy(codeBytes, 0, handle, codeBytes.Length); AddProc add = Marshal.GetDelegateForFunctionPointer(handle, typeof(AddProc)) as AddProc; int r = add(1976, 1); Console.WriteLine("本机代码返回:{0}", r); } finally { Marshal.FreeHGlobal(handle); } //本演示内包含的已经封装好的 本机字节代码,转换委托通用类 //打开注释就可以用了; /* using (NativeCodeHelper helper = new NativeCodeHelper(codeBytes)) { AddProc add = helper.ToDelegate<AddProc>(); Type t = add.Method.DeclaringType; int r = add(1976,1); Console.WriteLine("本机代码返回:{0}",r); } */ //Console.ReadLine(); } } /* 结束语 已知问题 1)在操作系统打开 DEP 保护的情况下,这类代码会不灵; 我没有测试,有兴趣的可以试验一下,估计是不会好用的; 2)如果要在 本机代码 中调用 Win API 函数,因为在不同系统不同版本中 Win API 的地址是不同的; 要有一些编写 shell code 能力于黑客技术关系密切这里不做详细描述 本文技术的适用范围 >> 遗留系统,C/C++ 的某些算法、尤其汇编形式的是如果懒的改成.net 可以直接吧二进制copy 出来直接调用、不过需要C/VC、反汇编、汇编有点了解要不没法Copy; >> 有些代码不想被反编译,给破解者增加些破解难度、郁闷有可能会改你代码的人 实用性有多少看官自己感觉吧,因为技术这东西是相对的 如果你的程序中到处都是这类代码是很难维护的,就是熟悉汇编的人 这种东西多了也很郁闷的、本机代码远比汇编难看的多 >> 忽悠小朋友 把我的代码直接copy倒你的项目里,一点都不改,要算int加法的时候都这么用 如果有小朋友看见一定会感觉你很 Cool 重要声明: 这种本机代码方式如果应用倒真实项目中一定要项目负责人的同意的情况下,否则出现 任何人事问题,或刑事问题与本文作者无关; 如 >> 在真实项目中使用本文技术,在代码中坠入逻辑炸弹者; >> 在真实项目中使用本文技术,拒不上缴本机字节代码对应的源代码者; >> 在真实项目或共享软件中,捆绑病毒代码者; */ /// <summary> /// 用于将本机代码 byte 数组转换为 .net 委托 /// </summary> /// <remarks> /// 实现了 IDisposable 使用了非托管资源 使用时不要忘记释放 /// </remarks> public class NativeCodeHelper:IDisposable { private bool _disposed = false; private byte[] _codeBytes = {}; private IntPtr _handle = IntPtr.Zero; public NativeCodeHelper(byte[] codeBytes) { this._codeBytes = codeBytes; } /// <summary> /// 把byte数字转换为本机类型的指针 主要处理 this._handle /// </summary> private void CreateHandle() { if (_handle == IntPtr.Zero) { _handle = Marshal.AllocHGlobal( this._codeBytes.Length); Marshal.Copy(_codeBytes, 0, _handle, _codeBytes.Length); } } /// <summary> /// 转换为指定的委托 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public T ToDelegate<T>() where T:class { this.CreateHandle(); //把指针转换为 委托方法 T result = Marshal.GetDelegateForFunctionPointer(_handle, typeof(T)) as T; return result; } #region IDisposable 成员 ~NativeCodeHelper() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { //给调用者忘记 Dispose 释放的提示 MethodBase mb = System.Reflection.MethodBase.GetCurrentMethod(); Type t = mb.DeclaringType; Trace.WriteLine("not Dispose" , "" + t + "." + mb ); } if (!this._disposed) { if (disposing) { //释放.net 需要 Dispose 的对象 } Marshal.FreeHGlobal(this._handle); _handle = IntPtr.Zero; } _disposed = true; } #endregion } }