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

C# 平台呼叫實戰:P/Invoke 完整指南與進階技巧

     編輯:關於C語言

為什麼需要 P/Invoke?

.NET Framework 類別庫雖然龐大,但無法涵蓋龐大的 Win32 API。團隊優先封裝最常用的功能,大量 Win32 功能仍需透過平台呼叫 直接存取。P/Invoke 是最常用的互通機制,另一種選擇是 Managed C++ 包裝(本文不討論)。

基礎範例:呼叫 Beep API

using System;
using System.Runtime.InteropServices;

namespace BeepDemo {
    class Program {
        [DllImport("kernel32.dll")]
        public static extern bool Beep(int frequency, int duration);

        static void Main() {
            Random r = new Random();
            for (int i = 0; i < 10000; i++) 
                Beep(r.Next(10000), 100); // 科幻電影音效
        }
    }
}

關鍵點:DllImport 指定 DLL,型別對應(DWORD→int,BOOL→bool),extern 宣告外部實作。需完全信任 才能執行 P/Invoke。

列舉與常數處理:MessageBeep

public enum BeepType : uint {
    SimpleBeep = 0xFFFFFFFF,  // -1 對應 uint.MaxValue
    IconAsterisk = 0x40,
    IconExclamation = 0x30,
    IconHand = 0x10,
    IconQuestion = 0x20,
    Ok = 0x00
}

[DllImport("user32.dll")]
public static extern bool MessageBeep(BeepType beepType);

// 呼叫
MessageBeep(BeepType.IconQuestion);

注意:Win32 常數常定義於 winuser.h,可用 findstr 搜尋。參數包含 -1 時建議用 int 而非 uint。

結構封送:GetSystemPowerStatus

[StructLayout(LayoutKind.Sequential)]
struct SystemPowerStatus {
    public byte ACLineStatus;
    public byte BatteryFlag;
    public byte BatteryLifePercent;
    public byte Reserved1;
    public int BatteryLifeTime;
    public int BatteryFullLifeTime;
}

// 進階:欄位用 enum 強型別
enum ACLineStatus : byte { Offline = 0, Online = 1, Unknown = 255 }
enum BatteryFlag : byte { High = 1, Low = 2, Critical = 4, Charging = 8, NoSystemBattery = 128, Unknown = 255 }

[DllImport("kernel32.dll")]
static extern bool GetSystemPowerStatus(ref SystemPowerStatus status);

ref 關鍵字表示傳遞指標而非值複本。結構預設循序配置,符合 C/C++ 記憶體佈局。

字串封送處理

基本字串:CharSet.Auto 自動選擇 ANSI/Unicode

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern bool GetDiskFreeSpace(
    [MarshalAs(UnmanagedType.LPTStr)] string rootPathName,
    ref int sectorsPerCluster, ref int bytesPerSector,
    ref int numberOfFreeClusters, ref int totalNumberOfClusters);

CharSet.Auto 讓執行階段依平台自動選擇 A/W 版本,效能優於預設 ANSI 轉換。

字串緩衝區:StringBuilder 替代不可變 string

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern int GetShortPathName(
    [MarshalAs(UnmanagedType.LPTStr)] string path,
    [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath,
    int shortPathLength);

// 呼叫
StringBuilder sb = new StringBuilder(80);
GetShortPathName(@"d:\test.jpg", sb, sb.Capacity);
string result = sb.ToString();

內嵌字串陣列結構:GetTimeZoneInformation

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TimeZoneInformation {
    public int Bias;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string StandardName;
    public SystemTime StandardDate;
    public int StandardBias;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string DaylightName;
    public SystemTime DaylightDate;
    public int DaylightBias;
}

關鍵:StructLayout.CharSet = Unicode 強制雙位元組字元,ByValTStr + SizeConst 對應固定長度字元陣列。

回呼函數:委派對應函數指標

delegate bool EnumDesktopProc(
    [MarshalAs(UnmanagedType.LPTStr)] string desktopName,
    int lParam);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool EnumDesktops(IntPtr hwinsta, EnumDesktopProc callback, int lParam);

重要陷阱:若非受控碼保存委派指標供日後使用(如 SetConsoleCtrlHandler),必須在 Managed 端保持委派參考,否則 GC 回收導致存取違規。

進階屬性選項速查

屬性用途
CallingConvention指定呼叫慣例(StdCall/Cdecl),錯誤導致堆疊損毀
CharSet控制 A/W 版本選擇
EntryPoint指定 DLL 實際匯出名稱,允許 C# 方法重命名
ExactSpellingtrue 關閉 A/W 自動搜尋
SetLastError保留 Win32 GetLastError,可用 Marshal.GetLastWin32Error() 讀取
StructLayout.Pack結構對齊封裝大小
StructLayout.Size強制結構大小

動態載入 DLL 技巧

[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);

// 先手動載入特定路徑 DLL
LoadLibrary(@"C:\MyLibs\custom.dll");
// 後續 DllImport 會重用已載入模組

常見錯誤排查

  1. long ≠ long:C++ long=4 bytes,C# long=8 bytes → 用 int 對應
  2. 字串 CharSet 錯誤:導致亂碼或存取違規
  3. 結構大小不符:檢查 Marshal.SizeOf()、Pack、CharSet
  4. 委派被 GC 回收:靜態欄位保持參考
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved