.NET Framework 類別庫雖然龐大,但無法涵蓋龐大的 Win32 API。團隊優先封裝最常用的功能,大量 Win32 功能仍需透過平台呼叫 直接存取。P/Invoke 是最常用的互通機制,另一種選擇是 Managed C++ 包裝(本文不討論)。
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。
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。
[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++ 記憶體佈局。
[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 轉換。
[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();
[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# 方法重命名 |
| ExactSpelling | true 關閉 A/W 自動搜尋 |
| SetLastError | 保留 Win32 GetLastError,可用 Marshal.GetLastWin32Error() 讀取 |
| StructLayout.Pack | 結構對齊封裝大小 |
| StructLayout.Size | 強制結構大小 |
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
// 先手動載入特定路徑 DLL
LoadLibrary(@"C:\MyLibs\custom.dll");
// 後續 DllImport 會重用已載入模組
int 對應Marshal.SizeOf()、Pack、CharSet