卸载使用 DllImport 加载的 DLL

Posted

技术标签:

【中文标题】卸载使用 DllImport 加载的 DLL【英文标题】:Unload a DLL loaded using DllImport 【发布时间】:2011-01-27 13:57:21 【问题描述】:

如何卸载在 C# 中使用 DllImport 加载的 DLL?

【问题讨论】:

如果您打算这样做只是为了节省已加载模块的几 KB 内存占用空间,那么这是不值得的。每当卸载 appDomain 时,CLR 都会自行执行此操作。我很想知道您是否有真正的理由尝试卸载本机 dll。 【参考方案1】:

从由 [DllImport] pinvoke 声明加载的进程中卸载非托管 DLL 的最可靠方法是再次通过 pinvoking LoadLibrary() 自行加载。这为您提供了对 DLL 的可靠句柄,并且即使 DLL 的模块名称不明确,也可以正常工作。它在运行时没有任何影响,除了 Windows 加载程序将 DLL 上的内部引用计数从 1 增加到 2。

然后您可以调用 FreeLibrary() 两次 以将引用计数减少到 0,并将您从 LoadLibrary() 获得的 IntPtr 传递给它。这将卸载 DLL,以及任何已加载的相关 DLL。

请注意,当您尝试在 DLL 上再次调用 any 导出的函数时,您会遇到非常严重的失败,在此之后 any 次。 pinvoke marshaller 不知道 DLL 不再存在,并将在它认为仍然有效的地址处调用函数。如果幸运的话,哪个会用 AccessViolation 异常轰炸你的程序。或者,如果您不那么幸运并且之前由 DLL 占用的地址空间被另一个 DLL 重新使用,则运行一段完全随机的代码。那时什么都有可能发生,都不好。

【讨论】:

你还是没有回答 subbu 的问题。他在问如何卸载由 DllImport 加载的 DLL,而不是通过 LoadLibrary() 手动加载。 看这里codeproject.com/KB/cs/… @Ants,这将卸载由 DllImport 加载的 DLL。 Mitch 通过尝试加载(已经加载的)DLL 来获取 DLL 句柄。然后您调用 FreeLibrary() 两次以删除您刚刚添加的(否则无用的)引用和 DllImport 具有的引用。 在我评论的时候,原来的答案并没有说 FreeLibrary() 应该被调用两次。 为什么 CLR 无法识别我们是否使用本机 FreeLibrary 调用卸载了本机模块。甚至 FreeLibrary 调用也仅通过 P/invoke。不是吗?【参考方案2】:

这应该释放之前在调用 P/Invoke 函数时加载的模块。

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

public static void UnloadModule(string moduleName)

    foreach(ProcessModule mod in Process.GetCurrentProcess().Modules)
    
        if(mod.ModuleName == moduleName)
        
            FreeLibrary(mod.BaseAddress);
        
    

【讨论】:

通常会起作用。但模块名称可能不明确,可能会释放错误的 DLL。 汉斯是对的。所以也许使用 Path.GetFileName(mod.FileName) 而不是 mod.ModuleName 更好? 或者挂在您从 LoadLibrary 获得的指针上,然后将 BaseAddress 与该指针进行比较。 (或者,如果您保存 IntPtr 句柄,则直接卸载句柄而不是迭代模块。)【参考方案3】:

根据 Peters 的建议,这对我有用:

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

    public static void UnloadImportedDll(string DllPath)
    
        foreach (System.Diagnostics.ProcessModule mod in System.Diagnostics.Process.GetCurrentProcess().Modules)
        
            if (mod.FileName == DllPath)
            
                FreeLibrary(mod.BaseAddress);
            
        
    

【讨论】:

【参考方案4】:

自从我在四处寻找信息时发现了这里的信息,我想我会回馈我最终为解决 OSX IN UNITY 上的 Sixense SDK 问题所做的工作。您将在其中看到在 OSX 上动态加载/卸载 dylib 的实现:

https://gist.github.com/amirebrahimi/d7b02c01bcd3ca7da144

【讨论】:

【参考方案5】:

如果你是函数式编程的粉丝,那么你可以使用 LINQ 来实现@IllidanS4 的建议:

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

public static void UnloadModule(string moduleName)

   var loadedAssemblyModule =
            Process.GetCurrentProcess().Modules.OfType<ProcessModule>()
                .FirstOrDefault(x => x.ModuleName == moduleName);

   if (loadedAssemblyModule != null)
       FreeLibrary(loadedAssemblyModule.BaseAddress);

【讨论】:

【参考方案6】:

聚会迟到了,但我已经做了一个工具来做这件事。它应该在 Unity 中运行,但我认为它可以被其他 C# 解决方案采用。可在此处获取:https://github.com/mcpiroman/UnityNativeTool。

请注意,它本质上是一种 hack(但经常使用 hack,请参阅 Harmony),所以我不太建议在生产代码中使用它。

【讨论】:

以上是关于卸载使用 DllImport 加载的 DLL的主要内容,如果未能解决你的问题,请参考以下文章

C#:加载 C++ DLL 时出现问题

C#调用C/C++的dll,一个库中包括多个函数,每调用一个函数都要用DllImport加载吗?求大神

dll

Windows动态链接库DLL

C#中如何动态加载和卸载DLL

使用AppDomain进行动态加载和卸载dll