首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 呼叫 Windows API 完整指南:從 P/Invoke 到結構體封送

     編輯:關於C#

前言:為何在 C# 中仍需呼叫 API?

雖然 .NET Framework 提供了龐大的基底類別庫(BCL),涵蓋檔案 I/O、網路通訊、執行緒管理等常見功能,但 Windows 作業系統的核心功能仍大量以 Win32 API 形式暴露。例如:進階視窗管理、系統資訊查詢、底層硬體控制、註冊表深度操作等,往往只能透過平台呼叫(P/Invoke)直接存取 kernel32.dll、user32.dll、gdi32.dll 等原生 DLL。掌握 P/Invoke 不僅能突破框架限制,更是 C# 開發者進階的必修課。

Win32 API 基礎架構速覽

Win32 API 以三大核心 DLL 為支柱:

  • KERNEL32.DLL:處理程序/執行緒管理、記憶體配置、檔案 I/O、時間日期等核心系統服務。
  • USER32.DLL:管理視窗、訊息佇列、選單、對話框、圖示、游標等使用者介面元素。
  • GDI32.DLL:提供繪圖介面(Graphics Device Interface),負責位圖、筆刷、字型、繪圖座標轉換等圖形輸出。

所有 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);

注意事項:

  1. LayoutKind.Sequential 確保欄位依宣告順序緊密排列,符合 C 語言結構體記憶體模型。
  2. 指標類欄位請使用 IntPtr,而非 uint,以相容 32/64 位元程序。
  3. 輸出參數建議使用 out 而非 ref,語意更清晰且免初始化。

完整範例:同時查詢 CPU 與記憶體狀態

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)以減少非受控碼依賴。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved