使用DllImport加载的DLL如何卸载?

38

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

我该如何卸载已经被加载进来的 DLL?


如果你只是为了节省几个 KB 的内存占用,而试图卸载已加载的模块,那么这并不值得。当应用程序域被卸载时,CLR 会自行处理。我很想知道你是否有一些真正的理由尝试卸载本机 DLL。 - RBT
5个回答

30
使用pinvoke声明加载的非托管DLL从进程中卸载的最可靠方法是通过使用pinvoke LoadLibrary()自行重新加载。这样可以获得一个可靠的DLL句柄,即使DLL的模块名称不明确,也可以正确工作。在运行时它没有任何影响,除了Windows加载器将DLL的内部引用计数从1增加到2。
然后您可以使用LoadLibrary()获得的IntPtr两次调用pinvoke FreeLibrary()以将引用计数减少为0。这将卸载DLL以及加载的所有依赖DLL。
请注意,如果在此之后的任何时间尝试再次调用DLL上的任何导出函数,都会遇到非常严重的故障。pinvoke marshaller不知道DLL已经不存在,并将在其仍然有效的地址处调用该函数。如果幸运的话,这会导致程序出现AccessViolation异常。如果不那么幸运,DLL曾经占用的地址空间已被另一个DLL重新使用,则会运行完全随机的代码。那时可能发生任何事情,这些事情都不好。

4
你还没有回答 Subbu 的问题。他问的是如何卸载通过 DllImport 加载的 DLL,而不是手动通过 LoadLibrary() 加载的 DLL。请回答他的问题。 - Ants
3
@Ants,这将卸载由DllImport加载的DLL。Mitch通过尝试加载(已加载的)DLL来获取DLL句柄。然后,您调用FreeLibrary()两次以摆脱刚刚添加的(否则无用的)引用和DllImport的引用。 - Technophile
当我发表评论时,原始答案没有说FreeLibrary()应该被调用两次。 - Ants
为什么CLR无法识别我们是否使用本机FreeLibrary调用卸载了本机模块,即使FreeLibrary调用也只能通过P/invoke进行。难道不是这样吗? - RBT
因为CLR不知道您是直接手动调用FreeLibrary(),所以它不知道清除它自己在之前加载的任何引用。如果FreeLibrary()是通过GetProcAddress()而不是DllImport被调用呢?那么CLR将不得不安装一个钩子到FreeLibrary()本身来拦截所有调用。 - Remy Lebeau

13

当您调用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);
        }
    }
}

3
通常而言,这会起作用。但模块名称可能会存在歧义,这可能会释放错误的动态链接库(DLL)。 - Hans Passant
韩斯是正确的。因此,也许最好使用 Path.GetFileName(mod.FileName)而不是 mod.ModuleName? - Peter
或者保留从LoadLibrary获取的指针,并将BaseAddress与其进行比较。 - yoyo
(或者,如果您保存了IntPtr句柄,只需直接卸载句柄,而不是迭代模块。) - yoyo
LINQ爱好者的即兴表演 - `var loadedAssemblyModule = Process.GetCurrentProcess().Modules.OfType<ProcessModule>() .FirstOrDefault(x => x.ModuleName == moduleName); if (loadedAssemblyModule != null) FreeLibrary(loadedAssemblyModule.BaseAddress);` - RBT

6

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

    [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);
            }
        }
    }

0
虽然有点晚了,但我已经制作了一个工具来完成这个任务。它应该在Unity中运行,但我认为它可以适用于其他C#解决方案。你可以在这里找到它:https://github.com/mcpiroman/UnityNativeTool
请注意,它本质上是一种hack(但经常被使用的hack,请参见Harmony),因此我不建议在生产代码中使用它。

-2

因为我在寻找信息时遇到了这里的资料,所以我想我将回馈我最终解决 Sixense SDK 在 OSX 上在 Unity 中出现的问题所采取的措施。您将看到其中一个在 OSX 上动态加载/卸载 dylib 的实现:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接