C#調用windows api的要點
最後更新:2017-02-28
來源:互聯網
上載者:User
window 在.Net Framework SDK文檔中,關於調用Windows API的指示比較零散,並且其中稍全面一點的是針對Visual Basic .net講述的。本文將C#中調用API的要點彙集如下,希望給未在C#中使用過API的朋友一點協助。另外如果安裝了Visual Studio .net的話,在C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CS目錄下有大量的調用API的例子。
一、調用格式
using System.Runtime.InteropServices; //引用此名稱空間,簡化後面的代碼
...
//使用DllImportAttribute特性來引入api函數,注意聲明的是空方法,即方法體為空白。
[DllImport("user32.dll")]
public static extern ReturnType FunctionName(type arg1,type arg2,...);
//調用時候與調用其他方法並無區別
DllImportAttribute特性的公用欄位如下:
1、CallingConvention 指示向非託管實現傳遞方法參數時所用的 CallingConvention 值。
CallingConvention.Cdecl : 調用方清理堆棧。它使您能夠調用具有 varargs 的函數。
CallingConvention.StdCall : 被呼叫者清理堆棧。它是從Managed 程式碼調用非託管函數的預設約定。
2、CharSet 控制調用函數的名稱版本及指示如何向方法封送 String 參數。
此欄位被設定為 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 的名稱匹配規則大不相同。對於 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”。
3、EntryPoint 指示要調用的 DLL 進入點的名稱或序號。
如果你的方法名不想與api函數同名的話,一定要指定此參數,例如:
[DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")]
public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type);
4、ExactSpelling 指示是否應修改非託管 DLL 中的進入點的名稱,以與 CharSet 欄位中指定的 CharSet 值相對應。如果為 true,則當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Ansi 值時,向方法名稱中追加字母 A,當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Unicode 值時,向方法的名稱中追加字母 W。此欄位的預設值是 false。
5、PreserveSig 指示託管方法簽名不應轉換成返回 HRESULT、並且可能有一個對應於傳回值的附加 [out, retval] 參數的非託管簽名。
6、SetLastError 指示被呼叫者在從屬性化方法返回之前將調用 Win32 API SetLastError。 true 指示調用方將調用 SetLastError,預設為 false。運行時封送拆收器將調用 GetLastError 並緩衝返回的值,以防其被其他 API 呼叫重寫。使用者可通過調用 GetLastWin32Error 來檢索錯誤碼。
二、參數類型:
1、數值型直接用對應的就可。
2、字串指標(api) -> string (.net)
3、控制代碼 (dWord) -> IntPtr
4、結構 -> 結構或者類
這種情況下,要先用StructLayout特性限定聲明,例如:
// declared as class
[ 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;
}
// declared as struct
[ 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 );
三、如果在調用平台 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 );