如何从 SQL 服务器进程卸载本机 DLL(使用 [DllImport()] 属性加载)?

How to unload native DLL (loaded using [DllImport()] attribute) from SQL Server process?

我创建了一个调用本机 C++ DLL 的 SQLCLR 用户定义函数。

本机 C++ DLL 是我的,如果我想对其进行更改,我需要停止 sqlservr 进程以复制新的。在生产中这是不可接受的。

即使我删除了使用该 DLL 的程序集,该文件仍在使用中。

如何覆盖本机 DLL?

编辑

DLL 方法声明:

[DllImport("Library64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr GetTiming();

MS 似乎在第一次调用它时加载库,并且似乎在进程终止时卸载....:)

听起来 "reference" 正被 App Domain 持有。您可以尝试卸载应用程序域,这应该会清除它(最好的猜测,因为我无法对此进行测试)。您可以通过对数据库进行任何安全更改来做到这一点。以下作品:

ALTER DATABASE {db_name} SET TRUSTWORTHY ON (or OFF if already ON);
GO
ALTER DATABASE {db_name} SET TRUSTWORTHY OFF (or ON if already OFF);
GO

请记住,这将卸载该特定数据库中的所有 AppDomain。这通常不是问题,因为人们很少在单个数据库中拥有多个应用程序域(这需要程序集由不同的用户拥有,而大多数人只使用 dbo)。


要查看存在哪些应用程序域以及其中加载了哪些程序集,运行以下内容:

SELECT DB_NAME(dca.[db_id]) AS [DatabaseName], dca.*, '---' AS [---], dcla.*
FROM sys.dm_clr_appdomains dca
INNER JOIN sys.dm_clr_loaded_assemblies dcla
        ON  dca.appdomain_address = dcla.appdomain_address
WHERE dca.[db_id] <> 32767;

如果该查询未返回任何内容并且您仍然无法替换该外部 DLL,请尝试以下操作(这似乎有点多,但我们需要在尝试其他任何操作之前知道它是否有效):

sp_configure 'clr enabled', 0;
RECONFIGURE;
GO
sp_configure 'clr enabled', 1;
RECONFIGURE;

另外两个可供尝试的选项是:

  • 创建一个您将通过 DLLImport 调用的包装器 DLL。它会调用你的 Library64.dll

  • 最后的手段是通过另一个非托管调用强制删除非托管 DLL。您没有使用 LoadLibrary() 创建 DLL,但您应该能够使用 GetModuleHandleExA() 获取对它的引用,然后在对 FreeLibrary() 的调用中使用该句柄。这个在下面的博客post中有描述:PInvoke Library Load/Unload Behavior – Freeing Unmanaged Libraries. It seems this was the only method to be successful. Please see @John's 具体代码

这是我所做的并且似乎有效。请告诉我这样做是否错误..

我添加了两个额外的 CLR 存储过程,如下所示:

[SqlProcedure]
public static void asdUnloadLibrary()
{
    try
    {
        var hMod = IntPtr.Zero;
        if (GetModuleHandleExA(0, "Engine64.dll", ref hMod))
        {
            while (FreeLibrary(hMod))
            { }
        }
        else
        {
            throw new Exception("Library not found");
        }
    }
    catch (Exception e)
    {
        throw e;
    }
    return;
}

[SqlProcedure]
public static void asdLoadLibrary()
{
    try
    {
        var hMod = IntPtr.Zero;
        LoadLibrary("Engine64.dll"); 
    }
    catch (Exception e)
    {
        throw e;
    }
    return;
}

现在...如果我想将新的本机 DLL 文件复制到服务器,我将:

1) Execute asdUnloadLibrary stored procedure. This will unload the dll.
2) Then, I can copy another version of dll to system folder
3) Then I can (but I think it is not necessary) do it:

ALTER DATABASE TEST SET TRUSTWORTHY OFF
GO
ALTER DATABASE TEST SET TRUSTWORTHY ON
GO

4) Execute asdLoadLibrary

现在原始 UDF 函数再次按预期运行...