Files
XplorePlane/XplorePlane/Helpers/DependencyVersionProvider.cs
T
2026-08-10 14:31:44 +08:00

199 lines
8.7 KiB
C#

// 文件功能:读取应用运行所依赖的托管程序集和原生库版本信息。
// 优先读取当前 AppDomain 已加载程序集,未加载时回退到应用部署目录,避免主动加载可选依赖。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
namespace XplorePlane.Services.Helpers
{
public sealed record DependencyVersionInfo(
string Category,
string Name,
string Version,
string Path,
bool IsLoaded);
/// <summary>
/// Reads dependency versions without forcing optional or cross-process libraries into
/// the main AppDomain. Loaded assembly metadata is preferred; deployed files are the fallback.
/// </summary>
public static class DependencyVersionProvider
{
private sealed record DependencyDescriptor(
string Category,
string DisplayName,
params string[] AssemblyNames);
private static readonly DependencyDescriptor[] Dependencies =
{
new("通信/公共库", "XP.Common", "XP.Common"),
new("通信/公共库", "BR.AN.PviServices", "BR.AN.PviServices"),
new("通信/公共库", "Prism.DryIoc", "Prism.DryIoc.Wpf", "Prism.Container.DryIoc"),
new("通信/公共库", "Prism", "Prism"),
new("通信/公共库", "DryIoc", "DryIoc"),
new("通信/公共库", "Serilog", "Serilog"),
new("通信/公共库", "System.Data.SQLite", "System.Data.SQLite"),
new("通信/公共库", "Microsoft.Data.Sqlite", "Microsoft.Data.Sqlite"),
new("图像处理库", "Emgu.CV", "Emgu.CV"),
new("图像处理库", "Emgu.CV.Bitmap", "Emgu.CV.Bitmap"),
new("图像处理库", "XP.ImageProcessing.Core", "XP.ImageProcessing.Core"),
new("图像处理库", "XP.ImageProcessing.Processors", "XP.ImageProcessing.Processors"),
new("图像处理库", "XP.ImageProcessing.RoiControl", "XP.ImageProcessing.RoiControl"),
new("相机库", "Basler.Pylon", "Basler.Pylon"),
new("相机库", "XP.Camera", "XP.Camera"),
new("UI 框架", "Fluent", "Fluent"),
new("UI 框架", "Telerik.Windows.Controls", "Telerik.Windows.Controls"),
new("UI 框架", "Telerik.Windows.Controls.GridView", "Telerik.Windows.Controls.GridView"),
new("UI 框架", "Telerik.Windows.Controls.Chart", "Telerik.Windows.Controls.Chart"),
new("硬件库", "XP.Hardware.RaySource", "XP.Hardware.RaySource"),
new("运行时", "System.Runtime", "System.Runtime")
};
/// <summary>读取预定义依赖项的版本、路径和加载状态。</summary>
/// <returns>依赖版本信息列表,包含托管依赖和 OpenCV 原生依赖。</returns>
public static IReadOnlyList<DependencyVersionInfo> GetVersions()
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => !assembly.IsDynamic)
.GroupBy(assembly => assembly.GetName().Name ?? string.Empty, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.First(), StringComparer.OrdinalIgnoreCase);
var versions = Dependencies
.Select(descriptor => ReadDependency(descriptor, loadedAssemblies))
.ToList();
versions.Add(ReadNativeDependency("图像处理库", "OpenCV (Native/cvextern)", "cvextern.dll"));
return versions;
}
/// <summary>读取单个托管程序集依赖的版本信息。</summary>
/// <param name="descriptor">依赖项描述信息。</param>
/// <param name="loadedAssemblies">按程序集名称索引的已加载程序集。</param>
/// <returns>找到的依赖版本信息;未找到时返回未部署状态。</returns>
private static DependencyVersionInfo ReadDependency(
DependencyDescriptor descriptor,
IReadOnlyDictionary<string, Assembly> loadedAssemblies)
{
foreach (var assemblyName in descriptor.AssemblyNames)
{
if (!loadedAssemblies.TryGetValue(assemblyName, out var assembly))
continue;
var path = GetAssemblyLocation(assembly);
return new DependencyVersionInfo(
descriptor.Category,
descriptor.DisplayName,
ReadVersion(path, assembly.GetName().Version),
path,
true);
}
foreach (var assemblyName in descriptor.AssemblyNames)
{
var path = FindDeployedAssemblyPath(assemblyName);
if (!File.Exists(path))
continue;
Version assemblyVersion = null;
try { assemblyVersion = AssemblyName.GetAssemblyName(path).Version; }
catch (Exception) { }
return new DependencyVersionInfo(
descriptor.Category,
descriptor.DisplayName,
ReadVersion(path, assemblyVersion),
path,
false);
}
return new DependencyVersionInfo(
descriptor.Category,
descriptor.DisplayName,
"未部署",
string.Empty,
false);
}
private static string FindDeployedAssemblyPath(string assemblyName)
{
var fileName = assemblyName + ".dll";
var roots = new List<string> { AppContext.BaseDirectory };
var current = new DirectoryInfo(AppContext.BaseDirectory);
for (var i = 0; i < 6 && current.Parent != null; i++)
{
current = current.Parent;
roots.Add(current.FullName);
}
foreach (var root in roots.Distinct(StringComparer.OrdinalIgnoreCase))
{
foreach (var relativeDirectory in new[]
{
string.Empty,
"ExternalLibraries",
Path.Combine("ExternalLibraries", "Host"),
"ReleaseFiles",
Path.Combine("ReleaseFiles", "Host")
})
{
var candidate = Path.Combine(root, relativeDirectory, fileName);
if (File.Exists(candidate))
return candidate;
}
}
return Path.Combine(AppContext.BaseDirectory, fileName);
}
/// <summary>读取应用目录中的原生依赖版本信息。</summary>
/// <param name="category">依赖类别。</param>
/// <param name="displayName">显示名称。</param>
/// <param name="fileName">原生库文件名。</param>
/// <returns>原生依赖版本信息。</returns>
private static DependencyVersionInfo ReadNativeDependency(
string category, string displayName, string fileName)
{
var path = Path.Combine(AppContext.BaseDirectory, fileName);
return File.Exists(path)
? new DependencyVersionInfo(category, displayName, ReadVersion(path, null), path, false)
: new DependencyVersionInfo(category, displayName, "未部署", string.Empty, false);
}
/// <summary>获取程序集文件位置;动态程序集或不支持位置查询时返回空字符串。</summary>
/// <param name="assembly">目标程序集。</param>
/// <returns>程序集文件路径,无法获取时为空字符串。</returns>
private static string GetAssemblyLocation(Assembly assembly)
{
try { return assembly.Location ?? string.Empty; }
catch (NotSupportedException) { return string.Empty; }
}
/// <summary>读取文件版本,文件版本不可用时回退到程序集版本。</summary>
/// <param name="path">程序集或原生库路径。</param>
/// <param name="assemblyVersion">已读取的程序集版本。</param>
/// <returns>可显示的版本文本。</returns>
private static string ReadVersion(string path, Version assemblyVersion)
{
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
{
try
{
var fileVersion = FileVersionInfo.GetVersionInfo(path).FileVersion;
if (!string.IsNullOrWhiteSpace(fileVersion))
return fileVersion;
}
catch (Exception) { }
}
return assemblyVersion?.ToString() ?? "未知";
}
}
}