首页 > 编程语言 >dotnet 使用 dnlib 检测插件程序集的 API 兼容性

dotnet 使用 dnlib 检测插件程序集的 API 兼容性

时间:2024-09-12 08:55:54浏览次数:12  
标签:插件 程序 dnlib System API var Microsoft searchPathList

本文将和大家介绍在开发 dotnet 的插件时,如何通过 dnlib 库检测当前的插件是否由于主应用程序的版本差异导致存在 API 兼容性问题

众所周知,在开发插件的过程中,插件与主程序之间的兼容性问题将持续是一个令开发者烦恼的事情。举个例子,我开发的插件是面向 1.0 版本的主程序开发了,我需要用到 A 类型的 B 方法。结果在我插件发布一段时间之后,我的主程序更新到 2.0 版本了,此版本的主程序更改了 A 类型的 B 方法,比如删除了 B 方法,或者修改了 B 方法的函数参数。那么此时我的插件将会与主程序存在 API 不兼容问题,强行运行将会导致运行过程中抛出找不到成员的异常

本文介绍的 dnlib 库,可以用来辅助检测,当前的插件是否和主程序存在不兼容的问题。可以预先知道是否存在兼容问题,从而可以更好的给出用户交互

具体使用方法如下,按照 dotnet 的惯例,先安装 dnlib 库。可以通过如下方式编辑 csproj 项目文件,添加如下代码用来快速安装

  <ItemGroup>
    <PackageReference Include="dnlib" Version="4.4.0" />
  </ItemGroup>

接下来编写一个名为 CompatibilityChecker 的静态类型,将在此类型实现通过 dnlib 提供的功能进行兼容性检测

在 CompatibilityChecker 添加一个名为 CheckCompatibility 的方法,此方法将可以用来检测输入程序集是否存在与主程序的兼容性问题。方法定义如下

using dnlib.DotNet;

static class CompatibilityChecker
{
    /// <summary>
    /// 检查插件API兼容性
    /// </summary>
    /// <param name="assemblyFilePath"></param>
    /// <param name="searchPathList"></param>
    /// <returns>
    /// result: true表示API兼容 false表示不兼容
    /// missingMembers: 缺失的API
    /// exception: 检测过程中的异常
    /// </returns>
    public static (bool result, List<MemberRef> missingMembers, Exception? exception) CheckCompatibility(string assemblyFilePath, List<string> searchPathList)
    {
        try
        {
            var missingMembers = CompatibilityChecker.GetMembersRef(assemblyFilePath, searchPathList).GetMissingMembers();
            return (!missingMembers.Any(), missingMembers, null);
        }
        catch (Exception e)
        {
            return (false, new List<MemberRef>(0), e);
        }
    }
}

以上代码的 GetMembersRef 则需要将程序集进行分析加载,此分析加载过程中并非将程序集加入到当前程序域内,仅仅只是做二进制分析而已

    private static IEnumerable<MemberRef> GetMembersRef(string filePath, List<string> searchPathList)
    {
        var context = new ModuleContext(new AssemblyResolverWithSearchPathList(searchPathList));
        var module = ModuleDefMD.Load(filePath, context);
        return GetModuleMembersRef(module);
    }

以上的 AssemblyResolverWithSearchPathList 类型为自定义类型,作用就是根据输入的程序集依赖寻找路径列表,执行程序集依赖寻找策略。这个类型为本文所需要的核心实现方法,其核心原理就是通过 dnlib 的分析,读取程序集依赖寻找路径,查找是否存在某些依赖成员无法找到,从而了解是否存在兼容性问题

class AssemblyResolverWithSearchPathList : AssemblyResolver
{
    public AssemblyResolverWithSearchPathList(List<string> searchPathList)
    {
        SearchPathList = searchPathList;
    }

    private List<string> SearchPathList { get; }

    protected override IEnumerable<string> GetModuleSearchPaths(ModuleDef module) => base.GetModuleSearchPaths(module).Concat(SearchPathList);
}

以上的 GetModuleMembersRef 方法为获取当前 Module 的成员引用,其实现方法如下

    private static IEnumerable<MemberRef> GetModuleMembersRef(ModuleDefMD module)
    {
        return module.GetMemberRefs()
            .Select(x => (member: x, assembly: x.DeclaringType.DefinitionAssembly))
            .Where(x => x.assembly != module.Assembly)
            .Where(x => x.assembly is not null) // 如果存在动态程序集,那这里可能拿到空值
            .Where(x => !IgnoreAssemblies.Contains(x.assembly.Name.ToString())) // 这是可选的
            .Select(x => x.member);
    }

以上代码里面将过滤出依赖的成员,同时通过 IgnoreAssemblies 加入一些可供忽略的程序集。这些程序集是我实际开发过程中,发现 dnlib 支持较弱的,代码如下

    private static readonly HashSet<string> IgnoreAssemblies = new()
    {
        "Microsoft.CSharp",
        "mscorlib",
        "PresentationCore",
        "PresentationFramework",
        "System",
        "System.Collections",
        "System.Core",
        "System.Diagnostics.Debug",
        "System.Drawing",
        "System.Globalization",
        "System.IO",
        "System.IO.Compression",
        "System.Linq",
        "System.Linq.Expressions", // 尝试解决 dynamic 找不到 CallSite 的锅
        "System.Net.Http",
        "System.Reflection",
        "System.Reflection.Extensions",
        "System.Resources.ResourceManager",
        "System.Runtime",
        "System.Runtime.Extensions",
        "System.Runtime.InteropServices",
        "System.Text.Encoding",
        "System.Threading",
        "System.Threading.Tasks",
        "System.ValueTuple",
        "System.Windows.Forms",
        "System.Xaml",
        "WindowsBase",
        // 以下这个库会提示找不到 get_PageSize 和 Render 方法
        "PdfiumViewer",
        // 以下的几个库提示找不到方法,细节我还不知道
        "OpenAI-DotNet",
        "Azure.AI.OpenAI",
        "Azure.Core",
        "Microsoft.SemanticKernel.Core",
        "Microsoft.SemanticKernel",
        "Microsoft.SemanticKernel.Abstractions",
        "Microsoft.SemanticKernel.Planning.ActionPlanner",
        "Microsoft.SemanticKernel.Planning.SequentialPlanner",
        "Microsoft.SemanticKernel.Skills.Core",
        "Microsoft.SemanticKernel.Connectors.AI.OpenAI",
    };

如果没有忽略这几个程序集,可能插件程序集在寻找依赖是否缺失的过程中,将会寻找失败或者是提示以上程序集里面必定存在某些缺失的成员

最后的 GetMissingMembers 方法则是通过判断其引用成员是否 Resolve 失败,返回失败的列表,代码如下

    private static List<MemberRef> GetMissingMembers(this IEnumerable<MemberRef> members) => members.Where(x => x.Resolve() == null).ToList();

如此即可完成 CompatibilityChecker 类型的实现,下面来看看其使用方法

首先是获取需要检测的插件程序集所在的文件路径,作为 filePath 参数传入,这个属于大家自己的业务逻辑,还请自行解决。接下来构建 依赖寻找文件夹路径列表,一般来说插件程序集所在的文件夹里面可能包含插件本身所需依赖,于是先将插件程序集所在文件夹加入到依赖寻找文件夹路径列表里,代码如下

    var searchPathList = new List<string>();
    var directoryName = Path.GetDirectoryName(filePath);
    if (directoryName != null)
    {
        searchPathList.Add(directoryName);
    }
    else
    {
        // 见鬼了,这个文件不在文件夹里?
    }

接下来将主应用程序所在的文件夹也加入到 依赖寻找文件夹路径列表 里面

最后需要将 dotnet 系列依赖加入,比如我的 dotnet 依赖是打到主应用程序里面的,参考 记将一个大型客户端应用项目迁移到 dotnet 6 的经验和决策

我需要使用如下代码将应用程序所使用的定制版本的 dotnet 加入到依赖寻找列表,如以下代码

    var dotnetRuntimeFolderRoot = Path.Combine(mainApplicationPath, @"..\runtime\shared\Microsoft.NETCore.App\");
    if (Directory.Exists(dotnetRuntimeFolderRoot))
    {
        var dotnetRuntimeFolder = Directory
            .GetDirectories(dotnetRuntimeFolderRoot, "*", SearchOption.TopDirectoryOnly).FirstOrDefault();
        if (dotnetRuntimeFolder != null)
        {
            searchPathList.Add(dotnetRuntimeFolder);
        }
    }

对于 WPF 和 WinForms 项目,我还需要将 Microsoft.WindowsDesktop.App 也加入到依赖寻找列表,如以下代码

    var desktopRuntimeFolderRoot = Path.Combine(mainApplicationPath, @"..\runtime\shared\Microsoft.WindowsDesktop.App\");
    if (Directory.Exists(desktopRuntimeFolderRoot))
    {
        var desktopRuntimeFolder = Directory.GetDirectories(desktopRuntimeFolderRoot, "*", SearchOption.TopDirectoryOnly).FirstOrDefault();
        if (desktopRuntimeFolder != null)
        {
            searchPathList.Add(desktopRuntimeFolder);
        }
    }

完成依赖寻找列表之后,即可调用 CheckCompatibility 方法,如以下代码

    var (result, missingMembers, exception) = CompatibilityChecker.CheckCompatibility(filePath, searchPathList);

通过判断 result 即可知道当前的插件程序集是否和主应用程序之间存在兼容问题,且通过 missingMembers 可以了解存在哪些 API 不兼容

通过此方法即可判断插件是否与主应用程序存在兼容性问题,从而更好进行用户界面交互

标签:插件,程序,dnlib,System,API,var,Microsoft,searchPathList
From: https://www.cnblogs.com/lindexi/p/18022278

相关文章

  • verilog vscode 与AI 插件
    Verilog轻量化开发环境背景笔者常用的开发环境VIAVDO,体积巨大,自带编辑器除了linting能用,编辑器几乎不能用,仿真界面很友好,但是速度比较慢。SublimeText,非常好用的编辑器,各种插件使用verilog非常方便,可以自动补全、生成调用、linting等;VSCODE,SublimeText有的插件,VSC......
  • PDF 全文多语言 AI 摘要 API 数据接口
    PDF全文多语言AI摘要API数据接口PDF/文本摘要AI生成PDF文档摘要AI处理/智能摘要。1.产品功能支持多语言摘要生成;支持formdata格式PDF文件流传参;快速处理大文件;基于AI模型,持续迭代优化;不存储PDF文件,处理完即释放,保证您的文档安全;全接口支持HTTP......
  • 多语言 AI 翻译 API 数据接口
    多语言AI翻译API数据接口ai/翻译基于AI多语言模型支持多语言/基于模型。1.产品功能基于自有专业模型进行AI多语言翻译高效的文本翻译性能全接口支持HTTPS(TLSv1.0/v1.1/v1.2/v1.3);全面兼容AppleATS;全国多节点CDN部署;接口极速响应,多台服务器构......
  • 面试-JS Web API-DOM
    概览DOM(DocumentObjectModel)DOM是哪种数据结构?---树......
  • HTB-Oopsie(越权漏洞,suid提权,js接口查询插件)
    前言各位师傅大家好,我是qmx_07,今天给大家讲解Oopsie靶机渗透过程信息搜集服务器开放了22SSH端口和HTTP80端口FindSomething插件介绍:帮助寻找网站的js接口,辅助渗透通过js接口查找,发现目录/cdn-cgi/login登录接口通过游客模式登录越权登录访问uploads文件......
  • 《守望先锋2》游戏启动时黑屏弹窗“找不到steam_api.dll”该怎么处理?守望先锋2游戏崩
    在启动《守望先锋2》时,出现黑屏弹窗,提示“找不到steam_api.dll”,这给玩家带来极大困扰。别慌张,这种状况是能够处理的。可能需要重新获取该文件,或者检查相关设置是否正确。具体要怎么操作呢?本篇将为大家带来《守望先锋2》游戏启动时黑屏弹窗“找不到steam_api.dll”该怎么处理的......
  • 【话费充值】话费API接口对接有哪些关键步骤
    话费API接口对接通常包括以下几个关键步骤:选择服务提供商:选择一个可靠的话费充值API服务提供商,这可能是电信运营商本身或是一个信誉良好的第三方服务提供商。注册和认证:在选定的服务提供商平台上注册,并获得API访问权限。这通常涉及到创建一个开发者账号,并获取API密钥或其他形......
  • 【2024年】最新已验证确定可以使用的免费股票数据接口集合(实时交易、历史交易、KDJ、M
    ​近一两年来,股票量化分析逐渐受到广泛关注。而作为这一领域的初学者,首先需要面对的挑战就是如何获取全面且准确的股票数据。因为无论是实时交易数据、历史交易记录、财务数据还是基本面信息,这些数据都是我们进行量化分析时不可或缺的宝贵资源。我们的核心任务是从这些数据......
  • 如何在Java中实现应用的动态扩展:基于热插拔与插件机制的实现
    如何在Java中实现应用的动态扩展:基于热插拔与插件机制的实现大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在现代应用开发中,为了应对不断变化的需求和快速迭代的要求,应用的动态扩展能力变得尤为重要。实现动态扩展的关键技术包括热插拔和插件机制。......
  • 如何在Flask中实现API
    在Flask中实现API是一个相对直接且灵活的过程,它允许你快速构建RESTful(RepresentationalStateTransfer)风格的Web服务。由于篇幅限制,我无法提供完整的5000字详细指南,但我可以概述关键步骤和最佳实践,帮助你理解如何在Flask中设计和实现API。1.Flask基础首先,确保你已经安装了F......