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

C# 呼叫 Windows API 函數完整指南:P/Invoke 核心技術與實戰範例

     編輯:關於C語言

前言:為什麼需要呼叫 Windows API?

Windows API 是構建 Windows 應用程式的基石。雖然 .NET Framework 提供了豐富的類別庫,但在某些情況下,我們仍需直接呼叫作業系統底層的 API 函數,例如:存取硬體裝置、控制視窗行為、處理系統層級事件等。C# 透過 Platform Invocation Services (P/Invoke) 機制,讓受控代碼能夠呼叫非受控的 DLL 函數(包括 Windows API),實現跨越互操作邊界的無縫整合。

核心機制:DllImport 屬性詳解

所有的 P/Invoke 宣告都從 System.Runtime.InteropServices.DllImportAttribute 開始。以下是標準語法結構:

[DllImport("DLL名稱", EntryPoint = "函數名稱", 
    CharSet = CharSet.Unicode/Ansi/Auto,
    SetLastError = true/false,
    CallingConvention = CallingConvention.StdCall/Cdecl,
    ExactSpelling = true/false)]
public static extern 返回類型 函數名稱(參數列表);

關鍵參數說明

  • EntryPoint:指定 DLL 中的實際函數名稱或序號(如 #123)。若省略,預設使用 C# 方法名稱。
  • CharSet:控制字串封送處理方式。CharSet.Unicode 對應 MessageBoxW,CharSet.Ansi 對應 MessageBoxA,CharSet.Auto 依平台自動選擇。
  • SetLastError:設為 true 時,可透過 Marshal.GetLastWin32Error() 取得 Win32 錯誤碼。
  • CallingConvention:Windows API 多為 StdCall,C 執行期庫多為 Cdecl。
  • ExactSpelling:true 禁止編譯器依 CharSet 修飾函數名稱(如加上 A/W 後綴)。

實戰範例一:基本函數呼叫與重命名

呼叫 user32.dll 的 MessageBox,並重命名為 MsgBox:

using System;
using System.Runtime.InteropServices;

public class Win32
{
    [DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = CharSet.Auto)]
    public static extern int MsgBox(IntPtr hWnd, string text, string caption, uint type);
}

// 呼叫方式
Win32.MsgBox(IntPtr.Zero, "Hello P/Invoke!", "測試", 0);

實戰範例二:結構體封送與記憶體佈局控制

Windows API 常使用結構體傳遞複雜參數。C# 需使用 StructLayoutAttribute 精確控制記憶體佈局:

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;
}

[StructLayout(LayoutKind.Explicit)]
public struct RECT
{
    [FieldOffset(0)] public int Left;
    [FieldOffset(4)] public int Top;
    [FieldOffset(8)] public int Right;
    [FieldOffset(12)] public int Bottom;
}

public class User32
{
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool PtInRect(ref RECT lprc, POINT pt);
}

LayoutKind 選擇指南:

  • Sequential:依宣告順序排列,適合大多數 Win32 結構體。
  • Explicit:透過 FieldOffset 精確控制位移,適用於聯集或特殊對齊需求。
  • Automatic:嚴禁用於 P/Invoke,執行期可能重新排序欄位。

實戰範例三:回調函數與委派

枚舉所有頂層視窗(EnumWindows)展示回調用法:

using System;
using System.Runtime.InteropServices;

public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

public class WindowEnumerator
{
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

    public static void Enumerate()
    {
        EnumWindowsProc callback = (hWnd, lParam) =>
        {
            Console.WriteLine($"視窗句柄: 0x{hWnd.ToInt64():X}");
            return true; // 繼續枚舉
        };
        EnumWindows(callback, IntPtr.Zero);
        // 注意:callback 必須保持存活,防止被 GC 回收導致存取違規
    }
}

實戰範例四:指標、陣列與字串緩衝區處理

取得電腦名稱(GetComputerName)示範 StringBuilder 作為輸出緩衝區:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetComputerName(StringBuilder lpBuffer, ref int nSize);

public static string GetMachineName()
{
    int capacity = 256;
    StringBuilder sb = new StringBuilder(capacity);
    if (GetComputerName(sb, ref capacity))
        return sb.ToString();
    throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}

進階主題:泛型委派、SafeHandle 與錯誤處理最佳實務

  • 使用 SafeHandle 衍生類別管理非受控資源(如檔案控制代碼、GDI 物件),確保關鍵性終結。
  • 搭配 SetLastError = true 與 Marshal.GetLastWin32Error() 進行錯誤診斷。
  • 針對 Unicode/ANSI 版本,優先使用 CharSet.Auto 並讓執行期自動選擇。
  • 避免在迴圈中重複定義委派實例,防止委派被 GC 回收導致回調失效。

總結

P/Invoke 是 C# 連結非受控世界的橋樑。掌握 DllImport、結構體佈局、委派回調、字串封送等核心技術,並遵循資源管理與錯誤處理最佳實務,即能安全、高效地呼叫 Windows API,擴展 .NET 應用程式的能力邊界。

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