P/Invoke 字串處理是最易出錯的領域。本文深入解析:ANSI/Unicode 雙版本機制、CharSet.Auto 自動選擇、StringBuilder 作為輸出緩衝區、結構內嵌字串陣列封送、委派實現回調函數指標。
FunctionAFunctionWTCHAR、_UNICODE 宏控制編譯版本Function,無則嘗試 FunctionA/FunctionWBOOL GetDiskFreeSpace(
LPCTSTR lpRootPathName, // 平台無關字串指標
LPDWORD lpSectorsPerCluster,
LPDWORD lpBytesPerSector,
LPDWORD lpNumberOfFreeClusters,
LPDWORD lpTotalNumberOfClusters
);
[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),但會雙向轉換損失效能.NET string 不可變,無法作為輸出緩衝區。StringBuilder 專為可變緩衝區設計。
[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 = 緩衝區大小(含結束字元)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;
[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;
}
StructLayout.CharSet = Unicode:結構內所有 ByValTStr 強制 Unicode(Win32 用 WCHAR),避免預設 ANSI 導致大小不符MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32):內嵌固定長度字串陣列,SizeConst 必須匹配原生定義// 驗證結構大小
int size = Marshal.SizeOf();
Console.WriteLine($"Managed size: {size}");
// 對照 C: sizeof(TIME_ZONE_INFORMATION) = 172 (Unicode 版)
BOOL EnumDesktops(
HWINSTA hwinsta,
DESKTOPENUMPROC lpEnumFunc, // 回調函數指標
LPARAM lParam
);
BOOL CALLBACK EnumDesktopProc(
LPTSTR lpszDesktop, // 桌面名稱
LPARAM lParam
);
// 委派簽章必須匹配回調(呼叫慣例 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) { ... }
字串封送核心口訣:
CharSet.Auto + LPTStr(或預設)StringBuilder,預設容量StructLayout.CharSet + ByValTStr + SizeConst