P/Invoke 進階主題:處理複雜結構(ACL/ACE)、DllImport/StructLayout 進階屬性、執行時期指定 DLL 載入路徑、常見封送失敗排查。本文為系列第三部分。
Win32 API SetEntriesInAcl 參數包含變長結構:
DWORD SetEntriesInAcl(
ULONG cCountOfExplicitEntries, // 項目數
PEXPLICIT_ACCESS pListOfExplicitEntries, // 緩衝區陣列
PACL OldAcl, // 原始 ACL 指標
PACL* NewAcl // 新 ACL 指標(輸出)
);
ACL 結構僅定義標頭,實際緩衝區包含變長 ACE(Access Control Entry)ACE 有多種型別(ACCESS_ALLOWED_ACE、ACCESS_DENIED_ACE 等),長度各異unsafe 代碼System.Security.AccessControl 命名空間提供 FileSecurity、DirectorySecurity、RawSecurityDescriptor 等高階封裝| 屬性 | 用途 | 關鍵說明 |
|---|---|---|
CallingConvention | 指定呼叫慣例 | 預設 StdCall;C 語言函數需 Cdecl,錯誤會導致堆疊損毀 |
CharSet | 控制 ANSI/Unicode 版本選擇 | Auto(預設)、Ansi、Unicode、None |
EntryPoint | 指定 DLL 實際匯出名稱 | 允許 C# 方法名與 DLL 匯出名不同 |
ExactSpelling | 關閉 A/W 自動查找 | true 時嚴格匹配 EntryPoint |
PreserveSig | 保留原始簽章(不轉 HRESULT) | COM 互操作預設將最後輸出參數轉為回傳值,此屬性關閉該行為 |
SetLastError | 呼叫 Win32 SetLastError | true 允許 Marshal.GetLastWin32Error() 取得錯誤碼 |
[DllImport("kernel32.dll",
CharSet = CharSet.Auto, // 自動選擇 A/W 版本
SetLastError = true, // 保留錯誤碼
CallingConvention = CallingConvention.StdCall, // 明確指定
ExactSpelling = false // 允許 A/W 查找
)]
static extern bool GetDiskFreeSpace(...);
| 屬性 | 用途 | 關鍵說明 |
|---|---|---|
LayoutKind | 記憶體配置模式 | Sequential(預設,按順序)、Explicit(手動指定位移)、Auto(由 CLR 優化,不可用於 P/Invoke) |
CharSet | 結構內字串預設編碼 | 影響 ByValTStr 編碼 |
Pack | 記憶體對齊封裝大小 | 1, 2, 4, 8, 16, 32, 64, 128;對應 C #pragma pack |
Size | 強制結構大小 | 在結構末尾保留額外空間 |
[StructLayout(LayoutKind.Explicit, Size = 8)]
struct MyUnion
{
[FieldOffset(0)] public long AsLong;
[FieldOffset(0)] public int AsIntLow;
[FieldOffset(4)] public int AsIntHigh;
[FieldOffset(0)] public double AsDouble;
}
DllImport 無法直接指定路徑,但可利用 LoadLibrary 特性:
LoadLibrary 直接返回現有模組控制代碼LoadLibrary("完整路徑\\mydll.dll")DllImport 會使用已載入模組[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern IntPtr GetModuleHandle(string lpModuleName);
// 使用範例
IntPtr hMod = LoadLibrary(@"C:\CustomPath\mylib.dll");
if (hMod == IntPtr.Zero)
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
// 此時 DllImport("mylib.dll") 會使用上方載入的模組
若開發類別庫,可在靜態建構函式檢查:
static MyClass()
{
if (GetModuleHandle("mylib.dll") != IntPtr.Zero)
throw new InvalidOperationException("偵測到同名 DLL 已載入,可能被劫持");
}
| # | 常見錯誤 | 原因 | 解決 |
|---|---|---|---|
| 1 | long != long | C++ long=4 bytes,C# long=8 bytes | C++ long 對應 C# int 或 uint |
| 2 | 字串類型錯誤 | MarshalAs 未正確指定 ANSI/Unicode/BSTR | 檢查 CharSet、UnmanagedType.LPStr/LPWStr/LPTStr/BStr |
| 3 | 結構大小不符 | Pack、CharSet、欄位順序、位元組對齊 | 用 Marshal.SizeOf<T>() 驗證,對照 C sizeof |
| 4 | 委派被 GC 回收 | 回調委派未保持參考 | 類別層級欄位保存委派實例 |
| 5 | 堆疊不平衡 | CallingConvention 錯誤 | Win32 API 多為 StdCall,C 語言庫為 Cdecl |
| 6 | 存取違規 | 指標參數 ref/out/IntPtr 錯誤 | 輸入指標用 IntPtr,輸出用 out,輸入輸出用 ref |
P/Invoke 進階關鍵在於:
DllImport/StructLayout 進階屬性,精確控制封送行為LoadLibrary 控制 DLL 載入順序與路徑