遇過這樣的需求:第三方 C++ DLL 內部有個靜態計數器,每次呼叫遞增。業務要求在特定時刻將計數器歸零,但 DLL 原始碼不可改。C# 直接 DllImport 只能載入一次,無法像 AppDomain 那樣卸載重載。解法:繞過 .NET 載入器,直接呼叫 Windows API 手動管理模組生命週期。
[DllImport("kernel32", EntryPoint = "LoadLibrary", SetLastError = true)]
static extern IntPtr LoadLibrary(string lpLibName);
[DllImport("kernel32", EntryPoint = "GetProcAddress", SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
static extern bool FreeLibrary(IntPtr hModule);
拿到 GetProcAddress 回傳的函數指標 IntPtr pfn 後,C# 無法直接像 C++ 那樣轉型呼叫。靈感來自 calli IL 指令:寫一個極小的 IL 組件 Wrapper.il,定義靜態方法接受 native int 並 calli unmanaged stdcall int32(),編譯成 DLL 後在 C# 專案參考即可。
.assembly extern mscorlib {}
.assembly Wrapper {}
.class public Wrapper {
.method public static int32 SomeMethod(native int pfn) {
.maxstack 2
.locals (int32 V_0)
ldarg.0
calli unmanaged stdcall int32()
stloc.0
ldloc.0
ret
}
}
C# 呼叫端:
IntPtr hMod = LoadLibrary("ADll.dll");
IntPtr pfn = GetProcAddress(hMod, "GetValue");
int result = Wrapper.SomeMethod(pfn); // 成功呼叫!
MessageBox.Show(result.ToString());
FreeLibrary(hMod); // 卸載,下次 LoadLibrary 會重新初始化靜態變數
注意:須確保呼叫慣例(stdcall/cdecl)與 DLL 導出一致,否則堆疊會損毀。