做爲初學者來講,在C#中使用API確是一件使人頭疼的問題。api
在使用API以前你必須知道如何在C#中使用結構、類型轉換、安全/不安全代碼,可控/不可控代碼等許多知識。緩存
在.Net Framework SDK文檔中,關於調用Windows API的指示比較零散,而且其中稍全面一點的是針對Visual Basic .net講述的。安全
一.調用格式app
using System.Runtime.InteropServices; //引用此名稱空間 //使用DllImportAttribute特性來引入api函數,注意聲明的是空方法,即方法體爲空。 [DllImport("user32.dll")] public static extern ReturnType FunctionName(type arg1,type arg2,...);
DllImportAttribute特性簡介函數
一、CallingConvention 指示向非託管實現傳遞方法參數時所用的 CallingConvention 值。佈局
CallingConvention.Cdecl : 調用方清理堆棧。它使您可以調用具備 varargs 的函數。
CallingConvention.StdCall : 被調用方清理堆棧。它是從託管代碼調用非託管函數的默認約定。測試
二、CharSet 控制調用函數的名稱版本及指示如何向方法封送 String 參數。ui
此字段被設置爲 CharSet 值之一。若是 CharSet 字段設置爲 Unicode,則全部字符串參數在傳遞到非託管實現以前都轉換成 Unicode 字符。這還致使向 DLL EntryPoint 的名稱中追加字母「W」。若是此字段設置爲 Ansi,則字符串將轉換成 ANSI 字符串,同時向 DLL EntryPoint 的名稱中追加字母「A」。大多數 Win32 API 使用這種追加「W」或「A」的約定。若是 CharSet 設置爲 Auto,則這種轉換就是與平臺有關的(在 Windows NT 上爲 Unicode,在 Windows 98 上爲 Ansi)。CharSet 的默認值爲 Ansi。CharSet 字段也用於肯定將從指定的 DLL 導入哪一個版本的函數。CharSet.Ansi 和 CharSet.Unicode 的名稱匹配規則大不相同。this
對於 Ansi 來講,若是將 EntryPoint 設置爲「MyMethod」且它存在的話,則返回「MyMethod」。若是 DLL 中沒有「MyMethod」,但存在「MyMethodA」,則返回「MyMethodA」。對於 Unicode 來講則正好相反。若是將 EntryPoint 設置爲「MyMethod」且它存在的話,則返回「MyMethodW」。若是 DLL 中不存在「MyMethodW」,但存在「MyMethod」,則返回「MyMethod」。若是使用的是 Auto,則匹配規則與平臺有關(在 Windows NT 上爲 Unicode,在 Windows 98 上爲 Ansi)。若是 ExactSpelling 設置爲 true,則只有當 DLL 中存在「MyMethod」時才返回「MyMethod」。spa
三、EntryPoint 指示要調用的 DLL 入口點的名稱或序號。
若是你的方法名不想與api函數同名的話,必定要指定此參數,例如:
[DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")] public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type);
四、ExactSpelling 指示是否應修改非託管 DLL 中的入口點的名稱,以與 CharSet 字段中指定的 CharSet 值相對應。若是爲 true,則當 DllImportAttribute.CharSet 字段設置爲 CharSet 的 Ansi 值時,向方法名稱中追加字母 A,當 DllImportAttribute.CharSet 字段設置爲 CharSet 的 Unicode 值時,向方法的名稱中追加字母 W。此字段的默認值是 false。
五、PreserveSig 指示託管方法簽名不該轉換成返回 HRESULT、而且可能有一個對應於返回值的附加 [out, retval] 參數的非託管簽名。
六、SetLastError 指示被調用方在從屬性化方法返回以前將調用 Win32 API SetLastError。 true 指示調用方將調用 SetLastError,默認爲 false。運行時封送拆收器將調用 GetLastError 並緩存返回的值,以防其被其餘 API 調用重寫。用戶可經過調用 GetLastWin32Error 來檢索錯誤代碼。
2、參數類型:
一、數值型直接用對應的就可。(DWORD -> int , WORD -> Int16)
二、API中字符串指針類型 -> .net中string
三、API中句柄 (dWord) -> .net中IntPtr
四、API中結構 -> .net中結構或者類。注意這種狀況下,要先用StructLayout特性限定聲明結構或類
公共語言運行庫利用StructLayoutAttribute控制類或結構的數據字段在託管內存中的物理佈局,即類或結構須要按某種方式排列。若是要將類傳遞給須要指定佈局的非託管代碼,則顯式控制類佈局是重要的。它的構造函數中用LayoutKind值初始化 StructLayoutAttribute 類的新實例。 LayoutKind.Sequential 用於強制將成員按其出現的順序進行順序佈局。
LayoutKind.Explicit 用於控制每一個數據成員的精確位置。利用 Explicit, 每一個成員必須使用 FieldOffsetAttribute 指示此字段在類型中的位置。如:
[StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)] public class MySystemTime { [FieldOffset(0)]public ushort wYear; [FieldOffset(2)]public ushort wMonth; [FieldOffset(4)]public ushort wDayOfWeek; [FieldOffset(6)]public ushort wDay; [FieldOffset(8)]public ushort wHour; [FieldOffset(10)]public ushort wMinute; [FieldOffset(12)]public ushort wSecond; [FieldOffset(14)]public ushort wMilliseconds; }
下面是針對API中OSVERSIONINFO結構,在.net中定義對應類或結構的例子:
/********************************************** * API中定義原結構聲明 * OSVERSIONINFOA STRUCT * dwOSVersionInfoSize DWORD ? * dwMajorVersion DWORD ? * dwMinorVersion DWORD ? * dwBuildNumber DWORD ? * dwPlatformId DWORD ? * szCSDVersion BYTE 128 dup (?) * OSVERSIONINFOA ENDS * * OSVERSIONINFO equ <OSVERSIONINFOA> *********************************************/ //.net中聲明爲類
[ StructLayout( LayoutKind.Sequential )] public class OSVersionInfo { public int OSVersionInfoSize; public int majorVersion; public int minorVersion; public int buildNumber; public int platformId; [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )] public String versionString; }
[ StructLayout( LayoutKind.Sequential )] public struct OSVersionInfo2 { public int OSVersionInfoSize; public int majorVersion; public int minorVersion; public int buildNumber; public int platformId; [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )] public String versionString; }
此例中用到MashalAs特性,它用於描述字段、方法或參數的封送處理格式。用它做爲參數前綴並指定目標須要的數據類型。例如,如下代碼將兩個參數做爲數據類型長指針封送給 Windows API 函數的字符串 (LPStr):
[MarshalAs(UnmanagedType.LPStr)] string existingfile; [MarshalAs(UnmanagedType.LPStr)] string newfile;
注意結構做爲參數時候,通常前面要加上ref修飾符,不然會出現錯誤:對象的引用沒有指定對象的實例。
[ DllImport( "kernel32", EntryPoint="GetVersionEx" )] public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi );
3、如何保證使用託管對象的平臺調用成功?
若是在調用平臺 invoke 後的任何位置都未引用託管對象,則垃圾回收器可能將完成該託管對象。這將釋放資源並使句柄無效,從而致使平臺invoke 調用失敗。用 HandleRef 包裝句柄可保證在平臺 invoke 調用完成前,不對託管對象進行垃圾回收。
例以下面:
FileStream fs = new FileStream( "a.txt", FileMode.Open ); StringBuilder buffer = new StringBuilder( 5 ); int read = 0; ReadFile(fs.Handle, buffer, 5, out read, 0 ); //調用Win API中的ReadFile函數
因爲fs是託管對象,因此有可能在平臺調用還未完成時候被垃圾回收站回收。將文件流的句柄用HandleRef包裝後,就能避免被垃圾站回收:
[ DllImport( "Kernel32.dll" )] public static extern bool ReadFile( HandleRef hndRef, StringBuilder buffer, int numberOfBytesToRead, out int numberOfBytesRead, ref Overlapped flag ); FileStream fs = new FileStream( "HandleRef.txt", FileMode.Open ); HandleRef hr = new HandleRef( fs, fs.Handle ); StringBuilder buffer = new StringBuilder( 5 ); int read = 0; // platform invoke will hold reference to HandleRef until call ends ReadFile( hr, buffer, 5, out read, 0 );
示例:
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Windows.Forms; namespace ApplicationTest { public class ApplicationShow { [DllImport("user32.dll")] private extern static int GetWindow(int hWnd, int wCmd); [DllImport("user32.dll")] private extern static int GetWindowLongA(int hWnd, int Indx); [DllImport("user32.dll", CharSet = CharSet.Auto)] private extern static int GetWindowTextLength(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool GetWindowText(int hWnd, StringBuilder title, int maxBufSize); private const int GW_HWNDFIRST = 0; private const int GW_HWNDNEXT = 2; private const int GWL_STYLE = (-16); private const int WS_VISIBLE = 268435456; private const int WS_BORDER = 8388608; public static List<string> GetRunApplication(Form form) { List<string> appString = new List<string>(); try { int handle = (int)form.Handle; int hwCurr; hwCurr = GetWindow(handle, GW_HWNDFIRST); while (hwCurr > 0) { int isTask = (WS_VISIBLE | WS_BORDER); int lngStyle = GetWindowLongA(hwCurr, GWL_STYLE); bool taskWindow = ((lngStyle & isTask) == isTask); if (taskWindow) { int length = GetWindowTextLength(new IntPtr(hwCurr)); StringBuilder sb = new StringBuilder(2 * length + 1); GetWindowText(hwCurr, sb, sb.Capacity); string strTitle = sb.ToString(); if (!string.IsNullOrEmpty(strTitle)) { appString.Add(strTitle); } } hwCurr = GetWindow(hwCurr, GW_HWNDNEXT); } return appString; } catch (Exception ex) { throw; } } } }
測試
private void btn_test_Click(object sender, EventArgs e) { List<string> list = ApplicationShow.GetRunApplication(this); list.ForEach(process => { lbox_test.Items.Add(process); }); }