首页 > 编程语言 >.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

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

时间:2024-03-21 14:23:29浏览次数:24  
标签:StartsWith string 入门教程 程序 System dll continue 构建

前言:

在本部分中,我们将深入探讨如何使用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 信息:

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

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

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

assemblyBuilder.DefineVersionInfoResource();

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

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的版本):

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

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应用程序的开发提供更多可能性。

标签:StartsWith,string,入门教程,程序,System,dll,continue,构建
From: https://www.cnblogs.com/cyq1162/p/18085811

相关文章

  • weixin201基于微信小程序的校园保修系统
    技术实现开发语言:Java+vue。框架:后端springboot+前端微信小程序。模式:B/S。数据库:mysql。开发工具:idea+微信小程序开发工具。论文字数:1.5万左右。数据库表个数:13个。运行环境:jdk+idea+tomcat+mysql+微信小程序开发工具。版本不限制,包安装运行!项目优点:前后端分离,注......
  • 下载 macOS 系统安装程序的方法
    阅读信息:版本:0.4.20231021难度:1/10到4/10阅读时间:5分钟适合操作系统:10.13,10.14,10.15,11.x,12.x,13.x,14 更新2023-10-21添加Mist的介绍支持版本的更新,13.x,14  简介Mac用户时不时会需要下载macOS的安装程序,目的不同,或者升级或者降级,或者研究或者收藏......
  • 一些程序行业的问题和解答,还有一些疑惑求助求助
    1【ChatGPT5.0发布,会代替程序员吗?】?xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2【程序员是青春饭吗?35岁之后怎么办】35岁被淘汰的是不爱学习的,依旧可以架构和项目经理还有开发xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3【程序......
  • 上门回收小程序开发,打造便利高效的回收模式,提高回收利润
    我国是人口大省,每年都能够产生出大量的废弃可回收物品,废品回收市场具有巨大的发展前景。近几年,随着互联网的发展,打造出了“互联网+回收”模式,居民只需要在手机上预约回收,就可以享受便捷、高效的回收服务。除此之外,回收小程序的出现,也让回收企业得到了快速发展。“互联网+回收......
  • 程序员常用的工具软件推荐
    前言现如今,技术发展十分迅猛,开发者只有通过不断的学习才能跟得上时代的步伐。而为了便于学习和工作(减少996),涌现了很多优秀的开发工具用以帮助开发者提高工作效率。现在我把我工作多年来收集实用的开发利器分享出来,希望能够给同学们参考借鉴(以下排名不分先后)。假如各位同学有更......
  • Node+Vue毕设社区居家养老管理系统(程序+mysql+Express)
    本系统(程序+源码)带文档lw万字以上 文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:在人口老龄化日益加剧的今天,社区居家养老已成为解决老年人养老问题的重要方式。随着社会经济的发展和科技的进步,老年人对于养老服务的需求不再局限于基本的......
  • 年过30转行鸿蒙,职场危机也是转机,程序员的春天又来了!
    前言各位码农朋友们,听我说啊,我这年过30的老程序员,最近可是迎来了人生的新篇章!你们猜怎么着?我准备转行鸿蒙了!没错,就是那个风头正劲的鸿蒙系统!现在啊,它可是IT圈里的新宠儿,不仅功能强大,而且未来潜力无限。我这老程序员也看准了这个机会,果断决定投身鸿蒙的大潮中。说实话,刚......
  • 构建Python虚拟环境详细教程
    构建Python虚拟环境的详细教程如下:一、使用Anaconda创建虚拟环境打开AnacondaPrompt(在Windows上)或终端(在macOS或Linux上)。创建一个新的虚拟环境。你可以指定环境的名称和你想要使用的Python版本。例如,如果你想创建一个名为“myenv”的虚拟环境,并希望使用Python3.8,可以运行......
  • WM8978 —— 带扬声器驱动程序的立体声编解码器(2)
    接前一篇文章:WM8978——带扬声器驱动程序的立体声编解码器(1)六、引脚详细说明引脚(PIN)名称(NAME)类型(TYPE)描述(DESCRIPTION)1LIP模拟输入左麦克风前置放大器正极输入2LIN模拟输入左麦克风前置放大器负极输入3L2/GPIO2模拟输入左声道线路输入/辅助麦克风前置放大器正输入/......
  • 程序员必须了解的 10个免费 Devops 工具
    哈喽大家好,我是咸鱼。近年来,DevOps已经成为一门将软件开发(Dev)与IT运维(Ops)相融合的重要学科,目的是为了缩短软件的开发生命周期并提供高质量软件的持续交付。这篇文章整理了十种基本的免费开源的DevOps工具,这些工具已经在实践中得到了证明,凭借有效性和能够简化DevOp......