我的程序需要动态加载DLL,这本来是一个非常简单的事。但是我遇到了一点问题,我定义了一个Plugin类型
用来存放DLL 里面封装了 LoadLibrary、FreeLibrary 和一些简单的通信功能。
当我尝试卸载它们的时候,程序直接报错(并没有报错
直接返回了 程序“[20112] Lang.exe”已退出,返回值为 3221226356 (0xc0000374)。
这就很奇怪了,我新建了一个程序测试了无数遍 都没有发现这个奇怪的现象
经过我的调试每次出错都在此处(plugin的Dispose)
报错的地方有"<标记>" 或许你可以直接Ctrl+F找"<标记>"
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; }
}
}
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框架可以使用,而且也是开源的可以自己部分修改为自己的需求,不需要自己造轮子。