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

C# 使用 Win32 類別庫進階(三):複雜結構封送、屬性選項、DLL 載入技巧、疑難排解

     編輯:關於C語言

前言

P/Invoke 進階主題:處理複雜結構(ACL/ACE)、DllImport/StructLayout 進階屬性、執行時期指定 DLL 載入路徑、常見封送失敗排查。本文為系列第三部分。

一、複雜結構封送挑戰:ACL/ACE 範例

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 等),長度各異
  • C# 需手動配置記憶體、計算位移、填充結構,極易出錯且屬 unsafe 代碼

建議策略

  1. 優先使用 .NET 原生 API:System.Security.AccessControl 命名空間提供 FileSecurity、DirectorySecurity、RawSecurityDescriptor 等高階封裝
  2. 必要時用 C++/CLI 封裝:寫 C++ 封裝層編譯為混合模式組件,C# 呼叫 managed wrapper
  3. 避免純 C# 手工封送複雜變長結構:維護成本極高

二、DllImport 進階屬性完整表

屬性用途關鍵說明
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 SetLastErrortrue 允許 Marshal.GetLastWin32Error() 取得錯誤碼

最佳實務

[DllImport("kernel32.dll", 
    CharSet = CharSet.Auto,           // 自動選擇 A/W 版本
    SetLastError = true,              // 保留錯誤碼
    CallingConvention = CallingConvention.StdCall, // 明確指定
    ExactSpelling = false             // 允許 A/W 查找
)]
static extern bool GetDiskFreeSpace(...);

三、StructLayout 進階屬性

屬性用途關鍵說明
LayoutKind記憶體配置模式Sequential(預設,按順序)、Explicit(手動指定位移)、Auto(由 CLR 優化,不可用於 P/Invoke)
CharSet結構內字串預設編碼影響 ByValTStr 編碼
Pack記憶體對齊封裝大小1, 2, 4, 8, 16, 32, 64, 128;對應 C #pragma pack
Size強制結構大小在結構末尾保留額外空間

Explicit 佈局範例(模擬 Union)

[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;
}

四、執行時期指定 DLL 載入路徑

DllImport 無法直接指定路徑,但可利用 LoadLibrary 特性:

  1. 進程中已載入同名 DLL → LoadLibrary 直接返回現有模組控制代碼
  2. 先手動 LoadLibrary("完整路徑\\mydll.dll")
  3. 後續 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 已載入,可能被劫持");
}

五、P/Invoke 疑難排解清單

#常見錯誤原因解決
1long != longC++ long=4 bytes,C# long=8 bytesC++ 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 進階關鍵在於:

  • 複雜結構優先用 .NET 原生封裝或 C++/CLI,避免手工封送
  • 精通 DllImport/StructLayout 進階屬性,精確控制封送行為
  • 善用 LoadLibrary 控制 DLL 載入順序與路徑
  • 建立疑難排解檢查清單,快速定位封送錯誤
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved