[API] 調用 Win32 API DLL (二)

  • 8948
  • 0
  • 2009-11-22

摘要:[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 程式碼時控制物件的配置。共有三個列舉成員:

  • Auto

             CLR 預設值,若類別或結構未標註 StructLayout 屬性則自動套用此列舉成員定義。執行階段 

             會自動選擇 Unmanaged 記憶體中物件成員的適當配置。使用這個列舉成員定義的物件不可

             以在 Managed 程式碼以外公開。嘗試這麼做會產生例外狀況。

  • Explicit

              Unmanaged 記憶體中每個物件成員的精確位置是被明確的控制。每個成員必須使用 FieldOffsetAttribute

              ,表示該欄位在型別中的記憶體相對位置。

  • Sequential

              物件的成員是依序配置的,其順序即是將他們匯出至 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

http://pinvoke.net/index.aspx

3.Microsoft Win32 to Microsoft .NET Framework API Map

http://msdn.microsoft.com/en-us/library/aa302340.aspx

原文出處:張宇超研究室

三小俠  小弟獻醜,歡迎指教