1. 程式人生 > >C# API 大全

C# API 大全

C:\ProgramFiles\MicrosoftVisual 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,...);
//呼叫時與呼叫其他方法並無區別

可以使用欄位進一步說明特性,用逗號隔開,如:
[ DllImport( "kernel32", EntryPoint="GetVersionEx" )]
DllImportAttribute特性的公共欄位如下:
1、CallingConvention 指示向非託管實現傳遞方法引數時所用的 CallingConvention 值。
CallingConvention.Cdecl : 呼叫方清理堆疊。它使您能夠呼叫具有 varargs 的函式。
CallingConvention.StdCall : 被呼叫方清理堆疊。它是從託管程式碼呼叫非託管函式的預設約定。
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、數值型直接用對應的就可。(DWORD -> int , WORD -> Int16)
2、API中字串指標型別 -> .net中string
3、API中控制代碼 (dWord) -> .net中IntPtr
4、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;
}
//或者
//.net中宣告為結構
[ 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 );




我在自己最近的程式設計中注意到一個趨勢,正是這個趨勢才引出本月的專欄主題。最近,我在基於 Microsoft? .NET Framework 的應用程式中完成了大量的 Win32? Interop。我並不是要說我的應用程式充滿了自定義的 interop 程式碼,但有時我會在 .NET Framework 類庫中碰到一些次要但又繁絮、不充分的內容,通過呼叫該 Windows? API,可以快速減少這樣的麻煩。

因此我認為,.NET Framework 1.0 或 1.1 版類庫中存在任何 Windows 所沒有的功能限制都不足為怪。畢竟,32 位的 Windows(不管何種版本)是一個成熟的作業系統,為廣大客戶服務了十多年。相比之下,.NET Framework 卻是一個新事物。

隨著越來越多的開發人員將生產應用程式轉到託管程式碼,開發人員更頻繁地研究底層作業系統以圖找出一些關鍵功能顯得很自然—至少目前是如此。

值得慶幸的是,公共語言執行庫 (CLR) 的 interop 功能(稱為平臺呼叫 (P/Invoke))非常完善。在本專欄中,我將重點介紹如何實際使用 P/Invoke 來呼叫 Windows API 函式。當指 CLR 的 COM Interop 功能時,P/Invoke 當作名詞使用;當指該功能的使用時,則將其當作動詞使用。我並不打算直接介紹 COM Interop,因為它比 P/Invoke 具有更好的可訪問性,卻更加複雜,這有點自相矛盾,這使得將 COM Interop 作為專欄主題來討論不太簡明扼要。

走進 P/Invoke

首先從考察一個簡單的 P/Invoke 示例開始。讓我們看一看如何呼叫 Win32 MessageBeep 函式,它的非託管宣告如以下程式碼所示:

BOOL MessageBeep(
UINT uType // beep type
);

為了呼叫 MessageBeep,您需要在 C# 中將以下程式碼新增到一個類或結構定義中:

[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);

令人驚訝的是,只需要這段程式碼就可以使託管程式碼呼叫非託管的 MessageBeep API。它不是一個方法呼叫,而是一個外部方法定義。(另外,它接近於一個來自 C 而 C# 允許的直接埠,因此以它為起點來介紹一些概念是有幫助的。)來自託管程式碼的可能呼叫如下所示:

MessageBeep(0);

請注意,現在 MessageBeep 方法被宣告為 static。這是 P/Invoke 方法所要求的,因為在該 Windows API 中沒有一致的例項概念。接下來,還要注意該方法被標記為 extern。這是提示編譯器該方法是通過一個從 DLL 匯出的函式實現的,因此不需要提供方法體。

說到缺少方法體,您是否注意到 MessageBeep 宣告並沒有包含一個方法體?與大多數演算法由中間語言 (IL) 指令組成的託管方法不同,P/Invoke 方法只是元資料,實時 (JIT) 編譯器在執行時通過它將託管程式碼與非託管的 DLL 函式連線起來。執行這種到非託管世界的連線所需的一個重要資訊就是匯出非託管方法的 DLL 的名稱。這一資訊是由 MessageBeep 方法宣告之前的 DllImport 自定義屬性提供的。在本例中,可以看到,MessageBeep 非託管 API 是由 Windows 中的 User32.dll 匯出的。

到現在為止,關於呼叫 MessageBeep 就剩兩個話題沒有介紹,請回顧一下,呼叫的程式碼與以下所示程式碼片段非常相似:

[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);

最後這兩個話題是與資料封送處理 (data marshaling) 和從託管程式碼到非託管函式的實際方法呼叫有關的話題。呼叫非託管 MessageBeep 函式可以由找到作用域內的extern MessageBeep 宣告的任何託管程式碼執行。該呼叫類似於任何其他對靜態方法的呼叫。它與其他任何託管方法呼叫的共同之處在於帶來了資料封送處理的需要。

C# 的規則之一是它的呼叫語法只能訪問 CLR 資料型別,例如 System.UInt32 和 System.Boolean。C# 顯然不識別 Windows API 中使用的基於 C 的資料型別(例如 UINT 和 BOOL),這些型別只是 C 語言型別的型別定義而已。所以當 Windows API 函式 MessageBeep 按以下方式編寫時

BOOL MessageBeep( UINT uType )

外部方法就必須使用 CLR 型別來定義,如您在前面的程式碼片段中所看到的。需要使用與基礎 API 函式型別不同但與之相容的 CLR 型別是 P/Invoke 較難使用的一個方面。因此,在本專欄的後面我將用完整的章節來介紹資料封送處理。

樣式

在 C# 中對 Windows API 進行 P/Invoke 呼叫是很簡單的。但如果類庫拒絕使您的應用程式發出嘟聲,應該想方設法呼叫 Windows 使它進行這項工作,是嗎?

是的。但是與選擇的方法有關,而且關係甚大!通常,如果類庫提供某種途徑來實現您的意圖,則最好使用 API 而不要直接呼叫非託管程式碼,因為 CLR 型別和 Win32 之間在樣式上有很大的不同。我可以將關於這個問題的建議歸結為一句話。當您進行 P/Invoke 時,不要使應用程式邏輯直接屬於任何外部方法或其中的構件。如果您遵循這個小規則,從長遠看經常會省去許多的麻煩。

圖 1 中的程式碼顯示了我所討論的 MessageBeep 外部方法的最少附加程式碼。圖 1 中並沒有任何顯著的變化,而只是對無包裝的外部方法進行一些普通的改進,這可以使工作更加輕鬆一些。從頂部開始,您會注意到一個名為 Sound 的完整型別,它專用於 MessageBeep。如果我需要使用 Windows API 函式 PlaySound 來新增對播放波形的支援,則可以重用 Sound 型別。然而,我不會因公開單個公共靜態方法的型別而生氣。畢竟這只是應用程式程式碼而已。還應該注意到,Sound 是密封的,並定義了一個空的私有建構函式。這些只是一些細節,目的是使使用者不會錯誤地從 Sound 派生類或者建立它的例項。

圖 1 中的程式碼的下一個特徵是,P/Invoke 出現位置的實際外部方法是 Sound 的私有方法。這個方法只是由公共 MessageBeep 方法間接公開,後者接受 BeepTypes 型別的引數。這個間接的額外層是一個很關鍵的細節,它提供了以下好處。首先,應該在類庫中引入一個未來的 beep 託管方法,可以重複地通過公共 MessageBeep 方法來使用託管 API,而不必更改應用程式中的其餘程式碼。

該包裝方法的第二個好處是:當您進行 P/Invoke 呼叫時,您放棄了免受訪問衝突和其他低階破壞的權利,這通常是由 CLR 提供的。緩衝方法可以保護您的應用程式的其餘部分免受訪問衝突及類似問題的影響(即使它不做任何事而只是傳遞引數)。該緩衝方法將由 P/Invoke 呼叫引入的任何潛在的錯誤本地化。

將私有外部方法隱藏在公共包裝後面的第三同時也是最後的一個好處是,提供了向該方法新增一些最小的 CLR 樣式的機會。例如,在圖 1 中,我將 Windows API 函式返回的 Boolean 失敗轉換成更像 CLR 的異常。我還定義了一個名為 BeepTypes 的列舉型別,它的成員對應於同該 Windows API 一起使用的定義值。由於 C# 不支援定義,因此可以使用託管列舉型別來避免幻數向整個應用程式程式碼擴散。

包裝方法的最後一個好處對於簡單的 Windows API 函式(如 MessageBeep)誠然是微不足道的。但是當您開始呼叫更復雜的非託管函式時,您會發現,手動將 Windows API 樣式轉換成對 CLR 更加友好的方法所帶來的好處會越來越多。越是打算在整個應用程式中重用 interop 功能,越是應該認真地考慮包裝的設計。同時我認為,在非面向物件的靜態包裝方法中使用對 CLR 友好的引數也並非不可以。

DLL Import 屬性

現在是更深入地進行探討的時候了。在對託管程式碼進行 P/Invoke 呼叫時,DllImportAttribute 型別扮演著重要的角色。DllImportAttribute 的主要作用是給 CLR 指示哪個 DLL 匯出您想要呼叫的函式。相關 DLL 的名稱被作為一個建構函式引數傳遞給 DllImportAttribute。

如果您無法肯定哪個 DLL 定義了您要使用的 Windows API 函式,Platform SDK 文件將為您提供最好的幫助資源。在 Windows API 函式主題文字臨近結尾的位置,SDK 文件指定了 C 應用程式要使用該函式必須連結的 .lib 檔案。在幾乎所有的情況下,該 .lib 檔案具有與定義該函式的系統 DLL 檔案相同的名稱。例如,如果該函式需要 C 應用程式連結到 Kernel32.lib,則該函式就定義在 Kernel32.dll 中。您可以在 MessageBeep 中找到有關 MessageBeep 的 Platform SDK 文件主題。在該主題結尾處,您會注意到它指出庫檔案是 User32.lib;這表明 MessageBeep 是從 User32.dll 中匯出的。

可選的 DllImportAttribute 屬性

除了指出宿主 DLL 外,DllImportAttribute 還包含了一些可選屬性,其中四個特別有趣:EntryPoint、CharSet、SetLastError 和 CallingConvention。

EntryPoint 在不希望外部託管方法具有與 DLL 匯出相同的名稱的情況下,可以設定該屬性來指示匯出的 DLL 函式的入口點名稱。當您定義兩個呼叫相同非託管函式的外部方法時,這特別有用。另外,在 Windows 中還可以通過它們的序號值繫結到匯出的 DLL 函式。如果您需要這樣做,則諸如“#1”或“#129”的 EntryPoint 值指示 DLL 中非託管函式的序號值而不是函式名。

CharSet 對於字符集,並非所有版本的 Windows 都是同樣建立的。Windows 9x 系列產品缺少重要的 Unicode 支援,而 Windows NT 和 Windows CE 系列則一開始就使用 Unicode。在這些作業系統上執行的 CLR 將Unicode 用於 String 和 Char 資料的內部表示。但也不必擔心—當呼叫 Windows 9x API 函式時,CLR 會自動進行必要的轉換,將其從 Unicode轉換為 ANSI。

如果 DLL 函式不以任何方式處理文字,則可以忽略 DllImportAttribute 的 CharSet 屬性。然而,當 Char 或 String 資料是等式的一部分時,應該將 CharSet 屬性設定為 CharSet.Auto。這樣可以使 CLR 根據宿主 OS 使用適當的字符集。如果沒有顯式地設定 CharSet 屬性,則其預設值為 CharSet.Ansi。這個預設值是有缺點的,因為對於在 Windows 2000、Windows XP 和 Windows NT? 上進行的 interop 呼叫,它會消極地影響文字引數封送處理的效能。

應該顯式地選擇 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情況是:您顯式地指定了一個匯出函式,而該函式特定於這兩種 Win32 OS 中的某一種。ReadDirectoryChangesW API 函式就是這樣的一個例子,它只存在於基於 Windows NT 的作業系統中,並且只支援 Unicode;在這種情況下,您應該顯式地使用 CharSet.Unicode。

有時,Windows API 是否有字符集關係並不明顯。一種決不會有錯的確認方法是在 Platform SDK 中檢查該函式的 C 語言標頭檔案。(如果您無法肯定要看哪個標頭檔案,則可以檢視 Platform SDK 文件中列出的每個 API 函式的標頭檔案。)如果您發現該 API 函式確實定義為一個對映到以 A 或 W 結尾的函式名的巨集,則字符集與您嘗試呼叫的函式有關係。Windows API 函式的一個例子是在 WinUser.h 中宣告的 GetMessage API,您也許會驚訝地發現它有 A 和 W 兩種版本。

SetLastError 錯誤處理非常重要,但在程式設計時經常被遺忘。當您進行 P/Invoke 呼叫時,也會面臨其他的挑戰—處理託管程式碼中 Windows API 錯誤處理和異常之間的區別。我可以給您一點建議。

如果您正在使用 P/Invoke 呼叫 Windows API 函式,而對於該函式,您使用 GetLastError 來查詢擴充套件的錯誤資訊,則應該在外部方法的 DllImportAttribute 中將 SetLastError 屬性設定為 true。這適用於大多數外部方法。

這會導致 CLR 在每次呼叫外部方法之後快取由 API 函式設定的錯誤。然後,在包裝方法中,可以通過呼叫類庫的 System.Runtime.InteropServices.Marshal 型別中定義的 Marshal.GetLastWin32Error 方法來獲取快取的錯誤值。我的建議是檢查這些期望來自 API 函式的錯誤值,併為這些值引發一個可感知的異常。對於其他所有失敗情況(包括根本就沒意料到的失敗情況),則引發在 System.ComponentModel 名稱空間中定義的 Win32Exception,並將 Marshal.GetLastWin32Error 返回的值傳遞給它。如果您回頭看一下圖 1 中的程式碼,您會看到我在 extern MessageBeep 方法的公共包裝中就採用了這種方法。

CallingConvention 我將在此介紹的最後也可能是最不重要的一個 DllImportAttribute 屬性是 CallingConvention。通過此屬性,可以給 CLR 指示應該將哪種函式呼叫約定用於堆疊中的引數。CallingConvention.Winapi 的預設值是最好的選擇,它在大多數情況下都可行。然而,如果該呼叫不起作用,則可以檢查 Platform SDK 中的宣告標頭檔案,看看您呼叫的 API 函式是否是一個不符合呼叫約定標準的異常 API。

通常,本機函式(例如 Windows API 函式或 C- 執行時 DLL 函式)的呼叫約定描述瞭如何將引數推入執行緒堆疊或從執行緒堆疊中清除。大多數 Windows API 函式都是首先將函式的最後一個引數推入堆疊,然後由被呼叫的函式負責清理該堆疊。相反,許多 C-執行時 DLL 函式都被定義為按照方法引數在方法簽名中出現的順序將其推入堆疊,將堆疊清理工作交給呼叫者。

幸運的是,要讓 P/Invoke 呼叫工作只需要讓外圍裝置理解呼叫約定即可。通常,從預設值 CallingConvention.Winapi 開始是最好的選擇。然後,在 C 執行時 DLL 函式和少數函式中,可能需要將約定更改為 CallingConvention.Cdecl。

資料封送處理

資料封送處理是 P/Invoke 具有挑戰性的方面。當在託管和非託管程式碼之間傳遞資料時,CLR 遵循許多規則,很少有開發人員會經常遇到它們直至可將這些規則記住。除非您是一名類庫開發人員,否則在通常情況下沒有必要掌握其細節。為了最有效地在 CLR 上使用 P/Invoke,即使只偶爾需要 interop 的應用程式開發人員仍然應該理解資料封送處理的一些基礎知識。

在本月專欄的剩餘部分中,我將討論簡單數字和字串資料的資料封送處理。我將從最基本的數字資料封送處理開始,然後介紹簡單的指標封送處理和字串封送處理。

封送數字和邏輯標量

Windows OS 大部分是用 C 編寫的。因此,Windows API 所用到的資料型別要麼是 C 型別,要麼是通過型別定義或巨集定義重新標記的 C 型別。讓我們看看沒有指標的資料封送處理。簡單起見,首先重點討論的是數字和布林值。

當通過值向 Windows API 函式傳遞引數時,需要知道以下問題的答案:

? 資料從根本上講是整型的還是浮點型的?

? 如果資料是整型的,則它是有符號的還是無符號的?

? 如果資料是整型的,則它的位數是多少?

? 如果資料是浮點型的,則它是單精度的還是雙精度的?


有時答案很明顯,但有時卻不明顯。Windows API 以各種方式重新定義了基本的 C 資料型別。圖 2 列出了 C 和 Win32 的一些公共資料型別及其規範,以及一個具有匹配規範的公共語言執行庫型別。

通常,只要您選擇一個其規範與該引數的 Win32 型別相匹配的 CLR 型別,您的程式碼就能夠正常工作。不過也有一些特例。例如,在 Windows API 中定義的 BOOL 型別是一個有符號的 32 位整型。然而,BOOL 用於指示 Boolean 值 true 或 false。雖然您不用將 BOOL 引數作為 System.Int32 值封送,但是如果使用 System.Boolean 型別,就會獲得更合適的對映。字元型別的對映類似於 BOOL,因為有一個特定的 CLR 型別 (System.Char) 指出字元的含義。

在瞭解這些資訊之後,逐步介紹示例可能是有幫助的。依然採用 beep 主題作為例子,讓我們來試一下 Kernel32.dll 低階 Beep,它會通過計算機的揚聲器發生嘟聲。這個方法的 Platform SDK 文件可以在 Beep 中找到。本機 API 按以下方式進行記錄:

BOOL Beep(
DWORD dwFreq, // Frequency
DWORD dwDuration // Duration in milliseconds
);

在引數封送處理方面,您的工作是瞭解什麼 CLR 資料型別與 Beep API 函式所使用的 DWORD 和 BOOL 資料型別相相容。回顧一下圖 2 中的圖表,您將看到 DWORD 是一個 32 位的無符號整數值,如同 CLR 型別 System.UInt32。這意味著您可以使用 UInt32 值作為送往 Beep 的兩個引數。BOOL 返回值是一個非常有趣的情況,因為該圖表告訴我們,在 Win32 中,BOOL 是一個 32 位的有符號整數。因此,您可以使用 System.Int32 值作為來自 Beep 的返回值。然而,CLR 也定義了 System.Boolean 型別作為 Boolean 值的語義,所以應該使用它來替代。CLR 預設將 System.Boolean 值封送為 32 位的有符號整數。此處所顯示的外部方法定義是用於 Beep 的結果 P/Invoke 方法:

[DllImport("Kernel32.dll", SetLastError=true)]
static extern Boolean Beep(
UInt32 frequency, UInt32 duration);

指標引數

許多 Windows API 函式將指標作為它們的一個或多個引數。指標增加了封送資料的複雜性,因為它們增加了一個間接層。如果沒有指標,您可以通過值線上程堆疊中傳遞資料。有了指標,則可以通過引用傳遞資料,方法是將該資料的記憶體地址推入執行緒堆疊中。然後,函式通過記憶體地址間接訪問資料。使用託管程式碼表示此附加間接層的方式有多種。

在 C# 中,如果將方法引數定義為 ref 或 out,則資料通過引用而不是通過值傳遞。即使您沒有使用 Interop 也是這樣,但只是從一個託管方法呼叫到另一個託管方法。例如,如果通過 ref 傳遞 System.Int32 引數,則線上程堆疊中傳遞的是該資料的地址,而不是整數值本身。下面是一個定義為通過引用接收整數值的方法的示例:

void FlipInt32(ref Int32 num){
num = -num;
}

這裡,FlipInt32 方法獲取一個 Int32 值的地址、訪問資料、對它求反,然後將求反過的值賦給原始變數。在以下程式碼中,FlipInt32 方法會將呼叫程式的變數 x 的值從 10 更改為 -10:

Int32 x = 10;
FlipInt32(ref x);

在託管程式碼中可以重用這種能力,將指標傳遞給非託管程式碼。例如,FileEncryptionStatus API 函式以 32 位無符號位掩碼的形式返回檔案加密狀態。該 API 按以下所示方式進行記錄:

BOOL FileEncryptionStatus(
LPCTSTR lpFileName, // file name
LPDWORD lpStatus // encryption status
);

請注意,該函式並不使用它的返回值返回狀態,而是返回一個 Boolean 值,指示呼叫是否成功。在成功的情況下,實際的狀態值是通過第二個引數返回的。它的工作方式是呼叫程式向該函式傳遞指向一個 DWORD 變數的指標,而該 API 函式用狀態值填充指向的記憶體位置。以下程式碼片段顯示了一個呼叫非託管 FileEncryptionStatus 函式的可能外部方法定義:

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename,
out UInt32 status);

該定義使用 out 關鍵字來為 UInt32 狀態值指示 by-ref 引數。這裡我也可以選擇 ref 關鍵字,實際上在執行時會產生相同的機器碼。out 關鍵字只是一個 by-ref 引數的規範,它向 C# 編譯器指示所傳遞的資料只在被呼叫的函式外部傳遞。相反,如果使用 ref 關鍵字,則編譯器會假定資料可以在被呼叫的函式的內部和外部傳遞。

託管程式碼中 out 和 ref 引數的另一個很好的方面是,地址作為 by-ref 引數傳遞的變數可以是執行緒堆疊中的一個本地變數、一個類或結構的元素,也可以是具有合適資料型別的陣列中的一個元素引用。呼叫程式的這種靈活性使得 by-ref 引數成為封送緩衝區指標以及單數值指標的一個很好的起點。只有在我發現 ref 或 out 引數不符合我的需要的情況下,我才會考慮將指標封送為更復雜的 CLR 型別(例如類或陣列物件)。

如果您不熟悉 C 語法或者呼叫 Windows API 函式,有時很難知道一個方法引數是否需要指標。一個常見的指示符是看引數型別是否是以字母 P 或 LP 開頭的,例如 LPDWORD 或 PINT。在這兩個例子中,LP 和 P 指示引數是一個指標,而它們指向的資料型別分別為 DWORD 或 INT。然而,在有些情況下,可以直接使用 C 語言語法中的星號 (*) 將 API 函式定義為指標。以下程式碼片段展示了這方面的示例:

void TakesAPointer(DWORD* pNum);

可以看到,上述函式的唯一一個引數是指向 DWORD 變數的指標。

當通過 P/Invoke 封送指標時,ref 和 out 只用於託管程式碼中的值型別。當一個引數的 CLR 型別使用 struct 關鍵字定義時,可以認為該引數是一個值型別。Out 和 ref 用於封送指向這些資料型別的指標,因為通常值型別變數是物件或資料,而在託管程式碼中並沒有對值型別的引用。相反,當封送引用型別物件時,並不需要 ref 和 out 關鍵字,因為變數已經是物件的引用了。

如果您對引用型別和值型別之間的差別不是很熟悉,請查閱 2000 年 12 月發行的 MSDN? Magazine,在 .NET 專欄的主題中可以找到更多資訊。大多數 CLR 型別都是引用型別;然而,除了 System.String 和 System.Object,所有的基元型別(例如 System.Int32 和 System.Boolean)都是值型別。

封送不透明 (Opaque) 指標:一種特殊情況

有時在 Windows API 中,方法傳遞或返回的指標是不透明的,這意味著該指標值從技術角度講是一個指標,但程式碼卻不直接使用它。相反,程式碼將該指標返回給 Windows 以便隨後進行重用。

一個非常常見的例子就是控制代碼的概念。在 Windows 中,內部資料結構(從檔案到螢幕上的按鈕)在應用程式程式碼中都表示為控制代碼。控制代碼其實就是不透明的指標或有著指標寬度的數值,應用程式用它來表示內部的 OS 構造。

少數情況下,API 函式也將不透明指標定義為 PVOID 或 LPVOID 型別。在 Windows API 的定義中,這些型別意思就是說該指標沒有型別。

當一個不透明指標返回給您的應用程式(或者您的應用程式期望得到一個不透明指標)時,您應該將引數或返回值封送為 CLR 中的一種特殊型別— System.IntPtr。當您使用 IntPtr 型別時,通常不使用 out 或 ref 引數,因為 IntPtr 意為直接持有指標。不過,如果您將一個指標封送為一個指標,則對 IntPtr 使用 by-ref 引數是合適的。

在 CLR 型別系統中,System.IntPtr 型別有一個特殊的屬性。不像系統中的其他基型別,IntPtr 並沒有固定的大小。相反,它在執行時的大小是依底層作業系統的正常指標大小而定的。這意味著在 32 位的 Windows 中,IntPtr 變數的寬度是 32 位的,而在 64 位的 Windows 中,實時編譯器編譯的程式碼會將 IntPtr 值看作 64 位的值。當在託管程式碼和非託管程式碼之間封送不透明指標時,這種自動調節大小的特點十分有用。

請記住,任何返回或接受控制代碼的 API 函式其實操作的就是不透明指標。您的程式碼應該將 Windows 中的控制代碼封送成 System.IntPtr 值。

您可以在託管程式碼中將 IntPtr 值強制轉換為 32 位或 64 位的整數值,或將後者強制轉換為前者。然而,當使用 Windows API 函式時,因為指標應是不透明的,所以除了儲存和傳遞給外部方法外,不能將它們另做它用。這種“只限儲存和傳遞”規則的兩個特例是當您需要向外部方法傳遞 null 指標值和需要比較 IntPtr 值與 null 值的情況。為了做到這一點,您不能將零強制轉換為 System.IntPtr,而應該在 IntPtr 型別上使用 Int32.Zero 靜態公共欄位,以便獲得用於比較或賦值的 null 值。

封送文字

在程式設計時經常要對文字資料進行處理。文字為 interop 製造了一些麻煩,這有兩個原因。首先,底層作業系統可能使用 Unicode 來表示字串,也可能使用 ANSI。在極少數情況下,例如 MultiByteToWideChar API 函式的兩個引數在字符集上是不一致的。

第二個原因是,當需要進行 P/Invoke 時,要處理文字還需要特別瞭解到 C 和 CLR 處理文字的方式是不同的。在 C 中,字串實際上只是一個字元值陣列,通常以 null 作為結束符。大多數 Windows API 函式是按照以下條件處理字串的:對於 ANSI,將其作為字元值陣列;對於 Unicode,將其作為寬字元值陣列。

幸運的是,CLR 被設計得相當靈活,當封送文字時問題得以輕鬆解決,而不用在意 Windows API 函式期望從您的應用程式得到的是什麼。這裡是一些需要記住的主要考慮事項:

? 是您的應用程式向 API 函式傳遞文字資料,還是 API 函式向您的應用程式返回字串資料?或者二者兼有?

? 您的外部方法應該使用什麼託管型別?

? API 函式期望得到的是什麼格式的非託管字串?


我們首先解答最後一個問題。大多數 Windows API 函式都帶有 LPTSTR 或 LPCTSTR 值。(從函式角度看)它們分別是可修改和不可修改的緩衝區,包含以 null 結束的字元陣列。“C”代表常數,意味著使用該引數資訊不會傳遞到函式外部。LPTSTR 中的“T”表明該引數可以是 Unicode 或 ANSI,取決於您選擇的字符集和底層作業系統的字符集。因為在 Windows API 中大多數字符串引數都是這兩種型別之一,所以只要在 DllImportAttribute 中選擇 CharSet.Auto,CLR 就按預設的方式工作。

然而,有些 API 函式或自定義的 DLL 函式採用不同的方式表示字串。如果您要用到一個這樣的函式,就可以採用 MarshalAsAttribute 修飾外部方法的字串引數,並指明一種不同於預設 LPTSTR 的字串格式。有關 MarshalAsAttribute 的更多資訊,請參閱位於 MarshalAsAttribute Class 的 Platform SDK 文件主題。

現在讓我們看一下字串資訊在您的程式碼和非託管函式之間傳遞的方向。有兩種方式可以知道處理字串時資訊的傳遞方向。第一個也是最可靠的一個方法就是首先理解引數的用途。例如,您正呼叫一個引數,它的名稱類似 CreateMutex 並帶有一個字串,則可以想像該字串資訊是從應用程式向 API 函式傳遞的。同時,如果您呼叫 GetUserName,則該函式的名稱表明字串資訊是從該函式向您的應用程式傳遞的。

除了這種比較合理的方法外,第二種查詢資訊傳遞方向的方式就是查詢 API 引數型別中的字母“C”。例如,GetUserName API 函式的第一個引數被定義為 LPTSTR 型別,它代表一個指向 Unicode 或 ANSI 字串緩衝區的長指標。但是 CreateMutex 的名稱引數被型別化為 LTCTSTR。請注意,這裡的型別定義是一樣的,但增加一個字母“C”來表明緩衝區為常數,API 函式不能寫入。

一旦明確了文字引數是隻用作輸入還是用作輸入/輸出,就可以確定使用哪種 CLR 型別作為引數型別。這裡有一些規則。如果字串引數只用作輸入,則使用 System.String 型別。在託管程式碼中,字串是不變的,適合用於不會被本機 API 函式更改的緩衝區。

如果字串引數可以用作輸入和/或輸出,則使用 System.StringBuilder 型別。StringBuilder 型別是一個很有用的類庫型別,它可以幫助您有效地構建字串,也正好可以將緩衝區傳遞給本機函式,由本機函式為您填充字串資料。一旦函式呼叫返回,您只需要呼叫 StringBuilder 物件的 ToString 就可以得到一個 String 物件。

GetShortPathName API 函式能很好地用於顯示什麼時候使用 String、什麼時候使用 StringBuilder,因為它只帶有三個引數:一個輸入字串、一個輸出字串和一個指明輸出緩衝區的字元長度的引數。

圖 3 所示為加註釋的非託管 GetShortPathName 函式文件,它同時指出了輸入和輸出字串引數。它引出了託管的外部方法定義,也如圖 3 所示。請注意第一個引數被封送為 System.String,因為它是一個只用作輸入的引數。第二個引數代表一個輸出緩衝區,它使用了 System.StringBuilder。

小結

本月專欄所介紹的 P/Invoke 功能足夠呼叫 Windows 中的許多 API 函式。然而,如果您大量用到 interop,則會最終發現自己封送了很複雜的資料結構,甚至可能需要在託管程式碼中通過指標直接訪問記憶體。實際上,本機程式碼中的 interop 可以是一個將細節和低階位元藏在裡面的真正的潘多拉盒子。CLR、C# 和託管 C++ 提供了許多有用的功能;也許以後我會在本專欄介紹高階的 P/Invoke 話題。

同時,只要您覺得 .NET Framework 類庫無法播放您的聲音或者為您執行其他一些功能,您可以知道如何向原始而優秀的 Windows API 尋求一些幫助。

API(應用程式設計介面)是程式與處理器介面的命令集。最常用的就是在外部呼叫微軟WINDOWS內部的程序。WINDOWS API包括成千的你可以使用的函式、結構、常量。這些函式是用C語言寫的,在使用他們之前,你必須宣告。定義Dll的程序將相當的複雜,甚至比VB還複雜。你可以使用API Viewer工具得到API函式的宣告,但是必須注意的是,它的引數型別跟C#的不一樣。
大部分的高階語言都支援API,微軟函式類庫(MFC)封裝了大部分的Win32 API。ODBC API對提高資料庫的操作速度大有好處。使用API,可以請求更底層的系統服務。API從簡單的對話方塊到複雜的加密運算都提供支援。開發者應該知道如何在他們程式中使用API
API有許多型別,(針對不同的作業系統、處理器…………)
OS specific API:
作業系統特有API:
每種作業系統都有一套公用API和專有API。比如:Windows NT 支援MS-DOS, Win16, Win32, POSIX (行動式作業系統介面),OS/2 console API ;同時Windows 95 supports MS-DOS, Win16 和Win32 API。
Win16 和 Win32 API:
WIN16 是基於16位的處理器,並使用16位的值,它是一個獨立的平臺。比如:你可以執行TSR 程式在MS-DOS環境下。
WIN32 是基於32位的處理器,並使用32位的值。他可用於任何作業系統,它的使用範圍更廣。
Win32 API has 32 prefix after the library name e.g. KERNEL32, USER32 etc?
Win32 API的DLL一般都具有32的字尾,比如:KERNEL32, USER32等。
所有的API都在下面3個DLL中實現的。
Kernel
User
GDI
1. KERNEL
它的庫名是:KERNEL32.DLL,它是作業系統管理的API集
Process loading. 載入程序
Context switching.
File I/O. 檔案操作
Memory management. 記憶體管理
比如:GlobalMemoryStatus 函式獲得目前系統物理虛擬記憶體的使用資訊。
2. USER
在WIN32下,它的庫名是 USER32.DLL
This allows managing the entire user interfaces such as
它管理全部的使用者介面,比如:
Windows 視窗
Menus 選單
Dialog Boxes 對話方塊
Icons etc., 圖示等
比如:DrawIcon 畫一個圖示在指定的裝置上。
3. GDI (Graphical Device Interface)
這個DLL是GDI32.dll,它負責影象的輸出,使用GDI繪出視窗,選單,對話方塊
It can create Graphical Output. 輸出影象
比如:CreateBitmap 函式建立一個指定寬度、高度和顏色格式的點陣圖。
C#中API的工具對初學者是相當不錯的。在C#使用中使用API之前,你應該先知道C#中如何使用結構、型別轉換,安全與不安全程式碼等。
使用複雜的api之前,我們先用一個簡單的MessageBox API作為列子。開啟一個C#工程,增加一個按鈕,在按鈕的點選事件中,我們將顯示一個資訊框。
增加使用外部庫的名稱空間:
using System.Runtime.InteropServices;
下面宣告API
[DllImport("User32.dll")]
DllImport屬性用來指定包含外部方法的動態連線庫的位置。 "User32.dll"指出了庫名,static 指明它不屬於特定的物件。extern 指明是一個外部的方法。帶有DllImport 屬性的方法必須帶有修飾符extern 。
MessageBox 是一個漢數名,帶四個引數返回一個int型值。
許多API使用結構來傳遞、返回引數,這樣可以減少複雜度。它也允許使用象MessageBox 函式那樣,使用固定的引數。

在按鈕的點選事件中增加下面程式碼:
protected void button1_Click (object sender, System.EventArgs e)
{
MessageBox (0,"API Message Box","API Demo",0);
}
編譯並執行程式,點選按鈕以後,你就可以看到一個由API呼叫的資訊框。
Using structure 使用結構
API中經常使用複雜的結構。不過一旦你明白了他們,將是很簡單的。
In next example we will use GetSystemInfo API which returns information about the current system.
下面的列子,我們用GetSystemInfo API得到當前系統的資訊。
第一步:增加一個C#視窗,並在上面增加一個按鈕,在視窗內碼表增加一個名稱空間:
using System.Runtime.InteropServices;
宣告 GetSystemInfo 的引數結構:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO {
public uint dwOemId;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}
宣告API函式:
[DllImport("kernel32")]
static extern void GetSystemInfo(ref SYSTEM_INFO pSI);
ref是一個標誌參量傳遞形式的關鍵字,它使傳入傳出的變數指向同一個變數(傳址傳遞)
在按鈕點選事件中增加下面的程式碼,
protected void button1_Click (object sender, System.EventArgs e)
{
try
{
SYSTEM_INFO pSI = new SYSTEM_INFO();
GetSystemInfo(ref pSI);
Once you retrieve the structure perform operations on required parameter
比如:
listBox1.Items.Insert(0,pSI.dwActiveProcessorMask.ToString());
}
catch(Exception er)
{
MessageBox.Show (er.Message);
}
}


用Visual C#呼叫Windows API函式

北京機械工業學院研00級(100085)冉林倉

Api函式是構築Windws應用程式的基石,每一種Windows應用程式開發工具,它提供的底層函式都間接或直接地呼叫了Windows API函式,同時為了實現功能擴充套件,一般也都提供了呼叫WindowsAPI函式的介面,也就是說具備呼叫動態連線庫的能力。Visual C#和其它開發工具一樣也能夠呼叫動態連結庫的API函式。.NET框架本身提供了這樣一種服務,允許受管轄的程式碼呼叫動態連結庫中實現的非受管轄函式,包括作業系統提供的Windows API函式。它能夠定位和呼叫輸出函式,根據需要,組織其各個引數(整型、字串型別、陣列、和結構等等)跨越互操作邊界。

下面以C#為例簡單介紹呼叫API的基本過程:
動態連結庫函式的宣告
 動態連結庫函式使用前必須宣告,相對於VB,C#函式宣告顯得更加羅嗦,前者通過 Api Viewer貼上以後,可以直接使用,而後者則需要對引數作些額外的變化工作。

 動態連結庫函式宣告部分一般由下列兩部分組成,一是函式名或索引號,二是動態連結庫的檔名。
 譬如,你想呼叫User32.DLL中的MessageBox函式,我們必須指明函式的名字MessageBoxA或MessageBoxW,以及庫名字User32.dll,我們知道Win32 API對每一個涉及字串和字元的函式一般都存在兩個版本,單位元組字元的ANSI版本和雙位元組字元的UNICODE版本。

 下面是一個呼叫API函式的例子:
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);

 其中入口點EntryPoint標識函式在動態連結庫的入口位置,在一個受管轄的工程中,目標函式的原始名字和序號入口點不僅標識一個跨越互操作界限的函式。而且,你還可以把這個入口點對映為一個不同的名字,也就是對函式進行重新命名。重新命名可以給呼叫函式帶來種種便利,通過重新命名,一方面我們不用為函式的大小寫傷透腦筋,同時它也可以保證與已有的命名規則保持一致,允許帶有不同引數型別的函式共存,更重要的是它簡化了對ANSI和Unicode版本的呼叫。CharSet用於標識函式呼叫所採用的是Unicode或是ANSI版本,ExactSpelling=false將告訴編譯器,讓編譯器決定使用Unicode或者是Ansi版本。其它的引數請參考MSDN線上幫助.

 在C#中,你可以在EntryPoint域通過名字和序號宣告一個動態連結庫函式,如果在方法定義中使用的函式名與DLL入口點相同,你不需要在EntryPoint域顯示宣告函式。否則,你必須使用下列屬性格式指示一個名字和序號。

[DllImport("dllname", EntryPoint="Functionname")]
[DllImport("dllname", EntryPoint="#123")]
值得注意的是,你必須在數字序號前加“#”
下面是一個用MsgBox替換MessageBox名字的例子:
[C#]
using System.Runtime.InteropServices;

public class Win32 {
[DllImport("user32.dll", EntryPoint="MessageBox")]
public static extern int MsgBox(int hWnd, String text, String caption, uint type);
}
許多受管轄的動態連結庫函式期望你能夠傳遞一個複雜的引數型別給函式,譬如一個使用者定義的結構型別成員或者受管轄程式碼定義的一個類成員,這時你必須提供額外的資訊格式化這個型別,以保持引數原有的佈局和對齊。

C#提供了一個StructLayoutAttribute類,通過它你可以定義自己的格式化型別,在受管轄程式碼中,格式化型別是一個用StructLayoutAttribute說明的結構或類成員,通過它能夠保證其內部成員預期的佈局資訊。佈局的選項共有三種:

佈局選項
描述
LayoutKind.Automatic
為了提高效率允許執行態對型別成員重新排序。
注意:永遠不要使用這個選項來呼叫不受管轄的動態連結庫函式。
LayoutKind.Explicit
對每個域按照FieldOffset屬性對型別成員排序
LayoutKind.Sequential
對出現在受管轄型別定義地方的不受管轄記憶體中的型別成員進行排序。
傳遞結構成員
下面的例子說明如何在受管轄程式碼中定義一個點和矩形型別,並作為一個引數傳遞給User32.dll庫中的PtInRect函式,
函式的不受管轄原型宣告如下:
BOOL PtInRect(const RECT *lprc, POINT pt);
注意你必須通過引用傳遞Rect結構引數,因為函式需要一個Rect的結構指標。
[C#]
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}

[StructLayout(LayoutKind.Explicit]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}

class Win32API {
[DllImport("User32.dll")]
public static extern Bool PtInRect(ref Rect r, Point p);
}
類似你可以呼叫GetSystemInfo函式獲得系統資訊:
? using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO {
public uint dwOemId;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}




[DllImport("kernel32")]
static extern void GetSystemInfo(ref SYSTEM_INFO pSI);

SYSTEM_INFO pSI = new SYSTEM_INFO();
GetSystemInfo(ref pSI);

類成員的傳遞
同樣只要類具有一個固定的類成員佈局,你也可以傳遞一個類成員給一個不受管轄的動態連結庫函式,下面的例子主要說明如何傳遞一個sequential順序定義的MySystemTime類給User32.dll的GetSystemTime函式, 函式用C/C++呼叫規範如下:

void GetSystemTime(SYSTEMTIME* SystemTime);
不像傳值型別,類總是通過引用傳遞引數.
[C#]
[StructLayout(LayoutKind.Sequential)]
public class MySystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
class Win32API {
[DllImport("User32.dll")]
public static extern void GetSystemTime(MySystemTime st);
}
回撥函式的傳遞:
從受管轄的程式碼中呼叫大多數動態連結庫函式,你只需建立一個受管轄的函式定義,然後呼叫它即可,這個過程非常直接。
如果一個動態連結庫函式需要一個函式指標作為引數,你還需要做以下幾步:
首先,你必須參考有關這個函式的文件,確定這個函式是否需要一個回撥;第二,你必須在受管轄程式碼中建立一個回撥函式;最後,你可以把指向這個函式的指標作為一個引數創遞給DLL函式,.

回撥函式及其實現:
回撥函式經常用在任務需要重複執行的場合,譬如用於列舉函式,譬如Win32 API 中的EnumFontFamilies(字型列舉), EnumPrinters(印表機), EnumWindows (視窗列舉)函式. 下面以視窗列舉為例,談談如何通過呼叫EnumWindow 函式遍歷系統中存在的所有視窗

分下面幾個步驟:
1. 在實現呼叫前先參考函式的宣告
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)
顯然這個函式需要一個回撥函式地址作為引數.
2. 建立一個受管轄的回撥函式,這個例子宣告為代表型別(delegate),也就是我們所說的回撥,它帶有兩個引數hwnd和lparam,第一個引數是一個視窗控制代碼,第二個引數由應用程式定義,兩個引數均為整形。

  當這個回撥函式返回一個非零值時,標示執行成功,零則暗示失敗,這個例子總是返回True值,以便持續列舉。
3. 最後建立以代表物件(delegate),並把它作為一個引數傳遞給EnumWindows 函式,平臺會自動地把代表轉化成函式能夠識別的回撥格式。

[C#]
using System;
using System.Runtime.InteropServices;

public delegate bool CallBack(int hwnd, int lParam);

public class EnumReportApp {

[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);

public static void Main()
{
CallBack myCallBack = new CallBack(EnumReportApp.Report);
EnumWindows(myCallBack, 0);
}

public static bool Report(int hwnd, int lParam) {
Console.Write("視窗控制代碼為");
Console.WriteLine(hwnd);
return true;
}
}

指標型別引數傳遞:
 在Windows API函式呼叫時,大部分函式採用指標傳遞引數,對一個結構變數指標,我們除了使用上面的類和結構方法傳遞引數之外,我們有時還可以採用陣列傳遞引數。

 下面這個函式通過呼叫GetUserName獲得使用者名稱
BOOL GetUserName(
LPTSTR lpBuffer, // 使用者名稱緩衝區
LPDWORD nSize // 存放緩衝區大小的地址指標
);
 
[DllImport("Advapi32.dll",
EntryPoint="GetComputerName",
ExactSpelling=false,
SetLastError=true)]
static extern bool GetComputerName (
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,
  [MarshalAs(UnmanagedType.LPArray)] Int32[] nSize );
 這個函式接受兩個引數,char * 和int *,因為你必須分配一個字串緩衝區以接受字串指標,你可以使用String類代替這個引數型別,當然你還可以宣告一個位元組陣列傳遞ANSI字串,同樣你也可以宣告一個只有一個元素的長整型陣列,使用陣列名作為第二個引數。上面的函式可以呼叫如下:

byte[] str=new byte[20];
Int32[] len=new Int32[1];
len[0]=20;
GetComputerName (str,len);
MessageBox.Show(System.Text.Encoding.ASCII.GetString(str));
 最後需要提醒的是,每一種方法使用前必須在檔案頭加上:
 using System.Runtime.InteropServices;

相關推薦

C# API 大全

C:\ProgramFiles\MicrosoftVisual Studio .NET\ FrameworkSDK\Samples\ Technologies\ Interop\PlatformInvoke\ WinAPIs\CS目錄下有大量的呼叫API的例子。 一、呼叫格

c# API大全(三)

c# API大全(3)  2009-11-15 16:09  API(應用程式設計介面)是程式與處理器介面的命令集。最常用的就是在外部呼叫微軟WINDOWS內部的程序。WINDOWS API包括成千的你可以使用的函式、結構、常量。這些函式是用C語言寫的,在使用他們之前,你必

MySQL C API函式大全

Mysql C API提供函式主要分為如下五種: 1. 控制類函式 2. 資訊獲取類函式 3. 行列類操作函式 4. 執行緒類操作函式 5. 出錯處理類函式 本文歸納了C API可使用的函式,具體用法,參考文末地址中對應條目,均有詳細說明。 函式 描述 mysql_affected_rows

c# api接口 文件處理

request () sys text ask tel 獲取文件 substr tap api接口 上傳文件 /// <summary> /// POST api/FileManager/PostFormData //

WebDriver API 大全

driver timeout med forward input 進程 元素操作 上傳 radi 訪問某網頁地址:driver.get(url) 或 driver.navigate().to(url) 訪問上一個訪問的網頁(模擬單擊瀏覽器的後退按鈕)driver.

zookeeper的c API 單線程與多線程問題 cli_st和cli_mt

.lib libs tool .com tag gnu 編譯選項 watch || 同樣的程序,在centos和ubuntu上都沒有問題,在solaris上問題卻多多,據說是solaris管理更加嚴格。 zookeeper_init方法,在傳入一個錯誤的host也能初始

C++ 資源大全中文版

max emp json數據 屬於 online ruby irrlicht cgi orb 標準庫 C++標準庫,包括了STL容器,算法和函數等。 C++ Standard Library:是一系列類和函數的集合,使用核心語言編寫,也是C++ISO自身標準的一

c#】RabbitMQ學習文檔(七)C# API

[] 檢索 並發 IT 重載 線上 request 基本屬性 and 今天這篇博文是我翻譯的RabbitMQ的最後一篇文章了,介紹一下RabbitMQ的C#開發的接口。好了,言歸正傳吧。 Net/C# 客戶端 API簡介 主要的命名空間,接口和類

C# API 獲取系統DPI縮放倍數跟分辨率大小

primary styles ice https orz IE blog ace user 原文:C# API 獲取系統DPI縮放倍數跟分辨率大小 using Syst

C#API解決自定義請求頭下的跨域問題

contains sha allow oba webapi with cat ride bapi 解決方法一: public class CrosHandler : DelegatingHandler { private const str

Python C API 使用詳解(二)

error 獲取 應該 tro pytho 都是 鍵值 字符 tin 簡介 介紹Python C API中的列表、元組、字典的使用,詳細的進行了API中方法的介紹。 Python List API List API 簡單介紹 int PyList_Check(PyObjec

【原創】C# API 未能創建 SSL/TLS 安全通道 問題解決

分享圖片 proto 執行 manager ima com 添加 poi ssl 在調用執行API之前添加以下代碼就行了 System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls1

C++ API實現創建桌面快捷方式

路徑 clu oca item obj print 句柄 回收 程序 1 #include<windows.h> 2 #include <string> 3 #include <shellapi.h> 4 #includ

lua原始碼學習(一)lua的c api外圍實現

工作後,整個人已經比較鬆懈了。儘管一直在看lua的原始碼。可是一直是比較零碎的時間,沒有系統的整理,所以還是收穫不多。由於近期工作也不是非常忙了,就想整理下lua的原始碼學習的筆記。加深下印象,並分享給大家。 先說下這系列bolg我會每週更新2-3篇,沒有順序的 這些文章

lua源代碼學習(一)lua的c api外圍實現

在操作 時間 struct ack cti 壓棧 c api field stat 工作後,整個人已經比較松懈了。盡管一直在看lua的源代碼。可是一直是比較零碎的時間,沒有系統的整理

使用TensorFlow C++ API構建線上預測服務

使用TensorFlow C++ API構建線上預測服務 執行環境:CentOS,TF-1.10 除了本機的tensorflow之外,仍需要安裝下面的tf。 原始碼安裝後,看到tensorflow/contrib/makefile/gen/lib/libtensorflow-co

Python與C之間的相互呼叫(Python C API及Python ctypes庫)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

C++ 資源大全

關於 C++ 框架、庫和資源的一些彙總列表,內容包括:標準庫、Web應用框架、人工智慧、資料庫、圖片處理、機器學習、日誌、程式碼分析等。 標準庫 C++標準庫,包括了STL容器,演算法和函式等。 C++ Standard Library:是一系列類和函式的集合,使用核心語言編寫,也是C+

Python C API 使用心得

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

C API方式串列埠讀寫

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!