.NET Framework 提供了豐富的類別庫,但部分系統層級功能(如底層視窗操作、裝置驅動程式互動、特定登錄操作)仍無對應的 Managed 封裝。此時,平台呼叫(P/Invoke)成為橋樑,讓 C# 直接呼叫 kernel32.dll、user32.dll、gdi32.dll 等原生 DLL 匯出函式。
本文記錄如何系統性整理 Win32 API 簽名、建構可重用的 Win32API 命名空間,並解決常見陷阱(如 DLL 命名衝突)。
Win32API.sln
├── Structs.Net.cs // 所有結構體、列舉定義
├── Kernel32.Net.cs // kernel32.dll 函式
├── User32.Net.cs // user32.dll 函式
├── GDI32.Net.cs // gdi32.dll 函式
├── OtherFnc.Net.cs // advapi32, shell32, spoolss, winmm
namespace Win32API
{
using System;
using System.Runtime.InteropServices;
// 1. 結構體與列舉集中放置
[StructLayout(LayoutKind.Sequential)]
public class RECT { public int Left, Top, Right, Bottom; }
public enum WindowStyles : uint { WS_OVERLAPPED = 0x00000000, ... }
// 2. 各 DLL 對應一個 static class
public static class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, ...);
}
public static class User32
{
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
}
// ... GDI32, Advapi32, Shell32, Spoolss, Winmm
}
csc /t:library /r:System.dll Structs.Net.cscsc /t:library /r:Structs.Net.dll Kernel32.Net.cs
using Win32API;
class Program
{
static void Main()
{
User32.MessageBox(IntPtr.Zero, "Hello P/Invoke", "Title", 0);
}
}
慘痛教訓:若將 C# 類別庫編譯為
User32.dll,執行時會載入「你的 DLL」而非系統C:\Windows\System32\user32.dll,導致MessageBox找不到入口點或簽名不符。解決:輸出檔名加上前綴(如
User32.Net.dll),或置於獨立目錄。編譯指令:csc /t:library /out:User32.Net.dll User32.Net.cs
// 尋找視窗
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// 設定前景視窗
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint GetFullPathName(string lpFileName, uint nBufferLength, StringBuilder lpBuffer, out IntPtr lpFilePart);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegOpenKeyEx(IntPtr hKey, string lpSubKey, uint ulOptions, int samDesired, out IntPtr phkResult);
SetLastError = true:搭配 Marshal.GetLastWin32Error() 取得錯誤碼。SafeHandle 衍生類:包裝非受控控制碼,確保釋放時機正確。建構一套乾淨、可維護的 Win32 API 互操作層,能讓 C# 應用程式在需要時突破 Managed 邊界,直達系統核心功能。關鍵在於結構化組織簽名、避免命名衝突、善用 SafeHandle 管理生命週期。將此庫視為基礎設施投資,長期回報極高。