zoukankan      html  css  js  c++  java
  • C# 程序集

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

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

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

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

    4. 程序集的清单
      程序集清单是元数据的一部分,描述了程序集和引用它所需要的所有信息和依赖关系。
      • 标识(名称、版本、文化和公钥)。
      • 属于该程序集的一个文件列表。
      • 引用程序集的列表。
      • 一组许可请求——运行这个程序集所需要的许可。
      • 导出的类型。
    5. 命名空间和程序集

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

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

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

    8. 查看程序集
      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查看程序集时,只能找到一个清单。

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

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

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

      设置程序集属性1

      设置程序集属性2

    3. 动态加载和创建程序集
      在开发期间,添加对程序集的引用,该程序集中的类型就可用于编译器。
      可以变成加载程序集。为此可以使用类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;
              }
          }

      程序运行结果如图所示:

      程序运行结果

    4. 应用程序域
      .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运行结果

      接着创建控制台程序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

      看到第二行New AppDomain中新加载的程序集的输出结果里看不到AssemblyA.exe的执行,因为没有创建新的进程;用CreateInstance()替代ExecuteAssembly()方法:它的第一个参数是程序集名称,第二个参数定义了应实例化的类,第三个参数true表示不区分大小写,System.Reflection.BindingFlags.CreateInstance是一个绑定标志枚举值,指定应调用的构造函数;只需要将上面代码secondDomain.ExecuteAssembly("AssemblyA.exe");注释掉,下面/**/中的代码取消注释,运行结果如下:
      DomainTest运行结果2
      在运行期间,主应用程序域会自动创建。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()方法,可以查看应用程序域中加载的程序集。

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

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

    查看全局程序集缓存:在`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配置以后建立的事件命令行,就可以在全局程序集缓存中安装每个成功建立的程序集。

  • 相关阅读:
    redis发布订阅
    redis学习笔记(面试题)
    redis安全 (error) NOAUTH Authentication required
    HDU3001 Travelling —— 状压DP(三进制)
    POJ3616 Milking Time —— DP
    POJ3186 Treats for the Cows —— DP
    HDU1074 Doing Homework —— 状压DP
    POJ1661 Help Jimmy —— DP
    HDU1260 Tickets —— DP
    HDU1176 免费馅饼 —— DP
  • 原文地址:https://www.cnblogs.com/zzp0320/p/7890407.html
Copyright © 2011-2022 走看看