首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET Compact Framework P/Invoke 呼叫 Win32 API 完整指南

     編輯:關於.NET

背景:為何需要 P/Invoke

.NET Compact Framework (CF) 是 .NET Framework 的精簡版,許多功能未內建實作,必須直接呼叫 Win32 API (位於 coredll.dll) 來完成。C++ 呼叫原生 DLL 直觀簡單,C# 則需透過 **P/Invoke (Platform Invocation Services)** 進行跨界呼叫。

一、匯入 DLL 基礎語法

[DllImport("coredll.dll", CharSet = CharSet.Auto)]
// 或指定進入點、呼叫慣例、設定 LastError
[DllImport("coredll.dll", EntryPoint = "FunctionName", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
  • CharSet.Auto:依平台自動選擇 ANSI/Unicode (Windows CE 為 Unicode)。
  • SetLastError = true:允許透過 Marshal.GetLastWin32Error() 取得錯誤碼。

二、錯誤處理最佳實務

Win32 API 多以回傳值表示成功/失敗。建議封裝為例外拋出,而非單純檢查回傳值:

int result = RasHangUp(rasSession);
if (result != 0)
{
    throw new ApplicationException($"RasHangUp({rasSession}) failed with error code: {result}");
}

三、參數轉換核心對照表 (Marshaling)

C/C++ 型別C# 對應型別說明
int, short, long, uintint, short, long, uint直接對應
float, doublefloat, double直接對應
BYTEbyte / uint單位元組
BOOLint / [MarshalAs(UnmanagedType.Bool)] boolWin32 BOOL 為 4 bytes
enumenum : uint指定底層型別為 uint
char不可直接對應C++ char=1 byte, C# char=2 bytes (Unicode)
LPWSTR / LPCWSTRstring / StringBuilderUnicode 字串指標
LPSTR / LPCSTRstring (配合 CharSet.Ansi)ANSI 字串指標
結構體struct + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]記憶體配置需完全一致
結構體指標ref Struct / out Struct / IntPtr (手動 Marshal)視 API 是否需配置記憶體
回呼函數delegate + GCHandle.Alloc 防止被 GC 回收委派實例必須保持存活

四、實戰範例:列舉 WiFi 介面 (WZCEnumInterfaces)

原生宣告

DWORD WZCEnumInterfaces(LPWSTR pSrvAddr, PINTFS_KEY_TABLE pIntfs);

typedef struct {
    DWORD dwNumIntfs;
    PINTF_KEY_ENTRY pIntfs;
} INTFS_KEY_TABLE, *PINTFS_KEY_TABLE;

C# 封裝

[DllImport("wzcsapi.dll")]
public static extern uint WZCEnumInterfaces(string pSrvAddr, ref INTFS_KEY_TABLE pIntfs);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct INTFS_KEY_TABLE : IDisposable
{
    private uint dwNumIntfs;
    public IntPtr pIntfs; // 指向 INTF_KEY_ENTRY 陣列

    public INTFS_KEY_TABLE(uint size)
    {
        dwNumIntfs = size;
        if (size != 0)
        {
            // 配置非受控記憶體:指標陣列大小 = IntPtr.Size * size
            pIntfs = Marshal.AllocHGlobal((int)(IntPtr.Size * size));
        }
        else pIntfs = IntPtr.Zero;
    }

    // 索引器:讀取第 i 個介面名稱
    public string this[uint i]
    {
        get
        {
            // 讀取指標陣列中第 i 個 IntPtr
            IntPtr entryPtr = (IntPtr)Marshal.ReadInt32(pIntfs, (int)(i * IntPtr.Size));
            // 假設 INTF_KEY_ENTRY 第一欄位為 LPWSTR (介面名稱指標)
            return Marshal.PtrToStringUni(entryPtr);
        }
    }

    public uint Count => dwNumIntfs;

    public void Dispose()
    {
        if (pIntfs != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(pIntfs);
            pIntfs = IntPtr.Zero;
        }
    }
}

五、巢狀結構體處理策略

若結構體包含另一結構體,可用以下任一種方式:

  1. 展平:將內部結構體欄位直接拉到外層 (需手動計算 Offset)。
  2. 使用 IntPtr + 手動 Marshal:外層只存指標,執行時 Marshal.PtrToStructure 讀取內部結構。
  3. 使用 byte[]:預留足夠位元組陣列,序列化/反序列化時手動處理。

本質上 C++ 結構體就是「記憶體 + Offset」,掌握這點即可靈活應對。

六、常見坑與避雷指南

  1. CallingConvention 不匹配:Win32 API 幾乎皆為 StdCall (C# 預設),但 C 執行期函式庫為 Cdecl。
  2. CharSet 錯誤導致字串亂碼:Windows CE 統一 Unicode,務必用 CharSet.Auto 或 CharSet.Unicode。
  3. 結構體大小計算錯誤:使用 Marshal.SizeOf<T>() 而非 sizeof (C# 不支援)。
  4. 非受控記憶體洩漏:AllocHGlobal 必須配對 FreeHGlobal,實作 IDisposable 並搭配 using。
  5. 委派被 GC 回收:傳給非受控碼的委派必須在類別層級保持參考,或用 GCHandle.Alloc(delegate)。
  6. 32/64 位元指標大小:CF 多為 32-bit,但代碼應使用 IntPtr / UIntPtr 確保可移植性。

七、參考資源

  • PInvoke.net — 社群維護的海量 API 簽名資料庫
  • Microsoft 官方文件:System.Runtime.InteropServices 命名空間
  • coredll.dll / wzcsapi.dll 等 Windows CE 系統 DLL 匯出函數表
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved