必须做 FreeLibrary 2 次,尽管我只做了 1 次 LoadLibrary。此外,卸载 DLL 后,再次尝试加载时,会发生错误

Have to do FreeLibrary 2 times although I have done LoadLibrary only 1 time. Also, after unloading DLL, when trying to load it again, error happens

具有以下用于加载和卸载 C++ DLL 的 C# 代码。

我只加载 DLL 一次,但代码必须卸载 DLL 2 次。同样在卸载 DLL 后,当我再次加载它并调用 DLL 的导出函数时,我收到以下错误消息:

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

DLL 依赖于其他 DLL。

    /// //////////////handle of FDD DLL:
    System.IntPtr SystemIntPtr_handle_of_DLL=System.IntPtr.Zero;

    private void button4_Click(object sender, EventArgs e)
    {
        try
        {
            string string_Dependency_path = ".\DLL_Dependencies\";
            Call_DLL.SetDllDirectory(string_Dependency_path);
            SystemIntPtr_handle_of_DLL = Call_DLL.LoadLibrary("DLL.dll");
            if (SystemIntPtr_handle_of_DLL == System.IntPtr.Zero) { throw new Exception("DLL did not load"); }

        }
        catch (Exception Exception_Object) { MessageBox.Show(Exception_Object.Message); }
    }

    private void button5_Click(object sender, EventArgs e)
    {
        try
        {
            int int_FreeLibrary_counter = 0;
            while (Call_DLL.FreeLibrary(SystemIntPtr_handle_of_DLL))
            {
                int_FreeLibrary_counter++;
            }
            MessageBox.Show("DLL unloaded. You will have to load it again. (Unloaded" + int_FreeLibrary_counter + " times)");
        }
        catch (Exception Exception_Object) { MessageBox.Show(Exception_Object.Message); }
    }

调用方法:

class Call_DLL
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool SetDllDirectory(string string_Dependency_path);
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string string_DLL_name);
    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool FreeLibrary(IntPtr IntPtr_handle_of_DLL);
}

编辑

我忘记包括以下调用 DLL 导出函数的内容,在加载 DLL 之后和卸载 DLL 之前。认为这些导出的函数内部发生了其他事情是有道理的,这导致了奇怪的行为(即加载 1 次,必须卸载 2 次):

    [DllImport(@"DLL.dll", EntryPoint = "getFreq")]
    public static extern System.IntPtr getFreq([In, Out, MarshalAs(UnmanagedType.LPStr)] string char_Address, [In, Out, MarshalAs(UnmanagedType.I4)]int int_Num, [In, Out, MarshalAs(UnmanagedType.I4)]int int_Samp);
    [DllImport(@"DLL.dll", EntryPoint = "setNat")]
    public static extern System.IntPtr setNat([In, Out, MarshalAs(UnmanagedType.LPStr)]string char_Address_File_nat);
    [DllImport(@"DLL.dll", EntryPoint = "getMode")]
    public static extern System.IntPtr getMode();

根据 https://msdn.microsoft.com/en-us/library/windows/desktop/ms683152%28v=vs.85%29.aspx(第一个 google 命中)If the function succeeds, the return value is nonzero. 你的卸载循环基本上说 "as long as freeing the library worked, free it again, until the unload fails".

如果您只调用一次 LoadLibrary,那么您也只需调用一次 FreeLibrary。第二次调用它会进入未定义的行为。

如果您查看 MSDN 文档 https://msdn.microsoft.com/en-us/library/windows/desktop/ms683152%28v=vs.85%29.aspx 函数 return 如果成功则为非零值,如果有错误则为 0。

如果您想确保它已卸载,那么您可以在调用 FreeLibrary 之后对其调用 GetModuleHandle,它应该 return 为 null。 https://msdn.microsoft.com/en-us/library/windows/desktop/ms683199(v=vs.85).aspx

更新了您的调用方法 class 以包括:

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);

然后将您的 button5 点击处理程序更改为以下内容:

private void button5_Click(object sender, EventArgs e)
{
    try
    {
        int freeCount = 0;
        while(Call_DLL.GetModuleHandle("DLL.dll") != System.IntPtr.Zero)
        {
             Call_DLL.FreeLibrary(SystemIntPtr_handle_of_DLL);
             freeCount++;
        }

        MessageBox.Show("DLL unloaded. You will have to load it again. (Unloaded" + int_FreeLibrary_counter + " times)");
     }
     catch(Exception Exception_Object)
     {
        MessageBox.Show(Exception_Object.Message);
     }
}

您问题的更新提供了足够的信息来解释该行为。

  1. 您调用 LoadLibrary 加载您的 DLL,该 DLL 占其中一个引用。
  2. 您调用了 DllImport p/invoke 函数,这些函数又导致对 LoadLibrary 的调用。那是对图书馆的另一个参考。
  3. 然后当您调用 FreeLibrary 时,这会成功两次,因为有两次调用 LoadLibrary

但是现在你有麻烦了,因为你已经落后于 p/invoke 系统并且它仍然认为它拥有对 DLL 的引用之一。您在第二次调用 FreeLibrary.

时从中窃取的引用

我想您缺少的信息是 DllImport p/invoke 如何绑定到函数。您希望他们通过调用 GetModuleHandle 获得模块句柄。他们没有。他们打电话给 LoadLibrary。他们在第一次调用时执行此操作,并且他们加载的模块保持加载状态,直到程序集本身卸载。

你最重要的是遵守规则。规则声明对 LoadLibrary 的每次调用都与对 FreeLibrary 的调用相匹配。您调用 LoadLibrary 一次。所以你也必须恰好调用 FreeLibrary 一次。停止调用它两次,一切都很好。

我怀疑您实际上是在尝试安排一个可以加载和卸载 DLL 的系统。这会阻止您通过 DllImport 使用 p/invoke。您必须使用 LoadLibraryGetProcAddress.

完成所有操作

您对 SetDllDirectory 的用法看起来有些混乱。您认为 ".\DLL_Dependencies\" 与什么有关?提供完整路径。

我知道这个问题已经完成了,但是在处理类似的问题时,我找到了一种使用 DllImport 动态加载和卸载库的方法,并且没有异常。

该方法基于以下利益相关者:
- DllImport 在第一次调用包装的方法时加载库。
- 如果库已经加载到堆栈上,对方法的调用将使用库的这个实例而不是加载另一个

基于这些考虑,您可以这样管理您的模块:

  1. 实施您的 class 包装库
    public class MyWrapper
    {
        int _libraryHandle = 0;

        [DllImport("mylibrary.dll")]
        public external static void MyMethod();
    }
  1. 在构造函数中使用 LoadLibrary 加载库:
    public MyWrapper()
    {
        _libraryHandle = LoadLibrary("mylibrary.dll");
    }
  1. 在 dispose 方法中使用 FreeLibrary 卸载库:
    public Dispose()
    {
        FreeLibrary(_libraryHandle);
    }

以这种方式,每次您将实例化您的包装器时,都会毫无例外地在堆栈上加载新出现的库。