C# 開發者遲早會面臨呼叫 Windows 原生 API (P/Invoke) 的需求。本文由淺入深,先示範最簡單的 MessageBox 呼叫,再進階講解結構體 (struct) 封送與 GetSystemInfo/GlobalMemoryStatus 實戰,幫助你掌握核心概念:DllImport、StructLayout、MarshalAs、ref/out 參數傳遞。
using System.Runtime.InteropServices;
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
DllImport:指定 DLL 名稱,CharSet.Auto 自動依專案 Unicode/ANSI 切換字串封送。static extern:標記為外部靜態方法,無方法主體。HWND→IntPtr、LPCTSTR→string、UINT→uint。private void btnCallApi_Click(object sender, EventArgs e)
{
MessageBox(IntPtr.Zero, "來自 User32.dll 的問候", "API Demo", 0);
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
public uint dwOemId;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct MEMORYSTATUS
{
public uint dwLength;
public uint dwMemoryLoad;
public uint dwTotalPhys;
public uint dwAvailPhys;
public uint dwTotalPageFile;
public uint dwAvailPageFile;
public uint dwTotalVirtual;
public uint dwAvailVirtual;
}
LayoutKind.Sequential:依欄位順序記憶體配置,符合 C 語言結構體預設佈局。CharSet.Auto:確保字串欄位正確封送(此範例無字串,但屬好習慣)。uint 對應 DWORD/ULONG,避免正負號誤解。[DllImport("kernel32.dll")]
static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern void GlobalMemoryStatus(ref MEMORYSTATUS lpBuffer);
ref 關鍵字:傳遞結構體參考,API 填寫資料後呼叫端可取得結果。kernel32.dll 為核心系統 DLL,常駐記憶體,載入開銷極低。private void btnGetInfo_Click(object sender, EventArgs e)
{
try
{
SYSTEM_INFO sysInfo = new SYSTEM_INFO();
GetSystemInfo(ref sysInfo);
MEMORYSTATUS memStatus = new MEMORYSTATUS { dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUS)) };
GlobalMemoryStatus(ref memStatus);
// 顯示處理器類型
string cpuType = sysInfo.dwProcessorType switch
{
386 => "Intel 386",
486 => "Intel 486",
586 => "Intel Pentium",
4000 => "MIPS R4000",
21064 => "DEC Alpha 21064",
_ => "(unknown)"
};
listBox.Items.Add($"CPU 類型: {cpuType}");
listBox.Items.Add($"處理器數量: {sysInfo.dwNumberOfProcessors}");
listBox.Items.Add($"頁面大小: {sysInfo.dwPageSize} bytes");
listBox.Items.Add($"記憶體使用率: {memStatus.dwMemoryLoad}%");
listBox.Items.Add($"可用實體記憶體: {memStatus.dwAvailPhys / 1024 / 1024} MB");
listBox.Items.Add($"總實體記憶體: {memStatus.dwTotalPhys / 1024 / 1024} MB");
}
catch (Exception ex)
{
MessageBox.Show($"API 呼叫失敗: {ex.Message}");
}
}
| 概念 | 說明 |
|---|---|
DllImport | 指定 DLL 路徑、進入點、呼叫慣例、字元集等 |
StructLayout | 控制結構體記憶體佈局,Sequential 最常用 |
MarshalAs | 單一欄位自訂封送,如 UnmanagedType.LPStr 強制 ANSI 字串 |
ref/out | 對應 C/C++ 指標參數,ref 需初始化,out 僅輸出 |
IntPtr | 平台無關指標/控制代碼,32/64 位元自動適配 |
SetLastError=true | 搭配 Marshal.GetLastWin32Error() 取得錯誤碼 |
MEMORYSTATUS.dwLength 必須先設定為 Marshal.SizeOf 結果,否則 API 可能拒絕填寫。LPWSTR;若 DLL 僅提供 ANSI 版本,需加 CharSet=CharSet.Ansi 或欄位加 [MarshalAs(UnmanagedType.LPStr)]。IntPtr,切勿用 int/long。LocalFree、CoTaskMemFree),C# 端可用 Marshal.FreeHGlobal。