.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

这篇具有很好参考价值的文章主要介绍了.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

在本部分中,我们将深入探讨如何使用C# Emit 来创建动态程序集。

动态程序集是在运行时生成的,为我们提供了一种灵活、动态地构建和加载程序集的能力。

1. 程序集的概念

程序集是.NET中的基本部署单位,它包含了可执行代码、资源、元数据等信息,是.NET应用程序的基本组成单元之一。

与静态程序集(即程序集持久化)不同,动态程序集是在运行时生成的,使得我们可以根据需要动态地构建和加载程序集。

动态程序集的重要性体现在多个方面,包括但不限于模块化、版本控制、代码复用等。

通过动态程序集,我们可以更好地管理和组织代码,实现更灵活的程序设计和开发。

2、Emit 构建程序集支持情况:

.Net 系列:动态程序集:支持;程序集持久化:支持。

.Net Core 系列:动态程序集:支持;程序集持久化不支持
 

3、Emit 创建动态程序集:

在C#中,操作程序集的核心类是:AssemblyBuilder。

在这过程,我们可以使用  AssemblyName 来定义程序集的名称和版本等信息。

AssemblyName assemblyName = new AssemblyName("MyDllName") { Version = new Version("1.0.0.0") };

创建程序集对象,在 .Net 和 .Net Core 中有所不同:

.NET 代码:通过 AppDomain.DefineDynamicAssembly 来创建动态程序集对象:

AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

AssemblyBuilderAccess 在 .Net 系列中的属性:

AssemblyBuilderAccess.Run; 表示程序集可被执行,但不能被保存。  
AssemblyBuilderAccess.Save; 表示程序集可被保存,但不能被执行。  
AssemblyBuilderAccess.RunAndSave; 表示程序集可被保存并能被执行。
AssemblyBuilderAccess.ReflectionOnly; 表示程序集只能用于反射上下文环境中,不能被执行。 
AssemblyBuilderAccess.RunAndCollect; 表示程序集可以被卸载并且内存会被回收

.NET Core 代码:通过 AssemblyBuilder.DefineDynamicAssembly 来创建动态程序集对象:

AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

AssemblyBuilderAccess 在 .Net Core 系列中的属性:

AssemblyBuilderAccess.Run; 表示程序集可被执行,但不能被保存。  
AssemblyBuilderAccess.Collect; 表示程序集可以被卸载并且内存会被回收
 

可以看到,目前版本是不支持持久化功能。

4:总结一:

在本章节中,我们讨论了如何使用 C# Emit 来构建动态程序集,以获得 AssemblyBuilder 这个程序集构建器,开启构建程序集的第一步。

在之后的文章,我们会讲解如何用它的 DefineDynamicModule 来定义模块,以之后构建类、成员、方法等,来实现构建完整的程序集。

当然,对于构建动态程序集,本文章到此会显的有点简单,为了避免被大伙说文章太水。

于是,打算追加一下对于构建静态程序集(即持久化程序集)的相关知识。

构建静态程序集的三种方式:

.Net 系列:使用 EmitCodeDom(System.CodeDom.Compiler 系统集成)

.Net Core 系列:Roslyn(Microsoft.CodeAnalysis.CSharp 需要引用组件包)

5、Emit 构建静态程序集:

在上述代码中,我们通过 AssemblyName 来指定程序集名称和版本号。

如果我们希望持久化程序集,并将程序集分发给其它人调用,则需要补充更多的相关信息:

比如:Taurus.MVC 微服务组件中就实现了动态生成 Rpc 调用程序集,以供其它微服务节点调用。

在定义完程序集名称和版本后(AssemblyBuilderAccess 需要指定为 RunAndSave )。

我们可以通过 AssemblyBuilder  的 SetCustomAttribute 方法,来添加自定义属性,最后通过 Save 方法,来持久化程序集:

internal class Program
{
    static void Main(string[] args)
    {
        string dllName = "MyDynamic";
        string title = "This is a dynamic title.";

        AssemblyName assemblyName = new AssemblyName(dllName) { Version = new Version("3.0.0.0") };
        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

        // 添加自定义属性到程序集var titleAttr = new CustomAttributeBuilder(
           typeof(AssemblyTitleAttribute).GetConstructor(new Type[] { typeof(string) }),
           new object[] { title }
       );
        assemblyBuilder.SetCustomAttribute(titleAttr);

        assemblyBuilder.DefineVersionInfoResource();
       
        assemblyBuilder.Save(dllName + ".dll");

        Console.WriteLine("程序集已生成");
    }
}

自定义支持哪些属性,可以参考工程目录下的:AssemblyInfo.cs 信息:

.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

没有指定完整的保存路径时,程序运行之后默认在运行目录下可以看到生成的程序集:

.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

通过反编绎工具,可以看到程序集的相关信息:

.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

在上面的示例代码中,我们追加了一行代码如下:

assemblyBuilder.DefineVersionInfoResource();

它让生成的程序集在 Window 平台可以看到相关信息,(不写入的话,相关内容为空):

.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

6、使用 CodeDom 生成静态程序集

CodeDom 的使用比较简单,无需要引用组件,代码编写也比较简洁,唯一的缺点,就是不支持 .NET Core 系列。

使用示例代码来自Taurus.MVC 微服务RPC代码生成(CodeDom 版本):

更多源码:https://github.com/cyq1162/Taurus.MVC/tree/master/src/Taurus.Core/Plugin/MicroService/Client/Proxy/DotNet

 /// <summary>
 /// Rpc 客户端代理生成类
 /// </summary>
 internal static class RestProxyCreator
 {
     /// <summary>
     /// 构建并保存程序集
     /// </summary>
     /// <param name="assName">程序集名称</param>
     /// <param name="savePath">保存路径</param>
     public static bool BuildAssembly(string assName, string savePath)
     {
         if (string.IsNullOrEmpty(assName)) { assName = "RpcProxy"; }
         if (string.IsNullOrEmpty(savePath))
         {
             savePath = AppConst.WebRootPath + assName + ".dll";
         }
         else if (!savePath.StartsWith(AppConst.WebRootPath))
         {
             savePath = AppConst.WebRootPath + savePath;
         }
         string version;
         var code = RestProxyCoder.CreateCode(assName, out version);
         var title = assName + " for Taurus MicroService, build on .net " + Environment.Version.ToString();
         var versionCode = RestProxyCoder.CreateVersionCode(title, title, version);

         CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
         CompilerParameters cp = new CompilerParameters();
         cp.ReferencedAssemblies.Add("System.dll");
         cp.ReferencedAssemblies.Add("System.Web.dll");
         cp.ReferencedAssemblies.Add("System.Xml.dll");
         cp.ReferencedAssemblies.Add("System.Data.dll");
         cp.ReferencedAssemblies.Add(AppConst.AssemblyPath + "CYQ.Data.dll");
         cp.ReferencedAssemblies.Add(AppConst.AssemblyPath + "Taurus.Core.dll");
         cp.GenerateExecutable = false;
         cp.GenerateInMemory = false;
         cp.OutputAssembly = savePath;
         CompilerResults cr = provider.CompileAssemblyFromSource(cp, versionCode, code);
         if (cr == null || cr.Errors.Count > 0) { return false; }
         return true;

     }
 }

7、 使用 Roslyn 生成静态程序集

Roslyn 需要引用组件,根据自己要支持的最低版本进行引用(如 4.1.0 就是最低支持.NET Core 2.1的版本):

.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

而且使用麻烦,两个距坑:

1、Win32 资源信息如何显示。

2、程序集引用路径错误问题。

为解决这两个距坑问题,费了不少时间和精力,毕竟网上国内外只有零散的引导信息,还有那不靠谱的 GPT 系列。

使用示例代码来自 Taurus.MVC 微服务RPC代码生成(Roslyn 版本):

更多源码:https://github.com/cyq1162/Taurus.MVC/tree/master/src/Taurus.Core/Plugin/MicroService/Client/Proxy/NetCore

/// <summary>
/// Rpc 客户端代理生成类
/// </summary>
internal static class RestProxyCreator
{
    /// <summary>
    /// 构建并保存程序集
    /// </summary>
    /// <param name="assName">程序集名称</param>
    /// <param name="savePath">保存路径</param>
    public static bool BuildAssembly(string assName, string savePath)
    {
        if (string.IsNullOrEmpty(assName)) { assName = "RpcProxy"; }
        if (string.IsNullOrEmpty(savePath))
        {
            savePath = AppConst.WebRootPath + assName + ".dll";
        }
        else if (!savePath.StartsWith(AppConst.WebRootPath))
        {
            savePath = AppConst.WebRootPath + savePath;
        }
        string version;
        var code = RestProxyCoder.CreateCode(assName, out version);
        var title = assName + " for Taurus MicroService, build on .netcore " + Environment.Version.ToString();
        var versionCode = RestProxyCoder.CreateVersionCode(title, title, version);

        SyntaxTree syntaxVersionTree = CSharpSyntaxTree.ParseText(versionCode);
        SyntaxTree syntaxCodeTree = CSharpSyntaxTree.ParseText(code);


        // 定义编译选项
        CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
        .WithOptimizationLevel(OptimizationLevel.Release); // 设置优化级别

        // 创建 Compilation
        CSharpCompilation compilation = CSharpCompilation.Create(assName,
            new[] { syntaxVersionTree, syntaxCodeTree },
            references: AddRef(),
            options: compilationOptions);

        // 编译并生成程序集
        using (MemoryStream ms = new MemoryStream())
        {
            using (Stream win32resStream = compilation.CreateDefaultWin32Resources(
                                                                        versionResource: true, // 生成版本号。
                                                                        noManifest: false,
                                                                        manifestContents: null,
                                                                        iconInIcoFormat: null))
            {

                EmitResult result = compilation.Emit(ms, win32Resources: win32resStream);

                if (!result.Success)
                {
                    //foreach (var diagnostic in result.Diagnostics)
                    //{
                    //    Console.WriteLine(diagnostic);
                    //}
                    //Console.WriteLine("fail....");
                    return false;
                }
                else
                {
                    // 保存程序集到文件
                    using (FileStream file = new FileStream(savePath, FileMode.Create))
                    {
                        ms.Seek(0, SeekOrigin.Begin);
                        ms.CopyTo(file);
                    }
                    //Console.WriteLine("OK....");
                }
            }
        }

        return true;

    }

    /// <summary>
    /// 添加程序集引用
    /// </summary>
    /// <returns></returns>
    private static PortableExecutableReference[] AddRef()
    {
        List<PortableExecutableReference> exeRefs = new List<PortableExecutableReference>();

        string path = typeof(object).Assembly.Location;

        #region 引用路径修正
        path = Path.GetDirectoryName(path);
        if (path.Contains("\\shared\\Microsoft.NETCore.App"))
        {
            // windows 平台,这是nuget 实现程序集路径
            string refPath = path.Replace("\\shared\\Microsoft.NETCore.App", "\\\\packs\\Microsoft.NETCore.App.Ref") + "\\ref";
            if (Directory.Exists(refPath))
            {
                string[] files = Directory.GetDirectories(refPath);
                if (files.Length > 0) { path = files[0] + "\\"; }
            }
        }
        #endregion
        foreach (string dllFilePath in Directory.GetFiles(path, "*.dll"))
        {
            var dll = Path.GetFileName(dllFilePath);

            if (dll.Split(".").Length > 4) { continue; }
            if (dll.StartsWith("Microsoft.")) { continue; }
            if (dll.StartsWith("System.Drawing.")) { continue; }
            if (dll.StartsWith("System.IO.")) { continue; }
            if (dll.StartsWith("System.Linq.")) { continue; }
            if (dll.StartsWith("System.Net.")) { continue; }
            if (dll.StartsWith("System.Reflection.")) { continue; }
            if (dll.StartsWith("System.Security.")) { continue; }
            if (dll.StartsWith("System.Text.")) { continue; }
            if (dll.StartsWith("System.Threading.")) { continue; }
            if (dll.StartsWith("System.Globalization.")) { continue; }
            if (dll.StartsWith("System.Resources.")) { continue; }
            if (dll.StartsWith("System.Transactions.")) { continue; }
            if (dll.StartsWith("System.Memory.")) { continue; }
            if (dll.StartsWith("System.Formats.")) { continue; }
            if (dll.StartsWith("System.ComponentModel.")) { continue; }
            if (dll.StartsWith("System.Windows.")) { continue; }
            if (dll.StartsWith("System.Diagnostics.")) { continue; }
            if (dll.Contains("VisualBasic")) { continue; }


            exeRefs.Add(MetadataReference.CreateFromFile(dllFilePath));
        }


        //添加引用程序集
        string cyqdata = AppConst.AssemblyPath + "CYQ.Data.dll";
        string taurus = AppConst.AssemblyPath + "Taurus.Core.dll";

        exeRefs.Add(MetadataReference.CreateFromFile(cyqdata));
        exeRefs.Add(MetadataReference.CreateFromFile(taurus));
        return exeRefs.ToArray();
    }
}

8、总结:

在本章节中,我们讨论了如何使用 C# Emit 来构建动态程序集,以获得 AssemblyBuilder 这个程序集构建器,开启构建程序集的第一步。

同时我们也讨论了如何使用 C# Emit 来构建动态程序集以及程序集的持久化。

同时还分享了自己的干货,如何使用 CodeDom 和 Roslyn 来构建静态程序集。

通过本章节的内容,读者可以掌握如何利用 C# Emit 技术构建动态程序集和静态程序集,从而实现灵活、动态的程序设计和开发。

动态程序集的应用能够提升代码的模块化性、版本控制和代码复用性,为.NET应用程序的开发提供更多可能性。文章来源地址https://www.toymoban.com/news/detail-842096.html

到了这里,关于.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • .NET Emit 入门教程:第六部分:IL 指令:6:详解 ILGenerator 指令方法:方法调用指令

    经过前面几篇的学习,我们了解到指令的大概分类,如: 参数加载指令,该加载指令以 Ld 开头,将参数加载到栈中,以便于后续执行操作命令。 参数存储指令,其指令以 St 开头,将栈中的数据,存储到指定的变量中,以方便后续使用。 创建实例指令,其指令以 New 开头,

    2024年04月08日
    浏览(65)
  • .NET Emit 入门教程:第六部分:IL 指令:7:详解 ILGenerator 指令方法:分支条件指令

    经过前面几篇的学习,我们了解到指令的大概分类,如: 参数加载指令,该加载指令以 Ld 开头,将参数加载到栈中,以便于后续执行操作命令。 参数存储指令,其指令以 St 开头,将栈中的数据,存储到指定的变量中,以方便后续使用。 创建实例指令,其指令以 New 开头,

    2024年04月12日
    浏览(45)
  • .NET Emit 入门教程:第六部分:IL 指令:3:详解 ILGenerator 指令方法:参数加载指令

    在上一篇中,我们介绍了 ILGenerator 辅助方法。 本篇,将详细介绍指令方法,并详细介绍指令的相关用法。 在接下来的教程,关于IL指令部分,会将指令分为以下几个分类进行讲解: 下面开始介绍第一部分,参数加载指令: 参数加载指令用于在方法中加载参数到操作数栈中,

    2024年04月08日
    浏览(70)
  • .NET Emit 入门教程:第六部分:IL 指令:9:详解 ILGenerator 指令方法:运算操作指令(指令篇结束)

    经过前面几篇的学习,我们了解到指令的大概分类,如: 参数加载指令,该加载指令以 Ld 开头,将参数加载到栈中,以便于后续执行操作命令。 参数存储指令,其指令以 St 开头,将栈中的数据,存储到指定的变量中,以方便后续使用。 创建实例指令,其指令以 New 开头,

    2024年04月22日
    浏览(42)
  • Python机器学习算法入门教程(第二部分)

    接着Python机器学习算法入门教程(第一部分),继续展开描述。 在 Python机器学习算法入门教程(第一部分)中的第六部分:线性回归:损失函数和假设函数 一节,从数学的角度解释了假设函数和损失函数,我们最终的目的要得到一个最佳的“拟合”直线,因此就需要将损失

    2024年02月05日
    浏览(39)
  • 【监控仪表系统】Grafana 中文入门教程 | 构建你的第一个仪表盘

    Grafana 读音:/grəˈfɑːnˌɑː/ 在大厂工作久了,时常对一些工具的存在觉得理所当然。 比如说,需要计算资源的时候,一个配置文件就可以要来两百台虚拟化好的机子。需要试下缓存?点下鼠标就可以要到几十个配置好的 Redis 结点。 最省心的是,这些工具都已经根据工作流

    2024年02月02日
    浏览(44)
  • 【Maven教程】(一)入门介绍篇:Maven基础概念与其他构建工具:理解构建过程与Maven的多重作用,以及与敏捷开发的关系 ~

    \\\" Maven \\\"可以翻译为 “知识的积累者” 或 “专家”。这个词源于波斯语,意为广受尊重和富有智慧的人。在软件开发领域中,Maven作为Apache 组织中的一个颇为成功的开源项目,它是一个非常流行的构建工具,它在项目管理、依赖管理和构建自动化方面提供了强大的功能和支持

    2024年02月12日
    浏览(50)
  • 小程序入门级教程

    小程序,这里主要说的是微信小程序,微信小程序是在16年推出的,同样的,微信小程序的竞品也有很多,比如:支付宝小程序,钉钉,美团,头条,抖音,qq小程序等等 可以实现自由分享 不用下载app也可使用 能快速的开发,使用微信接口 小程序是面向大众的,针对开发者

    2024年02月10日
    浏览(254)
  • 小程序入门——详细教程

                                                       🎬 艳艳耶✌️:个人主页                                                   🔥 个人专栏 :《Spring与Mybatis集成整合》《Vue.js使用》                                                    ⛺️ 生活的理想,

    2024年02月06日
    浏览(43)
  • 微信小程序入门教程 --(保姆级)

    一、小程序注册 1、首先,到小程序官网注册自己的小程序账号,以下附有地址和教程: 小程序官网 进入这个地址之后,会看到这样的页面,点击立即注册按钮 2、在接下来的页面,选择小程序 3、然后根据提示完成三个注册步骤,(1).填写正确的邮箱和密码格式;(2).进行邮箱

    2023年04月08日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包