C# 加载动态加载卸载DLL 超过两次直接崩溃返回内存已损坏 (-1073740940)

问题遇到的现象和发生背景

我的程序需要动态加载DLL,这本来是一个非常简单的事。但是我遇到了一点问题,我定义了一个Plugin类型
用来存放DLL 里面封装了 LoadLibrary、FreeLibrary 和一些简单的通信功能。
当我尝试卸载它们的时候,程序直接报错(并没有报错
直接返回了 程序“[20112] Lang.exe”已退出,返回值为 3221226356 (0xc0000374)。
这就很奇怪了,我新建了一个程序测试了无数遍 都没有发现这个奇怪的现象

img

Ps:虽然名字一样但是都是不同的DLL

经过我的调试每次出错都在此处(plugin的Dispose)

img


这是我的部分代码

报错的地方有"<标记>" 或许你可以直接Ctrl+F找"<标记>"

这里是我加载DLL的地方

using CSharpClient;
using Lang.Api;
using PInvoke;
using Lang.Entity;
using System.Runtime.InteropServices;
using static Lang.Plugin.SiriusApiDelegates;
using static Lang.Log.Log;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Lang.Plugin
{
    public static class SiriusEvent
    {
        public static void SiriusOnEvent(string data)
        {
            LogOut(data);
        }
    }
    public static class PluginManager
    {
        public static readonly Mutex eventMutex = new(false);
        public static Mutex EventMutex { get => eventMutex; }
        public static List<Plugin> plugins = new List<Plugin>();

        private static bool FileCompare(string file1, string file2)
        {
            int file1byte;
            int file2byte;
            FileStream fs1;
            FileStream fs2;

            // Determine if the same file was referenced two times.
            if (file1 == file2)
            {
                // Return true to indicate that the files are the same.
                return true;
            }

            // Open the two files.
            fs1 = new FileStream(file1, FileMode.Open);
            fs2 = new FileStream(file2, FileMode.Open);

            // Check the file sizes. If they are not the same, the files
            // are not the same.
            if (fs1.Length != fs2.Length)
            {
                // Close the file
                fs1.Close();
                fs2.Close();

                // Return false to indicate files are different
                return false;
            }

            // Read and compare a byte from each file until either a
            // non-matching set of bytes is found or until the end of
            // file1 is reached.
            do
            {
                // Read one byte from each file.
                file1byte = fs1.ReadByte();
                file2byte = fs2.ReadByte();
            }
            while ((file1byte == file2byte) && (file1byte != -1));

            // Close the files.
            fs1.Close();
            fs2.Close();

            // Return the success of the comparison. "file1byte" is
            // equal to "file2byte" at this point only if the files are
            // the same.
            return ((file1byte - file2byte) == 0);
        }

        private static bool FilesCompare(FileInfo target, List<FileInfo> files)
        {
            bool compare = false;
            foreach (FileInfo f in files)
            {
                if (FileCompare(target.FullName, f.FullName))
                {
                    compare = true;
                    break; 
                }
            }
            return compare;
        }
        public static void LoadPlugin()
        {
            var path = $"{AppDomain.CurrentDomain.SetupInformation.ApplicationBase}Plugin";
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);

            DirectoryInfo directoryInfo = new(path);
            FileInfo[] files = directoryInfo.GetFiles();//文件

            List<FileInfo> pluginFiles = new();

            foreach (FileInfo file in files)
            {
                if (!FilesCompare(file, pluginFiles))
                {
                    pluginFiles.Add(file);
                }
                else
                {
                    LogWar($"存在完全相同的文件:{file.FullName},已跳过");
                }
            }

            foreach(FileInfo pluginFile in pluginFiles)
            {

                if (Path.GetExtension(pluginFile.FullName).ToLower() == ".dll")
                {
                    Plugin plugin = new(pluginFile.FullName);
                    if (!plugin.Refuse)
                    {
                        if (null != plugin.PluginInfo)
                        {
                            LogOk($"{plugin.PluginInfo.pluginName} 加载完毕," +
                            $"作者:{plugin.PluginInfo.pluginAuthor} " +
                            $"版本:{plugin.PluginInfo.pluginVersion}," +
                            $"描述:{plugin.PluginInfo.pluginDis}," +
                            $"SDK版本:{plugin.PluginInfo.pluginSDKVer}");
                            plugins.Add(plugin);
                        }
                        else
                        {
                            LogErr(Path.GetFileName($"{pluginFile.Name}未公开AppInfo,加载失败!"));
                            plugin.Dispose(); // 执行多次时.. 崩溃 <标记>
                        }
                    }
                    else
                    {
                        if (null != plugin.PluginInfo)
                        {
                            LogErr(Path.GetFileName($"{plugin.PluginInfo.pluginName}插件主动拒绝被加载."));
                        }
                        else
                        {
                            LogErr(Path.GetFileName($"{pluginFile.Name}未公开AppInfo,加载失败!"));
                        }
                        RemovePlugin(plugin); // 执行多次时.. 崩溃 <标记>
                    }
                }

            }

        }
        public static void RemovePlugin(Plugin plugin)
        {
            EventMutex.WaitOne();
            plugins.Remove(plugin);
            plugin.Dispose(); // 崩溃 <标记>
            EventMutex.ReleaseMutex();
        }
        public static void RemovePlugin(int index)
        {
            try
            {
                EventMutex.WaitOne();
                var plugin = plugins[index];
                plugin.Dispose(); // 崩溃 <标记>
                File.Delete(plugin.pluginFilePath);
                LogOk(plugin.PluginInfo.pluginName + "->卸载成功!");
                plugins.Remove(plugin);
                EventMutex.ReleaseMutex();
            }
            catch (Exception e)
            {
                LogErr("卸载插件失败:" + e);
            }
        }
    }
    public static class SiriusApi
    {
    }
    public static class SiriusApiDelegates
    {
    }
    public static class SiriusTools
    {
        public static string GetRandomKey()
        {
            var random = new Random();
            const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            return new string(Enumerable.Repeat(chars, 32)
              .Select(s => s[random.Next(s.Length)]).ToArray());
        }
    }
    /// <summary>
    /// 用户自定义插件信息
    /// 适用于x86的
    /// 易语言 C++ 等...
    /// </summary>
    public class PluginInfo
    {
        [JsonPropertyName("name")]
        public string pluginName { get; set; }
        [JsonPropertyName("author")]
        public string pluginAuthor { get; set; }
        [JsonPropertyName("dis")]
        public string pluginDis { get; set; }
        [JsonPropertyName("ver")]
        public string pluginVersion { get; set; }
        [JsonPropertyName("sdk")]
        public string pluginSDKVer { get; set; }
    }
}


这里是我定义的Plugin

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lang.Plugin.Interface;
using PInvoke;

namespace Lang.Plugin
{
    public class Plugin : IPlugin
    {
        public readonly Kernel32.SafeLibraryHandle handle;
        private readonly PluginFunc pluginFunc;
        public bool Refuse { get; set; }
        public PluginInfo PluginInfo { get; set; }
        public string pluginFilePath { get; set; }
        public string pluginConfigPath { get; set; }
        public string pluginKey { get; set; }
        public Plugin(string path)
        {
            try
            {
                handle = Kernel32.LoadLibrary(path);
                pluginFunc = PluginFunc.CreatePluginFunction(handle);
                pluginKey = SiriusTools.GetRandomKey();
                PluginInfo = pluginFunc.OnInfo()!;
                pluginFunc.OnInit(pluginKey, PluginData.CreatePluginData(PluginInfo.pluginName));
                Refuse = pluginFunc.OnLoad() == 1;
                pluginFilePath = path;
            }catch(Exception ex)
            {
                Log.Log.LogErr($"载入插件时出错! {ex}");
                Refuse = true;
            }
        }
        public void Dispose()
        {
            PluginManager.eventMutex.WaitOne();
            GC.SuppressFinalize(this);
            OnStop();
            handle.Dispose();// 这里才是真正的卸载销毁 最后崩溃都会在此处 <标记>
            PluginManager.eventMutex.ReleaseMutex();
        }
    }
}

此问题已经解决了
问题是调用DLL中返回的String没有进行深拷贝

我将PluginInfo这里 DLL中的返回值是文本(char*) 我将C#中的Delegate返回值从string改为了IntPtr

    public class PluginDelegates
    {
        public delegate void Init(string key, string runPath); //初始化插件
        public delegate IntPtr AppInfo(); //初始化插件 -> 返回插件信息
        public delegate void Menu();// 插件菜单
        public delegate int OnLoad();// 加载插件
        public delegate void OnUnLoad();// 卸载插件
        public delegate void OnGuildMessage(string jsonData); // 收到消息
        public delegate int OnEnable();
        public delegate void OnDisable();
        public delegate void OnCreateGuild(string jsonData);
        public delegate void OnDeleteGuild(string jsonData);
        public delegate void OnUpDateGuild(string jsonData);
        public delegate int OnCommand(string command);
    }

注意看AppInfo 原来是String

随后调用解析时 使用

var data = appInfo();
Marshal.PtrToStringAnsi(data)

即可

不用手动dispose 只要继承了Idispose接口你不用他他一会自己释放内存了
这样操作容易对已经释放的对象操作,就会异常

handle.dispose 是否被多次调用;
dll中是否有循环语句或者写文件的语句正在执行时就被 handle.dispose.

Kernel32.SafeLibraryHandle 这个类型的定义是怎么定义的?据我所知调用winapi LoadLibrary 之后返回的这个句柄在C#里面对应的类型应该是 IntPtr,但是IntPtr这个类并没有继承IDispose,所以不能直接调用 handle.Dispose()。所以让帮忙找问题的话贴一下你这个Kernel32类的定义吧,要看下你是怎么调用winapi LoadLibrary的,而且也要看下你这个Kernel32.SafeLibraryHandle是怎么封装的。另外提醒一下,有现成的轻量级插件式MEF框架可以使用,而且也是开源的可以自己部分修改为自己的需求,不需要自己造轮子。