• C# 程序集


    一、概述
    1. 程序集是.NET应用程序的部署单元

    程序集是.NET应用程序的部署单元。
    程序集是自我描述的安装单元,由一个或多个文件组成。
    通常扩展名是EXE或DLL的.NET可执行程序称为程序集。
    .NET程序集包含元数据。

    1. 程序集的特性

      • 程序集是自我描述的。
      • 版本的相互依赖性在程序集的清单中记录。
      • 程序集可以并行加载。
      • 应用程序使用应用程序域来确保其独立性。
      • 安装简单。
    2. 程序集的结构

    程序集由描述它的元数据、描述导出类型和方法的类型元数据、MSIL代码和资源组成。

    1. 程序集的清单
      程序集清单是元数据的一部分,描述了程序集和引用它所需要的所有信息和依赖关系。

      • 标识(名称、版本、文化和公钥)。
      • 属于该程序集的一个文件列表。
      • 引用程序集的列表。
      • 一组许可请求——运行这个程序集所需要的许可。
      • 导出的类型。
    2. 命名空间和程序集

    命名空间完全独立于程序集。
    在一个程序集中可以有不同的命名空间,一个命名空间也可以分布在多个程序集中。
    命名空间是类名的一种扩展,属于类名范畴。

    1. 私有和共享程序集
      私有程序集在应用程序所在目录下或子目录中。
      私有程序集需要注意命名冲突。
      使用共享程序集时,需要注意:程序集必须是唯一的,有强名(唯一的名称,强制的版本号)

    2. 辅助程序集
      辅助程序集是只包含资源的程序集,它尤其适用于本地化。

    3. 查看程序集
      ildasm,这是一个MSIL反汇编程序。命令行运行ildasm,把程序集作为其参数或File|Open菜单。
      还有.NET Reflector是用于分析程序集的工具。

    二、构建程序集
    1. 创建模块和程序集

    模块是一个没有程序集特性的DLL。

    `csc /target:module hello.cs`
    创建模块`hello.netmodule`
    `ildasm hello.netmodule`查看这个模块。
    
    `/addmodule`选项,可以把模块添加到现有的程序集中。
    
    创建一个`A.cs`类文件,然后`csc /target:module A.cs`编译,生成了`A.netmodule`文件,它不包括程序集的信息;下面生成一个程序集B,它包括模块`A.netmodule`,命令如下:`csc /target:library /addmodule:A.netmodule /out:B.dll`,然后使用`IL`查看程序集:`ildasm B.dll`;如图所示:
    

    B.dll

    在使用IL查看程序集时,只能找到一个清单。
    
    模块的作用是更快地启动程序集,因为不是所有类都在一个文件中,模块只在需要时加载。另外可以使程序集可以使用多种编程语言来创建。
    
    1. 程序集的属性

      • ./Solution Explorer/Properties/AssemblyInfo.cs文件,可以使用一般的源代码编辑器配置程序集的属性。
      • assembly前缀把属性标记为全局属性。
      • 程序集的全局属性与特定的语言元素无关,用于程序集属性的参数是命名空间System.ReflectionSystem.Runtime.Compiler ServicesSystem.Runtime.InteropServices中的类。
      • System.Reflection命名空间中定义的程序集属性列表:

      命名空间中定义的程序集属性
      在VS中,可以右键项目 - 属性 - 应用程序设置 - 程序集信息 来配置这些属性:

      设置程序集属性1

      设置程序集属性2

    2. 动态加载和创建程序集
      在开发期间,添加对程序集的引用,该程序集中的类型就可用于编译器。
      可以变成加载程序集。为此可以使用类Assembly的静态方法Load(),这个方法是重载的,可以使用AssemblyName给它传送程序集的名称或字节数组。
      例子:使用WPF,输入代码,点击按钮执行并将结果显示到TextBlock中。
      UI

      创建类CodeProvider,定义方法CompileAndRun(),编译文本框中的代码,启动所生成的方法。代码如下:

    using System;
    using System.CodeDom.Compiler;
    using System.IO;
    using System.Reflection;
    using System.Text;
    using Microsoft.CSharp;
    namespace 动态加载和创建程序集
    {
        public class CodeProvider
        {
            private string prefix =
                "using System;" +
                "public static class Driver" +
                "{" +
                "public static void Run()" +
                "{";
    
            private string postfix =
                "}" +
                "}";
            public string CompileAndRun(string input, out bool hasError)
            {
                hasError = false;
                string returnData = null;
    
                CompilerResults results = null;
                using (CSharpCodeProvider provider = new CSharpCodeProvider())
                {
                    CompilerParameters options = new CompilerParameters();
                    options.GenerateInMemory = true;
    
                    StringBuilder sb = new StringBuilder();
                    sb.Append(prefix);
                    sb.Append(input);
                    sb.Append(postfix);
    
                    results = provider.CompileAssemblyFromSource(
                        options, sb.ToString());
                }
    
                if(results.Errors.HasErrors)
                {
                    hasError = true;
                    StringBuilder errorMessage = new StringBuilder();
                    foreach (CompilerError error in results.Errors)
                    {
                        errorMessage.AppendFormat("{0} {1}", error.Line, error.ErrorText);
                    }
    
                    returnData = errorMessage.ToString();
                }
                else
                {
                    TextWriter temp = Console.Out;
                    StringWriter writer = new StringWriter();
                    Console.SetOut(writer);
                    Type driverType = results.CompiledAssembly.GetType("Driver");
    
                    driverType.InvokeMember("Run", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, null, null, null);
                    Console.SetOut(temp);
    
                    returnData = writer.ToString();
                }
    
                return returnData;
            }
                
        }
    }
    
    按钮的`Click`事件连接到`Compile_Click`方法上:实例化`CodeProvider`类,调用方法`CompileAndRun()`;从文本框textCode中提取输入,把结果写到TextBlock控件textOutput中:
    
    private void Compile_Click(object sender, RoutedEventArgs e)
            {
                CodeProvider driver = new CodeProvider();
                bool isError;
                textOutput.Text = driver.CompileAndRun(textCode.Text, out isError);
                if(isError)
                {
                    textOutput.Background = Brushes.Red;
                }
            }
    
      程序运行结果如图所示:
    
    ![程序运行结果](//upload-images.jianshu.io/upload_images/4033095-94cb2e0f34bffab6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    1. 应用程序域
      .NET的应用程序边界:应用程序域。

    使用托管IL代码,运行库就不能访问同一个进程中的另一个应用程序的内存。多个应用程序可以运行在一个进程的多个应用程序域中。

    `AppDomain`类用于创建和中断应用程序域,加载和卸载程序集和类型、枚举域中的程序集和线程。
    
    例子:创建控制台程序`AssemblyA`,在`Main()`方法中添加`Console.WriteLine()`,再添加一个类`Demo`,其构造函数的参数是两个`int`值,用`AppDomain`类创建实例。
    
    namespace AssemblyA
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Main in domain {0} called", AppDomain.CurrentDomain.FriendlyName); ;
                Console.ReadKey();
            }
        }
    }
    
    ```
    

    namespace AssemblyA
    {
    public class Demo
    {
    public Demo(int val1, int val2)
    {
    Console.WriteLine("Constructor with the values {0}, {1}" + " in domain {2} called", val1, val2, AppDomain.CurrentDomain.FriendlyName);
    }
    }
    }

    运行结果如下图所示:
    
        ![AssemblyA运行结果](//upload-images.jianshu.io/upload_images/4033095-8833f9c9edc926c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
        接着创建控制台程序`DomainTest`,首先使用`AppDomain`类的`FriendlyName`属性显示当前域的名称;调用`CreateDomain()`方法,创建一个新的应用程序域`New AppDomain`,然后把程序集`AssemblyA`加载到新域中,通过调用`ExecuteAssembly()`来调用`Main()`方法:
    

    namespace DomainTest
    {
    class Program
    {
    static void Main(string[] args)
    {
    AppDomain currentDomain = AppDomain.CurrentDomain;
    Console.WriteLine(currentDomain.FriendlyName);

            AppDomain secondDomain = AppDomain.CreateDomain("New AppDomain");
            secondDomain.ExecuteAssembly("AssemblyA.exe");
            /*secondDomain.CreateInstance(
                "AssemblyA",
                "AssemblyA.Demo",
                true,
                System.Reflection.BindingFlags.CreateInstance,
                null,
                new object[] { 7, 3 }, null, null, null);*/
    
            Console.ReadKey();
        }
    }
    

    }

        启动程序前先把`Assembly.exe`复制到当前项目目录下`./bin/Debug`,运行结果:
        ![DomainTest运行结果1.png](//upload-images.jianshu.io/upload_images/4033095-b6e7782d2fa3b8d6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
        看到第二行`New AppDomain`中新加载的程序集的输出结果里看不到`AssemblyA.exe`的执行,因为没有创建新的进程;用`CreateInstance()`替代`ExecuteAssembly()`方法:它的第一个参数是程序集名称,第二个参数定义了应实例化的类,第三个参数`true`表示不区分大小写,`System.Reflection.BindingFlags.CreateInstance`是一个绑定标志枚举值,指定应调用的构造函数;只需要将上面代码`secondDomain.ExecuteAssembly("AssemblyA.exe");`注释掉,下面`/**/`中的代码取消注释,运行结果如下:
        ![DomainTest运行结果2](//upload-images.jianshu.io/upload_images/4033095-ae5d4144a9364d0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    在运行期间,主应用程序域会自动创建。ASP.NET为每个运行在Web服务器上的Web应用程序创建一个应用程序域;Internet Explorer创建运行托管控件的应用程序域;对于应用程序,**卸载程序集只能通过中断应用程序域来进行**。
        > 如果程序集是动态加载的,且需要在使用完后卸载程序集,应用程序域就是非常有用的。在主程序域中,不能删除已加载的程序集,但可以终止应用程序域,在该应用程序域中加载的所有程序集都会从内存中清除出去。
    
        修改之前的WPF程序:添加一个新类,使用`CreateInstanceAndUnwrap()`实例化类`CodeDriver`,调用`CompileAndRun()`方法,之后再次卸载新应用程序域。
        ```
    namespace 动态加载和创建程序集
    {
        public class CodeDriverInAppDomain
        {
            public string CompileAndRun(string code, out bool hasError)
            {
                AppDomain codeDomain = AppDomain.CreateDomain("CodeDriver");
                CodeDriver codeDriver = (CodeDriver)
                    codeDomain.CreateInstanceAndUnwrap(
                        "动态加载和创建程序集",
                        "动态加载和创建程序集.CodeDriver");
                string result = codeDriver.CompileAndRun(code, out hasError);
    
                AppDomain.Unload(codeDomain);
    
                return result;
            }
        }
    }
    

    要在另一个应用程序域中访问类CodeDriver,类CodeDriver就必须派生于基类MarshalByRefObject。所以CodeDriver.cs修改:

    namespace 动态加载和创建程序集
    {
        public class CodeDriver : MarshalByRefObject
        {
            ...
        }
    

    按钮事件处理程序可以修改为使用新类CodeDriverInAppDomain

    private void Compile_Click(object sender, RoutedEventArgs e)
    {
           //CodeDriver driver = new CodeDriver();
           CodeDriverInAppDomain driver = new CodeDriverInAppDomain();
           bool isError;
           textOutput.Text = driver.CompileAndRun(textCode.Text, out isError);
           if(isError)
           {
               textOutput.Background = Brushes.Red;
           }
    }
    

    使用AppDomain类的GetAssemblies()方法,可以查看应用程序域中加载的程序集。

    1. 共享程序集
      共享程序集必须有一个强名
      共享程序集必须有一个强名,来唯一地标识该程序集。
      强名由以下组成:
      • 程序集本身的名称
      • 版本号
      • 公钥
      • 文化

    为了唯一地标识公司中的程序集,应使用命名空间层次结构来给类命名。

    查看全局程序集缓存:在`C:Windowsassembly `  目录下 。
    程序集查看器可以使用Windows资源管理器查看和删除程序集。`gacutil.exe`工具可以使用命令行安装、卸载和显示程序集。
    
    **创建共享程序集**
    

    建立一个Visual C# Class Library项目SharedDemo;命名空间Wrox.ProCSharp.Assemblies.Sharing,类名SharedDemo
    类的构造函数把文件的所有行都将读取到一个集合中;文件名作为参数传送给构造函数;方法GetQuoteOfTheDay()只返回这个集合的一个随机字符串。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.IO;
    namespace Wrox.ProCSharp.Assemblies.Sharing
    {
        public class SharedDemo
        {
            private List<string> quotes;
            private Random random;
    
            public SharedDemo(string filename)
            {
                quotes = new List<string>();
                Stream stream = File.OpenRead(filename);
                StreamReader streamReader = new StreamReader(stream);
                string quote;
                while ((quote = streamReader.ReadLine()) != null)
                {
                    quotes.Add(quote);
                }
                streamReader.Close();
                stream.Close();
                random = new Random();
            }
    
            public string GetQuoteOfTheDay()
            {
                int index = random.Next(1, quotes.Count);
                return quotes[index];
            }
        }
    }
    

    要共享这个程序集,需要一个强名,使用强名工具(sn):sn -k mykey.snk,强名工具生成和编写一个公钥/私钥对,并把改密钥对写到文件中。
    在VS中,可以选择签名(signing)选项卡,用项目属性标记程序集。
    签名(signing)
    设置了signing选项后,重新建立文件后,公钥就在程序集清单中。
    程序集清单

    **安装共享程序集**
    

    程序集中有了公钥后就可以使用全局程序集缓存工具gacutil及其/i选项把它安装到全局程序集缓存中:
    gacutil /i SharedDemo.dll
    用VS配置以后建立的事件命令行,就可以在全局程序集缓存中安装每个成功建立的程序集。

    摘自《C#搞基程序设计》

    摘自《C#搞基程序设计》 - 2017年1月25日11:22:28 - P.440

  • 相关阅读:
    PHPExcel读取excel03/07版到数组
    firefox 自定义快捷键
    phpstorm 自定义函数配置
    解决FPDF报错:FPDF error: Not a JPEG file / FPDF error: Not a PNG file
    mysql学习笔记
    定时备份服务器数据库(借助windows任务计划以及mysqldump)
    discuz 注册用户用到的几个表
    phpstorm配置取消掉63342
    discuz random函数
    discuz X3.2邮箱非必填
  • 原文地址:https://www.cnblogs.com/jehorn/p/7744320.html
Copyright © 2020-2023  润新知