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

C# 平台呼叫 (P/Invoke) 實戰指南:從 Beep 到結構封送、字串編碼完整教學

     編輯:C# 入門

為什麼需要 P/Invoke?

.NET Framework 類別庫未涵蓋所有 Win32 API (數萬個)。當需要:

  • 硬體控制 (風扇、溫度、序列號)
  • 系統底層功能 (電源管理、檔案系統過濾器、視窗子類別化)
  • 高效能互通 (避免 COM Interop 開銷)
  • 舊版 DLL 整合

即須使用 **Platform Invocation Services (P/Invoke)** 直接呼叫 Unmanaged DLL 匯出函式。

四大核心步驟

  1. 找到 DLL 與函式簽名:MSDN / pinvoke.net / Windows SDK Header (`winuser.h`, `winbase.h`)。
  2. 型別對應 (Marshaling):Win32 型別 → C# 型別對照表 (見下)。
  3. 套用 `DllImport` 屬性:指定 DLL 名稱、呼叫慣例、字元集、SetLastError。
  4. 撰寫 `static extern` 宣告:在 `class` 或 `struct` 內定義。

Win32 ↔ C# 型別對照表 (高頻)

Win32C#備註
DWORD, ULONG, UINTuint / int建議用 `uint` 表達無號,`int` 相容 CLS
BOOLbool / int`[MarshalAs(UnmanagedType.Bool)]` 為 4 bytes;`bool` 為 1 byte
HANDLE, HWND, HMODULEIntPtr永遠用 `IntPtr`,不可用 `int`/`long`
LPSTR / LPCSTRStringBuilder / stringANSI 字串,`StringBuilder` 可修改
LPWSTR / LPCWSTRStringBuilder / stringUnicode 字串 (預設)
LPVOID / PVOIDIntPtr / void*泛型指標
BYTEbyte
WORDushort
LONG, INTint
FLOAT, DOUBLEfloat, double
GUIDGuid`[MarshalAs(UnmanagedType.LPStruct)]`
FILETIMElong / System.Runtime.InteropServices.ComTypes.FILETIME100-ns intervals since 1601

範例 1:最簡單的 Beep

internal static class Kernel32 {
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool Beep(uint dwFreq, uint dwDuration);
}

// 使用
Kernel32.Beep(1000, 500); // 1kHz, 0.5秒

範例 2:MessageBeep + 列舉常數

internal static class User32 {
    public enum MessageBeepType : uint {
        OK = 0x00000000,
        IconHand = 0x00000010,
        IconQuestion = 0x00000020,
        IconExclamation = 0x00000030,
        IconAsterisk = 0x00000040,
        SimpleBeep = 0xFFFFFFFF // -1
    }

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool MessageBeep(MessageBeepType uType);
}

User32.MessageBeep(User32.MessageBeepType.IconQuestion);

範例 3:結構封送 - GetSystemPowerStatus

[StructLayout(LayoutKind.Sequential)]
internal struct SystemPowerStatus {
    public byte ACLineStatus;      // 0=離線, 1=線上, 255=未知
    public byte BatteryFlag;       // 1=高, 2=低, 4=關鍵, 8=充電中, 128=無電池
    public byte BatteryLifePercent; // 0-100, 255=未知
    public byte Reserved1;
    public int BatteryLifeTime;    // 秒, -1=未知
    public int BatteryFullLifeTime; // 秒, -1=未知
}

internal static class Kernel32 {
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool GetSystemPowerStatus(ref SystemPowerStatus lpSystemPowerStatus);
}

// 使用
var status = new SystemPowerStatus();
if (Kernel32.GetSystemPowerStatus(ref status)) {
    Console.WriteLine($"AC: {status.ACLineStatus}, Battery%: {status.BatteryLifePercent}, Life: {status.BatteryLifeTime}s");
}

// 進階:將欄位轉為列舉
enum ACLineStatus : byte { Offline = 0, Online = 1, Unknown = 255 }
enum BatteryFlag : byte { High = 1, Low = 2, Critical = 4, Charging = 8, NoBattery = 128, Unknown = 255 }

範例 4:字串處理 - GetWindowText (ANSI/Unicode 自動)

internal static class User32 {
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowTextLength(IntPtr hWnd);
}

public static string GetWindowTitle(IntPtr hWnd) {
    int len = User32.GetWindowTextLength(hWnd);
    if (len == 0) return "";
    var sb = new StringBuilder(len + 1);
    User32.GetWindowText(hWnd, sb, sb.Capacity);
    return sb.ToString();
}

關鍵:`CharSet.Auto` 讓運行時依平台自動選擇 `GetWindowTextA` (ANSI) 或 `GetWindowTextW` (Unicode),且自動處理 `string` ↔ `StringBuilder` 封送。

進階封送技巧

需求屬性/寫法
固定長度字串緩衝區`[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string Path;`
陣列內嵌結構`[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] SerialNumber;`
自訂封送實作 `ICustomMarshaler`,`[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshaler))]`
委派回呼函式`delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);` + `GCHandle.Alloc(delegate)` 防止被 GC
錯誤碼獲取`SetLastError = true` + `Marshal.GetLastWin32Error()`

安全性與效能提醒

  • **安全性**:P/Invoke 需 `SecurityPermission(UnmanagedCode)`,預設 Full Trust 才能執行。沙箱環境 (ClickOnce、Silverlight) 會受限。
  • **效能**:每次跨界 ~50-100ns 開銷,熱路徑避免頻繁呼叫;批次操作考慮一次傳遞陣列/結構。
  • **除錯**:啟用 **非受控程式碼偵錯** (專案屬性 → Debug → Enable unmanaged code debugging) 可單步進入 DLL。
  • **資源釋放**:`SafeHandle` 衍生類別包裝 `HANDLE`,確保關閉、防止重複釋放、可靠最終釋放。

推薦資源

  • pinvoke.net - 社群維護的簽名資料庫
  • dotnet/pinvoke - 官方維護的 NuGet 套件 (`Microsoft.Windows.CsWin32` 可自動生成簽名)
  • Windows SDK `Include\um\*.h` - 最權威原始定義
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved