.NET Emit 入门教程:第三部分:构建模块(Module)
前言:
在这一部分中,我们将深入讨论动态程序集中模块的概念以及如何构建和管理模块。
1、模块的概念:
模块是动态程序集中的基本单位,它类似于一个独立的代码单元,可以包含类型、方法、字段等成员。
在动态程序集中,模块扮演着组织代码和实现代码复用的关键角色。
它们允许开发人员将相关功能和数据组织在一起,并在需要时进行引用和重用。
一个程序集可以包含一个或多个模块,这种模块化的设计有助于提高代码的可维护性和可扩展性。
通俗的讲人话:
即在设计上:在运行时,一个程序集可以包含多个模块,每个模块允许用不同的语言编写,比如VB模块混合C#模块。 在使用上:在编绎后,一个程序集只能包含一个模块。
下面来看一个组问答题:
2、程序集和模块的关系问答:
既然一个程序集可以定义多个Module,为什么通过反编绎dll,发现所有的dll文件都只有一个module呢?
在使用 C# 或 .NET Framework 动态创建程序集和模块时,无论你创建了多个模块,最终生成的 DLL 文件通常只会包含一个默认模块。
这是因为在 .NET 中,一个程序集(assembly)通常对应一个 DLL 或者 EXE 文件,而每个程序集只包含一个默认的模块。 即使你在代码中使用 DefineDynamicModule 创建了多个模块,最终生成的 DLL 文件也只会包含默认模块的信息。
其他通过 DefineDynamicModule 创建的模块并不会以独立的形式出现在最终的 DLL 文件中。 实际上,在 .NET 中,程序集可以包含多个模块,但这些附加的模块一般不会直接保存在磁盘文件中,而是在运行时动态加载到程序集中。这也是为什么反编译时只能看到一个模块的原因。 总的来说,即使你在代码中动态创建了多个模块,最终生成的 DLL 文件也只会包含一个默认模块。其他动态创建的模块会以其他方式与程序集关联,并不会直接体现在生成的 DLL 文件中。
了解完程序集与模块的对应关系,下面看看如何创建动态模块:
3、创建动态模块:
使用C# Emit 技术可以在运行时动态创建模块。
首先,需要获得 AssemblyBuilder(这个在构建程序集一文中,已经讲解了 .NET 和 .NET Core 下的相关获取用法)。
然后,通过 AssemblyBuilder 的 DefineDynamicModule方法,即可获得 ModuleBuilder(后续章节会通过它,来添加类型、方法、字段等成员到模块中,从而构建出所需的模块结构)。
下面是一个获得 ModuleBuilder 示例代码:
AssemblyBuilder ab = ......
ModuleBuilder mb = ab.DefineDynamicModule("一个名称");
4、创建静态模块(仅.NET系列支持):
在.NET中,如果要将该模块持久化到 dll 中,则需要在重载中指定 dll 的名称,如:
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); //...... ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("一个名称", dllName + ".dll");
//......
assemblyBuilder.Save(dllName + ".dll");
正如问答所说,一个程序集在持久化到文件中时,只能包含一个默认模块。
因此,通过指定和程序集相同的名称,来持久化到相同的程序集中。
如果定义多个模块,都指向同一个程序集名称中呢?
如下图,抛重复的文件名异常:
如果定义多个模块,都给予不同的程序集名称呢?
如下图,每个模块单独生成程序集:
因此,在运行时,可以定义多个模块,模块在运行时可以互动,但持久化到文件中,只能有一个模块。
5、模块间的交互
模块之间可能存在依赖关系,一个模块可能需要引用另一个模块中的类型或成员。
在动态程序集中,可以使用 AssemblyBuilder 来创建程序集并将模块进行组合,从而实现模块间的交互和依赖管理。
通常而言,不建议尝试在程序集中定义多个模块,下面给几个教训的示例:
因情节需要,以下内容中会提前出现 TypeBuilder、MethodBuilder 和 IL代码。
在 .NET 中定义多个模块,并尝试进行交互:
public static void Start()
{
// 创建第一个模块
AssemblyName assemblyName = new AssemblyName("MyAssembly");
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder1 = assemblyBuilder.DefineDynamicModule("Module1"); // 在第一个模块中定义一个类型
TypeBuilder typeBuilder1 = moduleBuilder1.DefineType("MyClass", TypeAttributes.Public); MethodBuilder methodBuilder1 = typeBuilder1.DefineMethod("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), null);
ILGenerator ilGenerator = methodBuilder1.GetILGenerator();
ilGenerator.EmitWriteLine("Call hello from Module1!");
ilGenerator.Emit(OpCodes.Ret);
typeBuilder1.CreateType(); // 在第二个模块中引用第一个模块中定义的类型
MethodInfo myMethod = moduleBuilder1.GetType("MyClass").GetMethod("MyMethod"); // 创建第二个模块
ModuleBuilder moduleBuilder2 = assemblyBuilder.DefineDynamicModule("Module2");
TypeBuilder typeBuilder2 = moduleBuilder2.DefineType("MyClass2", TypeAttributes.Public);
MethodBuilder methodBuilder2 = typeBuilder2.DefineMethod("MyMethod2", MethodAttributes.Public | MethodAttributes.Static, typeof(void), null);
var ilGenerator2 = methodBuilder2.GetILGenerator();
ilGenerator2.EmitWriteLine("Exe hello from Module2!");
ilGenerator2.EmitCall(OpCodes.Call, myMethod, null);
ilGenerator2.Emit(OpCodes.Ret);
typeBuilder2.CreateType(); //assemblyBuilder.LoadModule() var myMethod2 = moduleBuilder2.GetType("MyClass2").GetMethod("MyMethod2"); //myMethod.Invoke(null, null);
myMethod2.Invoke(null, null); Console.Read();
}
结果如下:
同样的代码,在 .NET Core 中执行,你将得到以下结果:
所以,你懂的,别折腾这个。
总结:
嗯,构建模块,一行代码的事情,愣是让我写成了一篇教程,太难了,下面进行总结。
在这个入门教程的第三部分中,我们学习了如何使用.NET Emit 构建模块(Module)。
通过创建和定义模块,我们可以更好地组织和管理我们的代码。
在这个过程中,我们了解了如何使用 AssemblyBuilder 和 ModuleBuilder 来动态生成模块。
通过学习构建模块的过程,我们可以更深入地理解.NET Emit 的强大功能,并且能够在运行时动态地生成和加载代码。
构建模块是.NET Emit中非常重要的一部分,它为我们提供了灵活性和扩展性,让我们能够更好地应对各种编程需求。
在接下来的学习中,我们将继续探索.NET Emit的各种功能和用法,不断丰富我们的动态代码生成技能,为我们的项目带来更多可能性。
希望这个入门教程能够帮助你更好地理解.NET Emit,并为你的编程之路增添新的技能和知识!
.NET Emit 入门教程:第三部分:构建模块(Module)的更多相关文章
- Node.js入门教程 第三篇 (模块及路由)
Node.js的模块 Node.js的模块与传统面向对象的类(class)不完全相同.Node.js认为文件即模块,即一个文件是一个模块.单一文件一般只专注做一件事情,保证了代码的简洁性. 创建模块: ...
- Docker入门教程(三)Dockerfile
Docker入门教程(三)Dockerfile [编者的话]DockerOne组织翻译了Flux7的Docker入门教程,本文是系列入门教程的第三篇,介绍了Dockerfile的语法,DockerOn ...
- WCF入门教程(三)定义服务协定--属性标签
WCF入门教程(三)定义服务协定--属性标签 属性标签,成为定义协议的主要方式.先将最简单的标签进行简单介绍,以了解他们的功能以及使用规则. 服务协定标识,标识哪些接口是服务协定,哪些操作时服务协定的 ...
- SQLite 入门教程(三)好多约束 Constraints(转)
转于: SQLite 入门教程(三)好多约束 Constraints 一.约束 Constraints 在上一篇随笔的结尾,我提到了约束, 但是在那里我把它翻译成了限定符,不太准确,这里先更正一下,应 ...
- 【知识整理】这可能是最好的RxJava 2.x 入门教程(三)
这可能是最好的RxJava 2.x入门教程系列专栏 文章链接: 这可能是最好的RxJava 2.x 入门教程(一) 这可能是最好的RxJava 2.x 入门教程(二) GitHub 代码同步更新:ht ...
- Photoshop入门教程(三):图层
学习心得:图层可以说是Photoshop的核心,看似简单,但是对于图像的各种编辑都是基于图层.他就像一层透明的.没有厚度的玻璃纸,每张玻璃纸设置不同的效果,层层叠加,最后显现出绚烂的效果. 在进行图像 ...
- Veins(车载通信仿真框架)入门教程(三)——多跳路由实现指导
Veins(车载通信仿真框架)入门教程(三)——多跳路由实现指导 Veins(车载通信仿真框架)入门教程(三)——多跳路由实现指导 必要的message类实现 从下面开始是在veins/src/vei ...
- [HeadFrist-HTMLCSS学习笔记]第三章构建模块:Web页面建设
[HeadFrist-HTMLCSS学习笔记]第三章构建模块:Web页面建设 敲黑板!! <q>元素添加短引用,<blockquote>添加长引用 在段落里添加引用就使用< ...
- WPF入门教程系列三——Application介绍(续)
接上文WPF入门教程系列二——Application介绍,我们继续来学习Application 三.WPF应用程序的关闭 WPF应用程序的关闭只有在应用程序的 Shutdown 方法被调用时,应用程序 ...
- C#入门教程(三)–接收用户输入、转义字符、类型转换-打造C#入门教程
上次教程主要讲解了visual stdio快捷键.变量相关的知识.具体教程戳这里:http://www.chengxiaoxiao.com/net/1027.html 越来越深入去写教程越来越发现,自 ...
随机推荐
- 用于解析FBNeo游戏数据的Python3脚本
FBNeo在代码中存储了游戏的元数据, 其数据格式为 struct BurnDriver BurnDrvCpsStriderua = { "striderua", "st ...
- Js中数组空位问题
Js中数组空位问题 JavaScript中数组空位指的是数组中的empty,其表示的是在该位置没有任何值,而且empty是区别于undefined的,同样empty也不属于Js的任何数据类型,并且在J ...
- flask+xlswriter+axios导出Excel
flask后端 starttime = request.json.get('starttime') endtime = request.json.get('endtime') # 根据时间查询数据库数 ...
- 如何拓展jwt返回的数据
默认的返回值仅有token,我们还需在返回值中增加username和id,方便在客户端页面中显示当前登陆用户 通过修改该视图的返回值可以完成我们的需求. 在user/utils.py中,创建 def ...
- shell之sort,uniq,cut,tr
# sort主要是要理解域 # sort选项 -c 测试文件是否已经分类 -m 合并两个分类文件 -u 删除所有复制行 -o 存储sort结果的输出文件名 -b 使用域进行分类时,忽略第一个空格 -n ...
- Taurus.MVC WebMVC 入门开发教程5:表单提交与数据验证
前言: 在本篇 Taurus.MVC WebMVC 入门开发教程的第五篇文章中,我们将学习如何处理表单提交和进行数据验证. 这是 Web 开发中非常重要的一部分,因为它涉及到用户输入数据的处理和有效性 ...
- C#的托盘窗体显示与隐藏效果 - 开源研究系列文章
今天无聊,进行的C#的编码内容仍然在继续.这些天不断地在完善及编写C#的Winform相关的代码,并将其整理形成博文.这次带来的是关于窗体的显示及隐藏效果的代码段.上次有过一个代码,这次当做新代码进行 ...
- vue3使用路由keep-alive和监听路由实现transition
随着vue3.0的发布,vue-router发布了4.0版本,文档 很明了,提供了vue2路由到vue3的变化和写法指导. vue2: // transition <transition nam ...
- 为什么Sync.Pool不需要加锁却能保证线程安全
1. 简介 我们在 Sync.Pool: 提高go语言程序性能的关键一步 一文中,已经了解了使用sync.Pool来实现对象的复用以减少对象的频繁创建和销毁,以及使用sync.Pool的一些常见注意事 ...
- [VueJsDev] 快速入门 - vscode 自动格式化
[VueJsDev] 目录列表 https://www.cnblogs.com/pengchenggang/p/17037320.html vscode 自动格式化(vue) ::: details ...