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 } }