雖然 .NET Framework 提供了龐大的基底類別庫(BCL),涵蓋檔案 I/O、網路通訊、執行緒管理等常見功能,但 Windows 作業系統的核心功能仍大量以 Win32 API 形式暴露。例如:進階視窗管理、系統資訊查詢、底層硬體控制、註冊表深度操作等,往往只能透過平台呼叫(P/Invoke)直接存取 kernel32.dll、user32.dll、gdi32.dll 等原生 DLL。掌握 P/Invoke 不僅能突破框架限制,更是 C# 開發者進階的必修課。
Win32 API 以三大核心 DLL 為支柱:
所有 API 函式均遵循 stdcall 呼叫慣例,參數由右至左壓入堆疊,由被呼叫者清理堆疊——這正是 DllImport 預設行為所對應的模型。
使用 System.Runtime.InteropServices 命名空間中的 DllImportAttribute 即可將非受控函式對應至 C# 靜態方法:
using System.Runtime.InteropServices;
public static class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
}
關鍵屬性說明:
CharSet:指定字串封送編碼(Auto 依平台自動選 Unicode/ANSI)。SetLastError = true:允許透過 Marshal.GetLastWin32Error() 取得 Win32 錯誤碼。ExactSpelling = false(預設):允許執行階段依 CharSet 尋找 MessageBoxW 或 MessageBoxA 進入點。SYSTEM_INFO 取得系統資訊許多 API 需要複雜結構體做為輸入/輸出參數。C# 使用 StructLayoutAttribute 控制記憶體配置:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
public uint dwOemId;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public IntPtr lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort dwProcessorLevel;
public ushort dwProcessorRevision;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
注意事項:
LayoutKind.Sequential 確保欄位依宣告順序緊密排列,符合 C 語言結構體記憶體模型。IntPtr,而非 uint,以相容 32/64 位元程序。out 而非 ref,語意更清晰且免初始化。using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class SystemInfoDemo
{
[StructLayout(LayoutKind.Sequential)]
public struct MEMORYSTATUS
{
public uint dwLength;
public uint dwMemoryLoad;
public ulong dwTotalPhys;
public ulong dwAvailPhys;
public ulong dwTotalPageFile;
public ulong dwAvailPageFile;
public ulong dwTotalVirtual;
public ulong dwAvailVirtual;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void GlobalMemoryStatusEx(ref MEMORYSTATUS lpBuffer);
[DllImport("kernel32.dll")]
public static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
public static void ShowSystemInfo(ListBox listBox)
{
// 取得 CPU 資訊
GetSystemInfo(out SYSTEM_INFO si);
listBox.Items.Add($"處理器核心數:{si.dwNumberOfProcessors}");
listBox.Items.Add($"頁面大小:{si.dwPageSize} bytes");
listBox.Items.Add($"處理器架構:{(ProcessorArchitecture)si.dwProcessorType}");
// 取得記憶體資訊(建議使用 GlobalMemoryStatusEx,支援 >4GB)
MEMORYSTATUS ms = new MEMORYSTATUS { dwLength = (uint)Marshal.SizeOf() };
GlobalMemoryStatusEx(ref ms);
listBox.Items.Add($"實體記憶體總計:{ms.dwTotalPhys / 1024 / 1024} MB");
listBox.Items.Add($"可用實體記憶體:{ms.dwAvailPhys / 1024 / 1024} MB");
listBox.Items.Add($"記憶體使用率:{ms.dwMemoryLoad}%");
}
}
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 呼叫後程式當機 | 簽章不符(參數型別、呼叫慣例、CharSet) | 參考 pinvoke.net 確認正確簽章;啟用 SetLastError 搭配除錯器檢查錯誤碼 |
| 結構體資料錯誤 | 記憶體對齊、指標大小、字串編碼不匹配 | 使用 StructLayout.Pack 控制對齊;指標用 IntPtr;字串指定 MarshalAs(UnmanagedType.LPWStr) |
| 記憶體洩漏 | 未釋放 API 分配的非受控資源(如 LocalFree、GlobalFree、CloseHandle) | 實作 IDisposable / SafeHandle 封裝非受控資源,確保釋放 |
| 64 位元相容性 | 以 int/uint 對應指標 | 一律使用 IntPtr、UIntPtr 對應 HANDLE、HWND、LPVOID 等 |
部分 API(如 EnumWindows、SetWindowsHookEx)需要回呼函式。C# 需將委派實例傳遞給非受控碼,並防止 GC 回收委派:
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
// 使用時必須保持委派實例存活
EnumWindowsProc callback = (hWnd, lParam) => {
// 處理視窗列舉
return true; // 繼續列舉
};
EnumWindows(callback, IntPtr.Zero);
// callback 變數在這裡仍被引用,不會被 GC 回收
P/Invoke 是 C# 與 Windows 作業系統深度整合的橋樑。掌握結構體配置、字串封送、錯誤處理、64 位元相容性等核心技巧,即能安全高效地調用數千個 Win32 API,突破框架限制,實現系統級功能。建議開發者善用 pinvoke.net 社群簽章庫,並優先考慮 .NET Core/.NET 5+ 內建的跨平台 API(如 System.Diagnostics.Process、Microsoft.Win32.Registry)以減少非受控碼依賴。