呼叫 Windows API (P/Invoke) 時,正確對應非受控型別至 .NET 受控型別至關重要。錯誤對應會導致記憶體損毀、存取違規、資料截斷等難以除錯的問題。本文提供完整對應表,涵蓋基礎型別、指標/字串/結構體/陣列封送、呼叫慣例、32/64 位元差異,並附上常見 Win32 結構體對應範例。
| Win32 (C/C++) | 說明 | .NET (C#) | 大小 | 備註 |
|---|---|---|---|---|
HANDLE, HWND, HMODULE, HINSTANCE, HDC, HKEY... | 32/64 位元指標/句柄 | IntPtr / nint (.NET 7+) | 4/8 bytes | 嚴禁用 `int`/`long` |
BYTE, UCHAR | 無號 8 位元 | byte | 1 | |
SHORT | 有號 16 位元 | short | 2 | |
WORD, USHORT, ATOM | 無號 16 位元 | ushort | 2 | |
INT, LONG, LONG32, BOOL | 有號 32 位元 | int | 4 | Win32 `BOOL` 為 4 bytes,非 `bool` (1 byte) |
UINT, ULONG, DWORD, DWORD32 | 無號 32 位元 | uint | 4 | |
LONGLONG, LONG64, INT64 | 有號 64 位元 | long | 8 | |
ULONGLONG, DWORD64, UINT64, QWORD | 無號 64 位元 | ulong | 8 | |
FLOAT | IEEE 754 單精度 | float | 4 | |
DOUBLE | IEEE 754 雙精度 | double | 8 | |
CHAR | ANSI 字元 (1 byte) | byte 或 sbyte | 1 | 字串用 `string` + `CharSet.Ansi` |
WCHAR, TCHAR (Unicode 建置) | Unicode 字元 (2 bytes) | char | 2 | 字串用 `string` + `CharSet.Unicode` |
| 非受控宣告 | 用途 | C# 簽章建議 | 關鍵屬性 |
|---|---|---|---|
LPSTR / char* | ANSI 輸入字串 | string | [MarshalAs(UnmanagedType.LPStr)], `CharSet.Ansi` |
LPCSTR / const char* | ANSI 唯讀字串 | string | 同上 (預設 `In`) |
LPWSTR / wchar_t* | Unicode 輸出/輸入輸出字串 | StringBuilder | [MarshalAs(UnmanagedType.LPWStr)], `CharSet.Unicode` |
LPCWSTR / const wchar_t* | Unicode 唯讀字串 | string | 同上 |
LPVOID / void* | 通用指標 | IntPtr / nint | 配合 `Marshal.StructureToPtr`/`PtrToStructure` |
INT* / int[] | 整數陣列 (輸入) | int[] / Span<int> | [In] / [In, Out] |
INT* / int[] | 整數陣列 (輸出) | int[] (預先配置) | [Out], `SizeParamIndex` 指定長度參數 |
struct MyStruct* | 結構體指標 | ref MyStruct / out MyStruct | [In]/[Out], 結構體加 `[StructLayout(LayoutKind.Sequential)]` |
[StructLayout(LayoutKind.Sequential)]
public struct POINT { public int X; public int Y; }
[StructLayout(LayoutKind.Sequential)]
public struct RECT { public int Left, Top, Right, Bottom; }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WNDCLASSEX
{
public uint cbSize;
public uint style;
public IntPtr lpfnWndProc; // 函數指標 -> IntPtr
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
[MarshalAs(UnmanagedType.LPTStr)] public string lpszMenuName;
[MarshalAs(UnmanagedType.LPTStr)] public string lpszClassName;
public IntPtr hIconSm;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = false)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// CharSet.Auto: 依平台自動選 Ansi/Unicode (建議)
// CharSet.Ansi: 強制 ANSI (EntryPoint="FindWindowA")
// CharSet.Unicode: 強制 Unicode (EntryPoint="FindWindowW")
// SetLastError=true: 允許 Marshal.GetLastWin32Error() 取得錯誤碼
// ExactSpelling=false: 允許運行時依 CharSet 修飾函數名稱 (A/W 後綴)
IntPtr / nint,絕不使用 `int`/`long`。P/Invoke 型別對應錯誤是導致「不可重現崩潰」的首要原因。請永遠使用 `IntPtr` 處理句柄/指標、明確指定 `CharSet` 與 `SetLastError`、結構體加上 `StructLayout.Sequential`、優先使用 CsWin32/LibraryImport 自動產生簽章。將此表加入團隊 Wiki,代碼審查時必查項目。