在
Net Framework SDK文檔中
關於調用Windows API的指示比較零散
並且其中稍全面一點的是針對Visual 講述的
本文將C#中調用API的要點匯集如下
希望給未在C#中使用過API的朋友一點幫助
另外如果安裝了Visual 的話
在C:\Program Files\Microsoft Visual Studio
NET\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CS目錄下有大量的調用API的例子
一
調用格式
using System
Runtime
InteropServices; //引用此名稱空間
簡化後面的代碼
//使用DllImportAttribute特性來引入api函數
注意聲明的是空方法
即方法體為空
[DllImport(
user
dll
)]
public static extern ReturnType FunctionName(type arg
type arg
);
//調用時與調用其他方法並無區別
可以使用字段進一步說明特性
用逗號隔開
如
[ DllImport(
kernel
EntryPoint=
GetVersionEx
)]
DllImportAttribute特性的公共字段如下
CallingConvention 指示向非托管實現傳遞方法參數時所用的 CallingConvention 值
CallingConvention
Cdecl : 調用方清理堆棧
它使您能夠調用具有 varargs 的函數
CallingConvention
StdCall : 被調用方清理堆棧
它是從托管代碼調用非托管函數的默認約定
CharSet 控制調用函數的名稱版本及指示如何向方法封送 String 參數
此字段被設置為 CharSet 值之一
如果 CharSet 字段設置為 Unicode
則所有字符串參數在傳遞到非托管實現之前都轉換成 Unicode 字符
這還導致向 DLL EntryPoint 的名稱中追加字母
W
如果此字段設置為 Ansi
則字符串將轉換成 ANSI 字符串
同時向 DLL EntryPoint 的名稱中追加字母
A
大多數 Win
API 使用這種追加
W
或
A
的約定
如果 CharSet 設置為 Auto
則這種轉換就是與平台有關的(在 Windows NT 上為 Unicode
在 Windows
上為 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
上為 Ansi)
如果 ExactSpelling 設置為 true
則只有當 DLL 中存在
MyMethod
時才返回
MyMethod
EntryPoint 指示要調用的 DLL 入口點的名稱或序號
如果你的方法名不想與api函數同名的話
一定要指定此參數
例如
[DllImport(
user
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 指示被調用方在從屬性化方法返回之前將調用 Win
API SetLastError
true 指示調用方將調用 SetLastError
默認為 false
運行時封送拆收器將調用 GetLastError 並緩存返回的值
以防其被其他 API 調用重寫
用戶可通過調用 GetLastWin
Error 來檢索錯誤代碼
二
參數類型
數值型直接用對應的就可
(DWORD
> int
WORD
> Int
)
API中字符串指針類型
>中string
API中句柄 (dWord)
>中IntPtr
API中結構
>中結構或者類
注意這種情況下
要先用StructLayout特性限定聲明結構或類
公共語言運行庫利用StructLayoutAttribute控制類或結構的數據字段在托管內存中的物理布局
即類或結構需要按某種方式排列
如果要將類傳遞給需要指定布局的非托管代碼
則顯式控制類布局是重要的
它的構造函數中用LayoutKind值初始化 StructLayoutAttribute 類的新實例
LayoutKind
Sequential 用於強制將成員按其出現的順序進行順序布局
LayoutKind
Explicit 用於控制每個數據成員的精確位置
利用 Explicit
每個成員必須使用 FieldOffsetAttribute 指示此字段在類型中的位置
如
[StructLayout(LayoutKind
Explicit
Size=
CharSet=CharSet
Ansi)]
public class MySystemTime
{
[FieldOffset(
)]public ushort wYear;
[FieldOffset(
)]public ushort wMonth;
[FieldOffset(
)]public ushort wDayOfWeek;
[FieldOffset(
)]public ushort wDay;
[FieldOffset(
)]public ushort wHour;
[FieldOffset(
)]public ushort wMinute;
[FieldOffset(
)]public ushort wSecond;
[FieldOffset(
)]public ushort wMilliseconds;
}
下面是針對API中OSVERSIONINFO結構
中定義對應類或結構的例子
/**********************************************
* API中定義原結構聲明
* OSVERSIONINFOA STRUCT
* dwOSVersionInfoSize DWORD ?
* dwMajorVersion DWORD ?
* dwMinorVersion DWORD ?
* dwBuildNumber DWORD ?
* dwPlatformId DWORD ?
* szCSDVersion BYTE
dup (?)
* OSVERSIONINFOA ENDS
*
* OSVERSIONINFO equ
*********************************************/
/中聲明為類
[ StructLayout( LayoutKindSequential )]
public class OSVersionInfo
{
public int OSVersionInfoSize;
public int majorVersion;
public int minorVersion;
public int buildNumber;
public int platformId;
[ MarshalAs( UnmanagedTypeByValTStr SizeConst= )]
public String versionString;
}
//或者
/中聲明為結構
[ StructLayout( LayoutKindSequential )]
public struct OSVersionInfo
{
public int OSVersionInfoSize;
public int majorVersion;
public int minorVersion;
public int buildNumber;
public int platformId;
[ MarshalAs( UnmanagedTypeByValTStr SizeConst= )]
public String versionString;
}
此例中用到MashalAs特性它用於描述字段方法或參數的封送處理格式用它作為參數前綴並指定目標需要的數據類型例如以下代碼將兩個參數作為數據類型長指針封送給 Windows API 函數的字符串 (LPStr)
[MarshalAs(UnmanagedTypeLPStr)]
String existingfile;
[MarshalAs(UnmanagedTypeLPStr)]
String newfile;
注意結構作為參數時候一般前面要加上ref修飾符否則會出現錯誤對象的引用沒有指定對象的實例
[ DllImport( kernel EntryPoint=GetVersionEx )]
public static extern bool GetVersionEx( ref OSVersionInfo osvi );
三如何保證使用托管對象的平台調用成功?
如果在調用平台 invoke 後的任何位置都未引用托管對象則垃圾回收器可能將完成該托管對象這將釋放資源並使句柄無效從而導致平台invoke 調用失敗用 HandleRef 包裝句柄可保證在平台 invoke 調用完成前不對托管對象進行垃圾回收
例如下面
FileStream fs = new FileStream( atxt FileModeOpen );
StringBuilder buffer = new StringBuilder( );
int read = ;
ReadFile(fsHandle buffer out read ); //調用Win API中的ReadFile函數
由於fs是托管對象所以有可能在平台調用還未完成時候被垃圾回收站回收將文件流的句柄用HandleRef包裝後就能避免被垃圾站回收:
[ DllImport( Kerneldll )]
public static extern bool ReadFile(
HandleRef hndRef
StringBuilder buffer
int numberOfBytesToRead
out int numberOfBytesRead
ref Overlapped flag );
FileStream fs = new FileStream( HandleReftxt FileModeOpen );
HandleRef hr = new HandleRef( fs fsHandle );
StringBuilder buffer = new StringBuilder( );
int read = ;
// platform invoke will hold reference to HandleRef until call ends
ReadFile( hr buffer out read );
From:http://tw.wingwit.com/Article/program/net/201311/12553.html