You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#插件中卸载DllImport导入的C++ DLL以支持重编译

解决C++ DLL被进程占用导致无法重新编译的问题

核心问题分析

通过[DllImport]导入的非托管C++ DLL会被加载到第三方应用的进程空间,CLR不会自动卸载它——哪怕你的C#插件DLL被卸载,非托管DLL仍会保持加载状态,导致编译器无法覆盖文件。要解决这个问题,必须手动控制非托管DLL的加载与卸载生命周期。

具体实现方案

1. 替换[DllImport]为手动加载/卸载逻辑

使用Windows原生APILoadLibrary、GetProcAddress和FreeLibrary来手动管理C++ DLL的加载与释放,替代[DllImport]的自动机制:

using System;
using System.Runtime.InteropServices;

public class CppDllWrapper
{
    // 导入Windows内核API
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr LoadLibrary(string lpFileName);

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

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

    private IntPtr _dllHandle;
    // 定义与C++函数匹配的委托类型
    private delegate int SampleCppFuncDelegate(int input);
    private SampleCppFuncDelegate _sampleFunc;

    // 加载C++ DLL并绑定目标函数
    public bool Initialize(string dllPath)
    {
        _dllHandle = LoadLibrary(dllPath);
        if (_dllHandle == IntPtr.Zero)
        {
            // 可添加错误日志逻辑
            return false;
        }

        // 获取函数地址并转换为委托
        IntPtr funcPtr = GetProcAddress(_dllHandle, "SampleCppFunction");
        if (funcPtr == IntPtr.Zero)
        {
            FreeLibrary(_dllHandle);
            return false;
        }
        _sampleFunc = Marshal.GetDelegateForFunctionPointer<SampleCppFuncDelegate>(funcPtr);
        return true;
    }

    // 包装C++函数调用
    public int CallSampleFunction(int input)
    {
        return _sampleFunc?.Invoke(input) ?? throw new InvalidOperationException("C++ DLL未加载");
    }

    // 卸载C++ DLL
    public void Cleanup()
    {
        if (_dllHandle != IntPtr.Zero)
        {
            FreeLibrary(_dllHandle);
            _dllHandle = IntPtr.Zero;
            _sampleFunc = null;
        }
    }
}

2. 在插件卸载时触发DLL释放

确保你的C#插件实现第三方应用提供的卸载回调接口(比如IPlugin.Unload),在回调中调用Cleanup方法:

public class YourCSharpPlugin : IPlugin
{
    private CppDllWrapper _cppWrapper;

    public void OnLoad()
    {
        _cppWrapper = new CppDllWrapper();
        _cppWrapper.Initialize(@"C:\Path\To\Your\cpp.dll");
    }

    public void OnUnload()
    {
        _cppWrapper?.Cleanup();
        // 其他插件资源清理逻辑
    }
}

3. 关键注意事项

  • 清除所有委托引用:如果代码中还有其他地方持有C++函数的委托引用,FreeLibrary可能无法真正释放DLL,需确保所有相关委托在卸载前被置为null。
  • 检查FreeLibrary返回值:若返回false,说明DLL的引用计数仍不为0,可能存在其他组件占用,可通过Marshal.GetLastWin32Error()获取错误码排查。
  • 避免静态引用:不要在静态类或静态字段中保存DLL句柄或委托,否则静态引用会阻止GC清理,导致FreeLibrary失效。

备选方案:加载临时副本规避文件占用

如果手动管理句柄仍有问题,可在插件加载时将C++ DLL复制到临时目录,加载临时副本。这样原文件不会被占用,编译时可直接覆盖:

public bool InitializeFromTemp(string originalDllPath)
{
    string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    Directory.CreateDirectory(tempDir);
    string tempDllPath = Path.Combine(tempDir, Path.GetFileName(originalDllPath));
    File.Copy(originalDllPath, tempDllPath, overwrite: true);
    return LoadLibrary(tempDllPath) != IntPtr.Zero;
}

注意插件卸载时需删除临时目录,避免冗余文件堆积。


内容的提问来源于stack exchange,提问作者chrisk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 07:37:27