摘要:[API] 調用 Win32 API DLL (二)
[ 透過 Platform Invoke 呼叫參數為類別或結構型態的 Win32 API ]
有個時候透過 P/Invoke 所呼叫的 Win32 API 傳入參數並不是很單純,因此在 C# Managed 程式碼這邊必須審慎的考量原
Win32 API 與 C# 程式的對應型態轉換,尤其需注意『指標』及 『結構』 型態的參數。
以下會呼叫 Win32 API 的 GetSystemInfo 函式。
Win32 API GetSystemInfo 函式原型宣告
void GetSystemInfo( LPSYSTEM_INFO lpSystemInfo ); |
GetSystemInfo:指向 SYSTEM_INFO 結構的指標,用以接受系統背景資訊。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
由以上資訊,我們知道在呼叫時必須傳入指向 SYSTEM_INFO 結構的指標,如果是使用 C/C++ 語言,只需引入相關的標頭檔
並建立一個 SYSTEM_INFO 結構實體即可。但是在 C# 的環境中,並不能直接取得 SYSTEM_INFO 結構的定義,因此必須用
Managed 程式碼中實做 C# 板本的 SYSTEM_INFO 結構。
SYSTEM_INFO 結構定義
typedef struct _SYSTEM_INFO { union{ DWORD dwOemId;
struct { WORD wProcessorArchitecture; WORD sResserved; }; }; DWORD dwPageSize; LPVOID lpMinimumApplicationAddress; LPVOID lpMaxmumAppicationAddress; DOWRD_PTR dwActiveProcessorMask; DWORD dwNumberOfProcessors; DWORD dwAllocationGranularity; WORD wProcessorLevel; WORD wProcessorRevision; } SYSTEM_INFO; |
以下程式碼便是以 P/Invoke 方式呼叫參數為結構型態的 GetSystemInfo,取出系統背景資訊後秀出本台電腦的 CPU 數量。
using System; using System.Runtime.InteropServices; namespace ExampleSolution {
class Example222 { static void Main(string[] args) {
SYSTEM_INFO si = new SYSTEM_INFO();
GetSystemInfo(ref si);
Console.WriteLine("本台電腦處理器數量:{0}顆。", si.dwNumberOfProcessors.ToString()); } [DllImport("kernel32.dll")] //使用 kernel32 組件。 public static extern void GetSystemInfo(ref SYSTEM_INFO si); [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO //重新定義屬於 C# 的 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; } } } |
輸出結果:
本台電腦處理器數量:1 顆。
在重新定義 SYSTEM_INFO 結構時,除了需要參考 Win32 環境的原始 SYSTEM_INFO 結構成員型態,並將其全部轉換到
C# 對應之型態外,也需加入 [StruckLayout(LayoutKind.Sequential)] 的屬性說明。StructLayout 屬性的功能在於設定類別
或結構在記憶體中的排列方式,通常,CLR 會控制 Managed 記憶體中類別或結構之資料欄位的實際配置,如果類別或
結構需要以某種方式排列,便可以使用 StructLayout 屬性加以設定。在預設的情況下,編譯器會依據類別或結構中各
成員的大小做最佳化排列,但是若該類別或結構用於 P/Invoke 情況以當作參數轉呼叫 DLL 動態連接函式庫時,則請務必
設定 StructLayout 屬性,避免影響轉呼叫外部 UnManaged 程式碼時因類別或結構記憶體位置錯置而導致非預期的結果。
StructLayout 屬性格式
StructLayout( LayoutKind [, CharSet][, Pack][, Size] )
Pack、Size 及 CharSet 為選擇性設定欄位
表: StructLayout 屬性的各欄位說明
LayoutKind | 列舉型別,在匯出至 Unmanaged 程式碼時控制物件的配置。共有三個列舉成員:
CLR 預設值,若類別或結構未標註 StructLayout 屬性則自動套用此列舉成員定義。執行階段 會自動選擇 Unmanaged 記憶體中物件成員的適當配置。使用這個列舉成員定義的物件不可 以在 Managed 程式碼以外公開。嘗試這麼做會產生例外狀況。
Unmanaged 記憶體中每個物件成員的精確位置是被明確的控制。每個成員必須使用 FieldOffsetAttribute ,表示該欄位在型別中的記憶體相對位置。
物件的成員是依序配置的,其順序即是將他們匯出至 Unmanaged 記憶體時所出現的順序。成員是根據 StructLayoutAttribute.Pack 中所指定的封裝來配置。 |
CharSet | 如果 CharSet 欄位設定為 CharSet.Unicode ,所有字串引數在傳遞到 Unmanaged 實做之前都會轉換為 Unicode 字元(LPWSTR)。如果欄位設定為 CharSet.Ansi,則字串會轉換為ANSI 字串 (LPSTR) 。 如果 CharSet.Ansi 欄位設定為 CharSet.Auto ,轉換則必須依據平台(WInNT、Win2000、WinXP 和 Win2003 系列產品中為 Unicode;在 Win98 和 WinME 上為 ANSI)。 |
Pack | 這個欄位指示當指定 LayoutKind.Sequential 值時應該使用的封裝大小。 Pack 的值必須為:0、1、2、4、8、16、32、64 或128。 其值為 0 時,表示將封裝對齊設為目前平台的預設值。 |
Size | 必須大於或等於所有成員的總和。 這個欄位只要針對編譯器撰寫人員,以指定類別或結構的總合大小(以位元組為單位),要擴充結構佔領的記憶體以便進行直接、Unmanaged 的存取時也很有用。例如,使用未在中繼資料中直接表式的等位時,可以使用這個欄位。 |
StructLayout 屬性使用範例
使用 LayoutKind.Sequectial 列舉成員定義,受定義的類別或結構之成員會依據定義的順序在記憶體中排列。
以下面的例子中,便會以先 balance 後 loan 的順序排列。
[StructLayout(LayouKind.Sequential)] public struct Account { public int balance; public int loan; } |
使用 LayoutKind.Explicit 列舉成員定義,每個成員必須使用 FieldoffsetAttribute,表示該欄位在型別中的記憶體相對位置,
如此程式設計師可以精確的掌控所有欄位於記憶體中的儲存順序。
[StructLayout(LayouKind.Explicit)] public struct Account { [FieldOffset(0)] public int balance; [FieldOffset(4)] public int loan; } |
API查詢:
1.Windows API Reference for C#, VB.NET and VB6
http://www.webtropy.com/articles/Win32-API-DllImport-art9.asp
2.PInvoke.NET
3.Microsoft Win32 to Microsoft .NET Framework API Map
http://msdn.microsoft.com/en-us/library/aa302340.aspx
原文出處:張宇超研究室
三小俠 小弟獻醜,歡迎指教