#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
// QQ群:605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.DependencyInjection;
using Furion.FriendlyException;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Data;
using System.Reflection;
using System.Runtime.Loader;
using ThingsGateway.Foundation.Extension.String;
using Yitter.IdGenerator;
namespace ThingsGateway.Gateway.Application;
///
/// 驱动插件服务
///
public class PluginSingletonService : ISingleton
{
private readonly ILogger _logger;
///
public PluginSingletonService(ILogger logger)
{
_logger = logger;
}
///
/// 插件文件路径/插件程序集
///
public ConcurrentDictionary AssemblyDict { get; private set; } = new();
///
/// 插件文件路径/插件域
///
public ConcurrentDictionary AssemblyLoadContextDict { get; private set; } = new();
///
/// 插件ID/插件Type
///
public ConcurrentDictionary DriverPluginDict { get; private set; } = new();
///
/// 获取插件
///
///
///
public DriverBase GetDriver(DriverPlugin plugin)
{
lock (this)
{
//添加默认上下文中继承类获取
switch (plugin.DriverTypeEnum)
{
case DriverEnum.Collect:
var driverType = App.EffectiveTypes.Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
if (driverType != null)
{
return GetDriver(plugin, driverType);
}
break;
case DriverEnum.Upload:
var upLoadType = App.EffectiveTypes.Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
if (upLoadType != null)
{
return GetDriver(plugin, upLoadType);
}
break;
}
//先判断是否已经拥有插件模块
if (DriverPluginDict.ContainsKey(plugin.Id))
{
var driver = (DriverBase)Activator.CreateInstance(DriverPluginDict[plugin.Id]);
driver.DriverPlugin = plugin;
return driver;
}
Assembly assembly = null;
_logger?.LogInformation($"添加插件文件:{plugin.FilePath}");
//根据路径获取dll文件
//主程序集路径
var path = AppContext.BaseDirectory.CombinePathOS(plugin.FilePath);
//全部程序集路径
List paths = new();
Directory.GetFiles(Path.GetDirectoryName(path), "*.dll").ToList().
ForEach(a => paths.Add(a.Replace("\\", "/")));
if (AssemblyDict.ContainsKey(plugin.FilePath))
{
assembly = AssemblyDict[plugin.FilePath];
}
else
{
//新建插件域,并注明可卸载
var assemblyLoadContext = new AssemblyLoadContext(plugin.Id.ToString(), true);
//获取插件程序集
assembly = GetAssembly(path, paths, assemblyLoadContext);
//添加到全局对象
AssemblyLoadContextDict.TryAdd(plugin.FilePath, assemblyLoadContext);
AssemblyDict.TryAdd(plugin.FilePath, assembly);
}
if (assembly != null)
{
//根据采集/上传类型获取实际插件类
switch (plugin.DriverTypeEnum)
{
case DriverEnum.Collect:
var driverType = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
if (driverType != null)
{
return GetDriver(plugin, driverType);
}
break;
case DriverEnum.Upload:
var upLoadType = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
if (upLoadType != null)
{
return GetDriver(plugin, upLoadType);
}
break;
}
throw new Exception($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 失败,{plugin.AssembleName}不存在");
}
else
{
throw new Exception($"加载驱动插件 {path} 失败,文件不存在");
}
DriverBase GetDriver(DriverPlugin plugin, Type driverType)
{
var driver = (DriverBase)Activator.CreateInstance(driverType);
_logger?.LogInformation($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 成功");
DriverPluginDict.TryAdd(plugin.Id, driverType);
driver.DriverPlugin = plugin;
return driver;
}
Assembly GetAssembly(string path, List paths, AssemblyLoadContext assemblyLoadContext)
{
Assembly assembly = null;
foreach (var item in paths)
{
using var fs = new FileStream(item, FileMode.Open);
if (item == path)
assembly = assemblyLoadContext.LoadFromStream(fs);
else
{
try
{
assemblyLoadContext.LoadFromStream(fs);
}
catch (Exception ex)
{
_logger.LogWarning($"尝试加载附属程序集{item}失败,如果此程序集为非引用,比如非托管DllImport,可以忽略此警告。错误信息:{(ex.Message)}");
}
}
}
return assembly;
}
}
}
///
/// 获取插件的属性值
///
public List GetDriverProperties(DriverBase driver)
{
var data = driver.DriverPropertys?.GetType().GetPropertiesWithCache().SelectMany(it =>
new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute() } })
.Where(x => x.devicePropertyAttribute != null).ToList()
.SelectMany(it => new[]
{
new DependencyProperty(){
PropertyName=it.property.Name,
Description=it.devicePropertyAttribute.Description,
Remark=it.devicePropertyAttribute.Remark,
Value=it.property.GetValue(driver.DriverPropertys)?.ToString(),
}
});
return data.ToList();
}
///
/// 获取插件的变量上传属性值
///
public List GetDriverVariableProperties(UpLoadBase driver)
{
var data = driver.VariablePropertys?.GetType().GetPropertiesWithCache()?.SelectMany(it =>
new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute() } })
?.Where(x => x.devicePropertyAttribute != null).ToList()
?.SelectMany(it => new[]
{
new DependencyProperty(){
PropertyName=it.property.Name,
Description=it.devicePropertyAttribute.Description,
Remark=it.devicePropertyAttribute.Remark,
Value=it.property.GetValue(driver.VariablePropertys)?.ToString(),
}
});
return data?.ToList();
}
///
/// 获取插件方法
///
///
///
public List GetMethod(DriverBase driver)
{
return driver.GetType().GetMethods().Where(
x => x.GetCustomAttribute(typeof(DeviceMethodAttribute)) != null).ToList();
}
///
/// 设置插件的属性值
///
public void SetDriverProperties(DriverBase driver, List deviceProperties)
{
var pluginPropertys = driver.DriverPropertys?.GetType().GetPropertiesWithCache().Where(a => a.GetCustomAttribute(typeof(DevicePropertyAttribute)) != null)?.ToList();
foreach (var propertyInfo in pluginPropertys ?? new())
{
var deviceProperty = deviceProperties.FirstOrDefault(x => x.PropertyName == propertyInfo.Name);
if (deviceProperty == null) continue;
var value = propertyInfo.PropertyType.ObjToTypeValue(deviceProperty?.Value ?? "");
propertyInfo.SetValue(driver.DriverPropertys, value);
}
}
///
/// 尝试添加插件,返回插件表示类,方法完成后会完全卸载插件
///
///
///
public async Task> TryAddDriverAsync(DriverPluginAddInput plugin)
{
var assemblyLoadContext = new AssemblyLoadContext(YitIdHelper.NextId().ToString(), true);
try
{
var driverPlugins = new List();
var maxFileSize = 100 * 1024 * 1024;//最大100m
//主程序集名称
var mainFileName = Path.GetFileNameWithoutExtension(plugin.MainFile.Name);
//插件文件夹相对路径
var dir = "Plugins".CombinePathOS(mainFileName);
//插件文件夹绝对路径
var fullDir = AppContext.BaseDirectory.CombinePathOS(dir);
//主程序集相对路径
var path = dir.CombinePathOS(plugin.MainFile.Name);
//主程序集绝对路径
var fullPath = fullDir.CombinePathOS(plugin.MainFile.Name);
//主程序集相对路径
//获取文件流
using var stream = plugin.MainFile.OpenReadStream(maxFileSize);
Directory.CreateDirectory(fullDir);//创建插件文件夹
using FileStream fs = new(fullPath, FileMode.Create);
await stream.CopyToAsync(fs);
fs.Seek(0, SeekOrigin.Begin);
//获取主程序集
var assembly = assemblyLoadContext.LoadFromStream(fs);
foreach (var item in plugin.OtherFiles)
{
using var otherStream = item.OpenReadStream(maxFileSize);
using FileStream fs1 = new(fullDir.CombinePathOS(item.Name), FileMode.Create);
await otherStream.CopyToAsync(fs1);
fs1.Seek(0, SeekOrigin.Begin);
try
{
assemblyLoadContext.LoadFromStream(fs1);
}
catch (Exception ex)
{
_logger.LogWarning($"尝试加载附属程序集{item}失败,如果此程序集为非引用,比如非托管DllImport,可以忽略此警告。错误信息:{(ex.Message)}");
}
}
if (assembly != null)
{
//获取插件的相关信息
var collectBase = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList();
for (int i = 0; i < collectBase.Count; i++)
{
var item = collectBase[i];
driverPlugins.Add(new DriverPlugin()
{
AssembleName = item.Name,
DriverTypeEnum = DriverEnum.Collect,
FilePath = path,
FileName = mainFileName,
});
}
collectBase.ForEach(a => a = null);
collectBase.Clear();
collectBase = null;
var upLoadBase = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList();
for (int i = 0; i < upLoadBase.Count; i++)
{
var item = upLoadBase[i];
driverPlugins.Add(new DriverPlugin()
{
AssembleName = item.Name,
DriverTypeEnum = DriverEnum.Upload,
FilePath = path,
FileName = mainFileName,
});
}
upLoadBase.ForEach(a => a = null);
upLoadBase.Clear();
upLoadBase = null;
}
else
{
throw Oops.Bah("加载驱动文件失败");
}
if (driverPlugins.Count == 0)
{
throw Oops.Bah("找不到对应的驱动");
}
assembly = null;
var driverPluginCaches = App.GetService().GetCacheList();
var filePaths = driverPlugins.Select(a => a.FilePath).Distinct().ToList();
driverPluginCaches = driverPluginCaches.Where(a => filePaths.Contains(a.FilePath)).ToList();
foreach (var item in driverPluginCaches)
{
//卸载相同文件的插件域,这么可能会重复执行,但只会卸载一次
DeleteDriver(item.FilePath, driverPluginCaches.Select(a => a.Id).ToList());
}
return driverPlugins;
}
finally
{
assemblyLoadContext.Unload();
}
}
private List WeakReferences = new();
///
/// 删除插件
///
private void DeleteDriver(string path, List ids)
{
if (AssemblyLoadContextDict.TryGetValue(path, out var assemblyLoadContext))
{
ids ??= new();
DriverPluginDict.RemoveWhere(a => ids.Contains(a.Key));
AssemblyDict.Remove(path);
AssemblyLoadContextDict.Remove(path);
WeakReference alcWeakRef = new WeakReference(assemblyLoadContext, true);
WeakReferences.Add(alcWeakRef);
assemblyLoadContext.Unload();
GC.Collect();
}
try
{
foreach (WeakReference item in WeakReferences)
{
if (item.IsAlive)
{
GC.Collect();
}
else
{
WeakReferences.Remove(item);
}
}
}
catch
{
}
}
}