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

C# 動態呼叫 Windows DLL (P/Invoke 進階:Run-time 動態載入與 IL 發射)

     編輯:關於C#

一般 P/Invoke 使用 [DllImport] 靜態宣告,但若 DLL 路徑、函數名稱在執行期才決定,或需呼叫非標準呼叫慣例的函數,可使用 LoadLibrary/GetProcAddress 取得函數指標,再透過 DynamicMethod 或 AssemblyBuilder 發射 IL 動態建立委派進行呼叫。本文提供完整可重用的 WinDllInvoke 方法。

適用場景

  • DLL 為選用元件,執行期才決定載入哪個版本/路徑。
  • 函數名稱含特殊字元或依配置動態決定。
  • 需支援多種呼叫慣例(`stdcall`、`cdecl`、`thiscall`)。
  • 避免靜態 DllImport 導致啟動時找不到 DLL 拋出例外。

核心實作 WinDllInvoke

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

public static class DynamicDllInvoker
{
    [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
    private static extern IntPtr LoadLibrary(string lpLibFileName);

    [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

    [DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
    private static extern bool FreeLibrary(IntPtr hModule);

    /// 
    /// 動態載入 DLL 並呼叫指定函數
    /// 
    /// DLL 完整路徑或檔名(需在 PATH 中)
    /// 函數名稱(區分大小寫)
    /// 引數陣列(順序需與原生簽章一致)
    /// 回傳型別
    /// 函數回傳值
    public static object Invoke(string dllPath, string funcName, object[] args, Type returnType)
    {
        IntPtr hMod = LoadLibrary(dllPath);
        if (hMod == IntPtr.Zero)
            throw new DllNotFoundException($"無法載入 DLL: {dllPath}");

        IntPtr procAddr = GetProcAddress(hMod, funcName);
        if (procAddr == IntPtr.Zero)
        {
            FreeLibrary(hMod);
            throw new EntryPointNotFoundException($"找不到函數: {funcName}");
        }

        try
        {
            // 1. 建立參數型別陣列
            Type[] paramTypes = new Type[args.Length];
            for (int i = 0; i < args.Length; i++)
                paramTypes[i] = args[i]?.GetType() ?? typeof(object);

            // 2. 發射動態方法
            var dm = new DynamicMethod(
                "DynamicInvoke", returnType, paramTypes, typeof(DynamicDllInvoker).Module, true);
            ILGenerator il = dm.GetILGenerator();

            // 3. 將引數推入堆疊
            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (paramTypes[i].IsValueType)
                    il.Emit(OpCodes.Ldarg, i);       // by value
                else
                    il.Emit(OpCodes.Ldarga, i);      // by reference (address)
            }

            // 4. 載入函數指標 (視平台 32/64 位元)
            if (IntPtr.Size == 4)
                il.Emit(OpCodes.Ldc_I4, procAddr.ToInt32());
            else
                il.Emit(OpCodes.Ldc_I8, procAddr.ToInt64());

            // 5. 發出 calli (stdcall)
            var cc = CallingConventions.Standard;
            il.EmitCalli(OpCodes.Calli, cc, returnType, paramTypes);
            il.Emit(OpCodes.Ret);

            // 6. 建立委派並呼叫
            var del = dm.CreateDelegate(Expression.GetDelegateType(paramTypes.Concat(new[] { returnType }).ToArray()));
            return del.DynamicInvoke(args);
        }
        finally
        {
            FreeLibrary(hMod);
        }
    }
}

使用範例:呼叫 Kernel32.dll Beep

// 靜態簽章對應: BOOL Beep(DWORD dwFreq, DWORD dwDuration)
object result = DynamicDllInvoker.Invoke(
    "kernel32.dll", "Beep",
    new object[] { 750u, 300u }, // uint 對應 DWORD
    typeof(bool)                 // 回傳 BOOL -> bool
);
Console.WriteLine("Beep 回傳: " + result);

關鍵細節說明

項目說明
DynamicMethod vs AssemblyBuilderDynamicMethod 輕量、無需儲存組件、效能優,適合單次/少量呼叫;AssemblyBuilder 可持久化、適合大量重複呼叫。
呼叫慣例Windows API 多為 StdCall (C# 預設)。C 執行期庫函數多為 Cdecl,需改 CallingConventions.Cdecl。
參數封送範例僅處理基本值型別/參考型別。字串需 Marshal.StringToHGlobalAnsi/Uni、結構體需 Marshal.StructureToPtr、陣列需固定記憶體。
32/64 位元指標大小由 IntPtr.Size 判斷,IL 中分別用 Ldc_I4/Ldc_I8 載入。
例外處理原生例外 (SEH) 不會自動轉為 managed exception,需自行檢查回傳值或 GetLastError。

現代替代方案

  • NativeLibrary ( .NET Core 3.0+ ):NativeLibrary.Load、GetExport 取得函數指標,搭配 Marshal.GetDelegateForFunctionPointer 直接轉委派,無需手寫 IL。
  • LibraryImport ( .NET 7+ ):Source Generator 自動產生 P/Invoke 包裝,效能最佳、支援 StringMarshalling、ArrayMarshalling 等屬性。

結語

動態 DLL 呼叫屬於進階互通性技術,掌握 LoadLibrary/GetProcAddress + DynamicMethod/Delegate 流程,能在插件架構、選用元件、跨版本相容等場景提供極大彈性。現代 .NET 專案優先考慮 NativeLibrary 或 LibraryImport,僅在舊版框架或極特殊需求下使用本文 IL 發射方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved