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

C# 動態呼叫 Native DLL 匯出函式:Marshal.GetDelegateForFunctionPointer 實戰

     編輯:關於C語言

從 C++ 到 C#:動態載入 DLL 的演進

在 C++ 中,我們習慣使用 LoadLibrary、GetProcAddress 組合動態載入 DLL 並取得函式位址。C# 2.0 引入 Marshal.GetDelegateForFunctionPointer 後,同樣能在 managed 程式碼中達成此目標,且型別安全。

核心步驟

  1. 定義與目標函式簽章相符的委派型別
  2. P/Invoke 宣告 LoadLibrary、GetProcAddress、FreeLibrary
  3. 取得函式指標並轉換為委派實例
  4. 像呼叫普通方法一樣呼叫該委派
  5. 釋放 DLL 模組控制代碼

完整實作範例

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class DynamicDllInvoker
{
    // 1. 定義委派:對應 MessageBoxA(int hwnd, string lpText, string lpCaption, int uType)
    public delegate int MsgBoxDelegate(int hwnd, string msg, string caption, int type);

    // 2. P/Invoke 宣告 Kernel32 API
    [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
    public static extern IntPtr LoadLibrary(string lpFileName);

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

    [DllImport("kernel32.dll", ExactSpelling = true)]
    public static extern bool FreeLibrary(IntPtr hModule);

    // 3. 封裝:取得委派實例
    private static Delegate GetFunctionDelegate(IntPtr moduleHandle, string functionName, Type delegateType)
    {
        IntPtr funcPtr = GetProcAddress(moduleHandle, functionName);
        if (funcPtr == IntPtr.Zero)
            return null;
        
        return Marshal.GetDelegateForFunctionPointer(funcPtr, delegateType);
    }

    // 4. 實際呼叫範例
    public void CallMessageBox(IntPtr ownerHandle, string text, string caption, int buttons)
    {
        IntPtr hUser32 = LoadLibrary("user32.dll");
        if (hUser32 == IntPtr.Zero)
        {
            MessageBox.Show("無法載入 user32.dll");
            return;
        }

        try
        {
            MsgBoxDelegate msgBox = (MsgBoxDelegate)GetFunctionDelegate(hUser32, "MessageBoxA", typeof(MsgBoxDelegate));
            if (msgBox != null)
            {
                msgBox(ownerHandle.ToInt32(), text, caption, buttons);
            }
            else
            {
                MessageBox.Show("找不到 MessageBoxA 函式");
            }
        }
        finally
        {
            FreeLibrary(hUser32);
        }
    }
}

// 使用方式:
// var invoker = new DynamicDllInvoker();
// invoker.CallMessageBox(this.Handle, "動態呼叫成功!", "測試", 0x40); // MB_ICONINFORMATION

關鍵技術細節

項目說明
Calling ConventionWin32 API 預設為 StdCall,C# 委派預設即為 StdCall,通常無需額外指定
字串編碼MessageBoxA 為 ANSI 版本,委派參數用 string 會自動轉換;若呼叫 MessageBoxW 需加 [MarshalAs(UnmanagedType.LPWStr)]
指標大小使用 IntPtr 接收 LoadLibrary/GetProcAddress 回傳值,支援 32/64 位元自動適配
委派存活期委派實例必須在 DLL 卸載前保持存活,否則 GC 可能回收導致存取違規

進階應用場景

  • 外掛架構:執行時動態發現並載入擴充 DLL
  • 相容性處理:根據 OS 版本載入不同版本的系統 DLL
  • 效能優化:延遲載入非必要的大型 Native 函式庫
  • 測試替身:單元測試時動態替換 Native 相依性

注意事項

  • 委派簽章必須與 Native 函式完全一致(參數順序、型別、呼叫慣例)
  • 錯誤處理:檢查 LoadLibrary、GetProcAddress 回傳值,避免 NullReferenceException
  • 記憶體管理:確保 FreeLibrary 在 finally 區塊呼叫,防止模組洩漏
  • 安全性:僅載入受信任的 DLL,避免惡意程式碼注入
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved