一、什么是程序集(Assembly)?

经由编译器编译得到的,供CLR进一步编译执行的那个中间产物,在WINDOWS系统中,它一般表现为·dll或者是·exe的格式,但是要注意,它们跟普通意义上的WIN32可执行程序是完全不同的东西,程序集必须依靠CLR才能顺利执行。 ----百度百科之程序集

程序集可分为两种类型:

(1)、可执行程序,后缀为.exe(GUI,图形用户接口;或CUI,命令行用户接口)

(2)、类库,后缀为.dll

其结构如下图:

在其构成中,只有PE头、CLR头、清单是必须的。其他均为可选的。

二、程序集结构解析

1、清单(Manifest)

我们要如何去查看一个程序集的清单呢?

此时我们就要借助微软自带的强大的工具ILDASM,此程序如果你要装Visual Studio就会自动帮你装上去,路径在:

C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe.

这里为了演示方便,我新建了一个程序集AssemblyLib作为演示。我们点击此工具的文件按钮,选择我们那个程序集文件,界面就如下图所示:

我们点击视图-元信息-显示! 滚动到Assembly就可看到AssemblyDef元数据表

里面包含了程序集的名称、公钥、版本、程序集特性等信息。我们接着往下翻,就可以看到FileDef表、ExportedTypeDef表、ManifestResourceDef表。FileDef表描述了构成程序集的模块信息、ExportedTypeDef表描述了外部模块中存在的类型信息、ManifestResourceDef表包含的是嵌入到程序集的资源信息。

除了主要的这四张表以外,清单中还包含了AssemblyRef表,此表定义了该程序集所引用的其他程序集信息。

从上面可以看出,清单描述了程序集的几乎一切信息,回答了这样几个问题:“程序集是什么(名称、版本、特性等)”“由什么构成(模块、资源)”和“外部依赖是什么(引用了其他哪些程序集)”。

2、元数据

所谓元数据就是描述数据的数据,这样看来,清单也是属于元数据的一种。

元数据与清单类似,也包含了几张表:

ModuleDef表,包含了当前模块的名称和后缀等信息。

TypeDef表,包含了每个类型的信息,这些信息包括类型名称、类型的基类、标记等信息(public、private等)。

MethodDef表,包含了每个方法的信息,这些信息包括方法名称、签名、标记等信息(public、static、virtual等)。

类似地,还有FieldDef、ParamDef、PropertyDef、EventDef几张表。

类型元数据中除了包含模块中定义的类型以外,还包含外部类型的引用,这些信息包含在另外一组表中:TypeRef、MemberRef。

我们只要知道,类型元数据,定义了程序集中所有类型的信息。

3、程序集资源

程序集中还可以包含资源(Resource),资源可以是字符串,也可以是任何格式的文件,比如图片、Excel文档等。

现在假设我们需要在项目中得到一张图片的资源,我们通常有三种做法:

<1>、将图片保存在程序根目录的文件夹下,然后通过路径获得。

第一种方法我们都很熟悉,这里就不作介绍了。

<2>、将图片作为资源嵌入程序集内。

第二种方法我们平常是比较少遇到的,我们看一下如何去做。

程序集的资源(Resource)是一段具有名称的字节数组。可以将资源想象成一个Dictionay<string,byte[]>,即一个以string为键,以byte[]

为值的字典。因为字节数组是二进制形式,所以资源可以是任何文件。

将资源嵌入程序集内也有两种方法:

①、将文件直接嵌入程序集

这种方法只要将文件添加到项目中,然后查看文件的"属性",将"生成操作"的值设为嵌入的资源。这里需要注意两点,一是资源加到程序集以后,资源的名称并不等于文件名,VS会自动在文件名前面加上程序集的默认命名空间、文件所在的文件夹名。二是

资源的名称是大小写敏感的。

那我们在程序中如何获取资源呢?

可以在调用Assembly类型的实例方法GetManifestResourceNames()中获得程序集的所有资源名称,接下来调用GetManifestResourceStream()方法获得资源的字节流 。

Assembly asm = Assembly.GetExecutingAssembly(); // 获得当前执行的程序集
string[] nameArray = asm.GetManifestResourceNames(); // 获得资源名称
foreach (string name in nameArray) {
Console.WriteLine(name);
using (Stream s = asm.GetManifestResourceStream(name)) {// 获得字节流
}

②、将.resources资源文件嵌入程序集

在第一种方法,资源在程序集中是零散的,我们为了集中管理资源,可以使用.resx文件将资源嵌入到程序集当中。.resx文件是一个XML格式的文本文件,记录了程序集中包含的资源名称和路径,它是程序开发时的设计工具,通过可视化的方式来对程序集中的资源进行分类和管理。注意.resx只是一个XML文本文件,类似一个资源清单,本身并不是程序集资源。在生

成程序集时,.resx会被自动转换为.resources文件,.resources文件包含了实际的资源文件(例如图片或者音频),并嵌入到程序集当中,但习惯上仍将.resx称作资源文件。

<3>、将资源作为独立程序集

在第二种方法中我们将资源直接嵌入到程序集中,会迅速增大程序集的体积,因此,我们可以将资源单独放在一个单独的程序集中,然后再由主程序集引用它。这样做的好处就是如果主程序没有

用到资源,那么就不用去加载这个程序集。我们可以先引用资源程序集的.dll(假设为res.dll),然后用如下的代码去访问资源程序集的资源:

Assembly asm = Assembly.Load("res");
ResourceManager r = new ResourceManager("Resource", asm);
...

在多语言的应用程序中,通常会将各个不同地区的语言文本作为资源,单独放到各自独立的程序集中,使得应用程序可以根据计算机的本地语言来显示相应资源中的文本。

此时我们只要在项目中新建一个资源文件,Resource.en.resx,在这个资源文件中添加字符串资源,名称为"address",值为"China,GuangDong,Shenzhen"。

重新生成项目,在bin\debug文件夹下,会看多多了一个子文件夹en,其中包含了ConsoleApp.resources.dll文件,该程序集包含了英文版本的资源。类似地,可以创建包含了德

语、日语等其他国家语言的程序集,这种程序集有一个形象的名字,叫做卫星程序集(Satellite Assembly)

Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
ResourceManager r = new ResourceManager("ConsoleApp.Resource",
Assembly.GetExecutingAssembly());
string address = r.GetString("address");
Console.WriteLine("address:" + address);

其中的"Thread.CurrentThread.CurrentUICulture=new System.Globalization.CultureInfo("en");"语句,将当前的UI区域性改为了en,即英语地区国家。之后,运行ConsoleApp.exe,会看到显示的输出为:

address:China, Guang Dong, Shenzhen

三、强名称程序集

1、强名称的定义

我们在新建程序集时,对程序集进行命名,那此时怎么命名比较好呢?

比如在上面我们建的程序集Assemblylib,别人也可以建这个名称的程序集,我们如何去比较好的划分呢。首先,我们先对同一个程序集不同版本进行划分,我们给程序集加上版本号以及区域性。 如下图:

这样之后,我们可以很好地分清自己的程序集之间不会发生冲突,但还是无法区分别人与自己的程序集。

为了解决这个问题,我们可以继续加入公司名、公司的URL以及GUID,这样,这个程序集的规则就有了唯一标识,这个时候就会衍生了另一个问题别人拿到你这个程序集后就可以看到你这个程序集的信息,从而进行仿冒。出于这些考虑,微软选择了使用公钥/私钥非对称加密(RSA)的方式,并结合使用了散列函数(SHA1)来保证:程序集的唯一性、防仿冒性、防篡改性。

2、为程序集赋予强名称

接下来我们看下如何实现防伪冒性:

我们需要用到一个工具SN.exe ,这个工具在你装VS的时候就会自动帮你装好,这个文件笔者的目录为:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools

我们可以使用vs自带的开发人员命令提示符,此工具笔者在:C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio 2017\Visual Studio Tools下 打开命令提示符输入sn.exe,我们可以看到如下图的帮助页面:

再输入 sn -k D:\Assemblysnk.snk 回车后就会将秘钥写入D盘根目录下

我们打开D盘根目录即可看到这个秘钥文件。

上面的文件中包含公钥及私钥,接下来我们将公钥提取出来并另存为另一个文件。

从上面的结果可以看出,公钥的字节数很长,有128字节,操作起来很不方便。因此,对公钥进行了散列运算,获得了一个它的8字节的哈希值,也就是公钥标记。由于公钥标记是公钥的摘要,或者“指纹”,它们是对等的,此时我们只要关注公钥标记即可。 我们再将公钥加入程序集规则即可。 单纯加公钥别人可以仿冒,并无多大意义,公私钥对就显出了效果。

我们使用VS,将公私钥对加入签名中。 这样就做到了程序集的防伪冒。(加密部分笔者这里就不一一赘述,有兴趣的请自行了解)。

总结

在这一部分的阅读学习中,笔者先解释了什么是程序集,分析了程序集的结构,还介绍了在程序集中嵌入资源的几种方法,接下来讲了强名称程序集如何一步步去做到唯一性与防伪。在我们日常的开发过程中,程序集是我们接触最多的文件,平时我们只懂得如何去生成与引用,并不知其具体的原理,这样进一步的了解,对我们以后的开发有着奠定基石的作用!

《.NET之美》之程序集的更多相关文章

  1. 读 《.Net 之美》解析.Net Remoting (应用程序域)-- Part.1

    读 <.Net 之美>解析.Net Remoting (应用程序域)-Part1 理解 .Net Remoting 前言: 看张子阳老师的文章,总是给自己很大的信心,这个专题基本上以张老师 ...

  2. 29防止程序集被篡改仿冒,全局程序集缓存GAC

      为什么需要强名称程序集和数字签名 有一个类库项目ClassLib,对应的程序集是ClassLib.dll.当前控制台项目引用ClassLib.dll程序集的方式有2种: 1.通过添加现有项目 文件 ...

  3. iOS开发系列--打造自己的“美图秀秀”

    --绘图与滤镜全面解析 概述 在iOS中可以很容易的开发出绚丽的界面效果,一方面得益于成功系统的设计,另一方面得益于它强大的开发框架.今天我们将围绕iOS中两大图形.图像绘图框架进行介绍:Quartz ...

  4. 一次修改闭源 Entity Provider 程序集以兼容新 EntityFramework 的过程

    读完本文你会知道,如何在没有源码的情况下,直接修改一个 DLL 以去除 DLL 上的强命名限制,并在该程序集上直接添加你的“友元程序集(一种特殊的 Attribute,将它应用在程序集上,使得程序集内 ...

  5. 【.net 深呼吸】程序集的热更新

    当一个程序集被加载使用的时候,出于数据的完整性和安全性考虑,程序集文件(在99.9998%的情况下是.dll文件)会被锁定,如果此时你想更新程序集(实际上是替换dll文件),是不可以操作的,这时你得把 ...

  6. 【.net 深呼吸】跨应用程序域执行程序集

    应用程序域,你在网上可以查到它的定义,凡是概念性的东西,大伙儿只需要会搜索就行,内容看了就罢,不用去记忆,更不用去背,“名词解释”是大学考试里面最无聊最没水平的题型. 简单地说,应用程序域让你可以在一 ...

  7. ADO.NET编程之美----数据访问方式(面向连接与面向无连接)

    最近,在学习ADO.NET时,其中提到了数据访问方式:面向连接与面向无连接.于是,百度了一下,发现并没有很好的资料,然而,在学校图书馆中发现一本好书(<ASP.NET MVC5 网站开发之美&g ...

  8. 运用Mono.Cecil 反射读取.NET程序集元数据

    CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改.CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修 ...

  9. .NET 程序集单元测试工具 SmokeTest 应用指南

    Smoke Test(冒烟测试),也称Regression Test(回归测试),是对软件的安装和基本功能的测试.一般地我们使用脚本来实现Smoke Test的自动化,可借用虚拟机的snapshot机 ...

随机推荐

  1. hiho 第六周 01背包

    简单的01背包,没有报名,这周的没有权限提交 #include<iostream> #include<memory.h> using namespace std; #defin ...

  2. LeetCode算法题(长期更新)

    1.给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样 ...

  3. Ubuntu18.04安装网易云音乐

    一. 安装 去网易云官网下载对应于ubuntu系统的安装包 安装依赖 dpkg -s libcanberra-gtk-module #检查依赖是否安装 sudo apt install libcanb ...

  4. Go语言函数

    目录 函数定义 函数返回多个值 函数参数 Go 语言函数值传递 Go语言函数引用传递 函数用法 函数作为值 匿名函数 闭包 方法 不定参数的函数 init函数 内建函数 函数调用机制 总结 函数定义 ...

  5. Baidu WebUploader 前端文件上传组件的使用

    简介 WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件.在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流I ...

  6. 夜谈Java类的定义

    女孩:谈Java了,好耶? 男孩:夜谈一下,Java的类的定义~ 女孩:那谈Java的类的什么呢? 男孩:类的定义,对象的定义,类中的方法,构造方法,this关键字,方法的重载,Java中的类的访问权 ...

  7. springboot源码解读

    springboot源码从main函数开始 public static void main(String[] args) { ApplicationContext app = SpringApplic ...

  8. nrf52832 SDK 15.3.0 Flash 操作

    debug中遇到的问题: 1,写入数据,读出来不对 2,看之前被人写的代码发现,读flash直接用的标准C库函数:memcpy,但是,写用的专门的flash写接口.所以,我这里两个疑问: A,写fla ...

  9. 大叔力量VIP介绍

    VIP介绍 框架技术点 平台 仓储 日志收集 缓存 消息队列 服务总线 事务 任务调度 授权 模块化 服务发现 .net ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ - .net core ✔ ✔ ✔ ✔ ✔ ...

  10. 一款jq的计时器

    举例子: http://files.cnblogs.com/Alandre/201201031633347950.rar