Windows API 是構建 Windows 應用程式的基石。雖然 .NET Framework 提供了豐富的類別庫,但在某些情況下,我們仍需直接呼叫作業系統底層的 API 函數,例如:存取硬體裝置、控制視窗行為、處理系統層級事件等。C# 透過 Platform Invocation Services (P/Invoke) 機制,讓受控代碼能夠呼叫非受控的 DLL 函數(包括 Windows API),實現跨越互操作邊界的無縫整合。
所有的 P/Invoke 宣告都從 System.Runtime.InteropServices.DllImportAttribute 開始。以下是標準語法結構:
[DllImport("DLL名稱", EntryPoint = "函數名稱",
CharSet = CharSet.Unicode/Ansi/Auto,
SetLastError = true/false,
CallingConvention = CallingConvention.StdCall/Cdecl,
ExactSpelling = true/false)]
public static extern 返回類型 函數名稱(參數列表);
#123)。若省略,預設使用 C# 方法名稱。CharSet.Unicode 對應 MessageBoxW,CharSet.Ansi 對應 MessageBoxA,CharSet.Auto 依平台自動選擇。true 時,可透過 Marshal.GetLastWin32Error() 取得 Win32 錯誤碼。StdCall,C 執行期庫多為 Cdecl。true 禁止編譯器依 CharSet 修飾函數名稱(如加上 A/W 後綴)。呼叫 user32.dll 的 MessageBox,並重命名為 MsgBox:
using System;
using System.Runtime.InteropServices;
public class Win32
{
[DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = CharSet.Auto)]
public static extern int MsgBox(IntPtr hWnd, string text, string caption, uint type);
}
// 呼叫方式
Win32.MsgBox(IntPtr.Zero, "Hello P/Invoke!", "測試", 0);
Windows API 常使用結構體傳遞複雜參數。C# 需使用 StructLayoutAttribute 精確控制記憶體佈局:
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Explicit)]
public struct RECT
{
[FieldOffset(0)] public int Left;
[FieldOffset(4)] public int Top;
[FieldOffset(8)] public int Right;
[FieldOffset(12)] public int Bottom;
}
public class User32
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PtInRect(ref RECT lprc, POINT pt);
}
LayoutKind 選擇指南:
Sequential:依宣告順序排列,適合大多數 Win32 結構體。Explicit:透過 FieldOffset 精確控制位移,適用於聯集或特殊對齊需求。Automatic:嚴禁用於 P/Invoke,執行期可能重新排序欄位。枚舉所有頂層視窗(EnumWindows)展示回調用法:
using System;
using System.Runtime.InteropServices;
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
public class WindowEnumerator
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public static void Enumerate()
{
EnumWindowsProc callback = (hWnd, lParam) =>
{
Console.WriteLine($"視窗句柄: 0x{hWnd.ToInt64():X}");
return true; // 繼續枚舉
};
EnumWindows(callback, IntPtr.Zero);
// 注意:callback 必須保持存活,防止被 GC 回收導致存取違規
}
}
取得電腦名稱(GetComputerName)示範 StringBuilder 作為輸出緩衝區:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetComputerName(StringBuilder lpBuffer, ref int nSize);
public static string GetMachineName()
{
int capacity = 256;
StringBuilder sb = new StringBuilder(capacity);
if (GetComputerName(sb, ref capacity))
return sb.ToString();
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
SafeHandle 衍生類別管理非受控資源(如檔案控制代碼、GDI 物件),確保關鍵性終結。SetLastError = true 與 Marshal.GetLastWin32Error() 進行錯誤診斷。CharSet.Auto 並讓執行期自動選擇。P/Invoke 是 C# 連結非受控世界的橋樑。掌握 DllImport、結構體佈局、委派回調、字串封送等核心技術,並遵循資源管理與錯誤處理最佳實務,即能安全、高效地呼叫 Windows API,擴展 .NET 應用程式的能力邊界。