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

C# 使用 Win32 類別庫進階(二):字串封送、StringBuilder 緩衝區、結構內嵌字串陣列、回調委派

     編輯:關於C語言

前言

P/Invoke 字串處理是最易出錯的領域。本文深入解析:ANSI/Unicode 雙版本機制、CharSet.Auto 自動選擇、StringBuilder 作為輸出緩衝區、結構內嵌字串陣列封送、委派實現回調函數指標。

一、Win32 雙版本機制:A (ANSI) / W (Unicode)

  • Win9x:單位元組 ANSI,API 為 FunctionA
  • WinNT/現代 Windows:雙位元組 Unicode,API 為 FunctionW
  • C/C++:TCHAR、_UNICODE 宏控制編譯版本
  • P/Invoke:執行期自動選擇,優先查找 Function,無則嘗試 FunctionA/FunctionW

二、簡單字串參數:CharSet.Auto + LPTStr

API 原型

BOOL GetDiskFreeSpace(
    LPCTSTR lpRootPathName,  // 平台無關字串指標
    LPDWORD lpSectorsPerCluster,
    LPDWORD lpBytesPerSector,
    LPDWORD lpNumberOfFreeClusters,
    LPDWORD lpTotalNumberOfClusters
);

正確 P/Invoke 定義

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool GetDiskFreeSpace(
    [MarshalAs(UnmanagedType.LPTStr)] string rootPathName,
    out uint sectorsPerCluster,
    out uint bytesPerSector,
    out uint numberOfFreeClusters,
    out uint totalNumberOfClusters
);

關鍵點

  • CharSet.Auto:Win9x 用 ANSI,NT 用 Unicode,自動匹配
  • LPTStr:對應 LPCTSTR,平台相關字串指標
  • 不加 MarshalAs 也可工作(預設 ANSI),但會雙向轉換損失效能

三、字串輸出緩衝區:必須用 StringBuilder

.NET string 不可變,無法作為輸出緩衝區。StringBuilder 專為可變緩衝區設計。

API:GetShortPathName

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetShortPathName(
    [MarshalAs(UnmanagedType.LPTStr)] string path,
    [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath,
    int shortPathLength
);

呼叫範例

StringBuilder shortPath = new StringBuilder(260); // MAX_PATH
int result = GetShortPathName(@"D:\Very Long Path\Test.txt", shortPath, shortPath.Capacity);
if (result > 0)
    Console.WriteLine(shortPath.ToString()); // 短路徑

關鍵細節

  • Capacity = 緩衝區大小(含結束字元)
  • 回傳值 = 實際寫入字元數(不含結束字元),0 表示失敗
  • 必須預先配置足夠容量,否則截斷或失敗

四、結構內嵌字串陣列:ByValTStr + SizeConst

Win32 結構

typedef struct _TIME_ZONE_INFORMATION {
    LONG Bias;
    WCHAR StandardName[32];  // 固定長度 Unicode 字串陣列
    SYSTEMTIME StandardDate;
    LONG StandardBias;
    WCHAR DaylightName[32];
    SYSTEMTIME DaylightDate;
    LONG DaylightBias;
} TIME_ZONE_INFORMATION;

C# 封送定義

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TimeZoneInformation
{
    public int Bias;
    
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string StandardName;
    
    public SystemTime StandardDate;
    public int StandardBias;
    
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string DaylightName;
    
    public SystemTime DaylightDate;
    public int DaylightBias;
}

[StructLayout(LayoutKind.Sequential)]
struct SystemTime
{
    public short Year, Month, DayOfWeek, Day, Hour, Minute, Second, Milliseconds;
}

兩大關鍵屬性

  1. StructLayout.CharSet = Unicode:結構內所有 ByValTStr 強制 Unicode(Win32 用 WCHAR),避免預設 ANSI 導致大小不符
  2. MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32):內嵌固定長度字串陣列,SizeConst 必須匹配原生定義

驗證技巧

// 驗證結構大小
int size = Marshal.SizeOf();
Console.WriteLine($"Managed size: {size}");
// 對照 C: sizeof(TIME_ZONE_INFORMATION) = 172 (Unicode 版)

五、回調函數:委派替代函數指標

Win32 回調原型

BOOL EnumDesktops(
    HWINSTA hwinsta,
    DESKTOPENUMPROC lpEnumFunc,  // 回調函數指標
    LPARAM lParam
);

BOOL CALLBACK EnumDesktopProc(
    LPTSTR lpszDesktop,  // 桌面名稱
    LPARAM lParam
);

C# 委派定義

// 委派簽章必須匹配回調(呼叫慣例 StdCall 為預設)
delegate bool EnumDesktopProc(
    [MarshalAs(UnmanagedType.LPTStr)] string desktopName,
    int lParam
);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool EnumDesktops(
    IntPtr windowStation,
    EnumDesktopProc callback,
    int lParam
);

呼叫範例

EnumDesktopProc myCallback = (name, param) => 
{
    Console.WriteLine($"桌面: {name}");
    return true; // 繼續列舉
};

EnumDesktops(IntPtr.Zero, myCallback, 0);

六、委派生命週期陷阱:必須保持參考

封送器建立委派的函數指標傳給非受控代碼,但無法知曉非受控代碼是否會保存指標供未來使用。

危險範例


// 錯誤:委派為區域變數,呼叫後可能被 GC 回收
SetConsoleCtrlHandler((type) => { return true; }, true);

正確做法

// 類別層級欄位保持參考
private static EnumDesktopProc _callbackRef;

public void RegisterCallback()
{
    _callbackRef = MyCallback; // 保持參考
    EnumDesktops(IntPtr.Zero, _callbackRef, 0);
}

private bool MyCallback(string name, int param) { ... }

七、總結

字串封送核心口訣:

  1. 輸入字串:CharSet.Auto + LPTStr(或預設)
  2. 輸出緩衝區:必須用 StringBuilder,預設容量
  3. 結構內嵌字串:StructLayout.CharSet + ByValTStr + SizeConst
  4. 回調委派:類別層級保持參考,防止 GC 回收導致存取違規
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved