说明:
最近开始准备把学到的 .NET 知识重新整理一遍,眼过千遍不如手过一遍,所以我准备记下我的学习心得,已备参考。 J
各位都是大虾了,如果有哪些错误或者不完整的地方,还请不吝指出。
多谢了。
本文分为两部分:
第一部分是一些必须了解的概念;
第二部分是一个完整的例子来逐一说明这些概念;
第一部分 基本概念
托管模块( Managed Module
托管模块是一个需要 CLR 才能执行的标准 Windows 可移植可执行( portable executable ,简称 PE )文件。
元数据( Metadata
简单的讲,元数据就是一个数据表的集合,在这些表中,其中一些用于描述托管模块中所定义的内容(比如所定义的类型和它们的成员),另外还有一些用于描述托管模块中所引用的内容(比如被引用的类型和它们的成员)。
URL: ms-help://MS.MSDNQTR.2004APR.1033/cpguide/html/cpconmetadataoverview.htm
程序集清单( Assembly Manifest
程序集清单是另外一些元数据表的集合。这些表描述了组成程序集的文件,程序集所有文件中实现的公有导出类型,以及一些程序集相关的资源文件或数据文件。
ms-help://MS.MSDNQTR.2004APR.1033/cpguide/html/cpconAssemblyManifest.htm
1. 程序集( Assembly )的概念:
首先:程序集是一个或多个托管模块,以及一些资源文件的逻辑组合。因为它是一个逻辑上的组合,所以程序集的逻辑表示和物理表示可以相互分离。如何将代码和资源划分到不同的文件中完全取决于我们。例如,我们可以将一些很少使用的类型或资源放在一个单独的 Assembly Module 中,然后根据需要(比如第一次用到的时候),从 web 上下载它们。如果没有用到,它们将不会被下载。这样既节省磁盘空间,也减少了安装时间。程序集允许我们将文件的部署分解开来,同时又将所有的文件看作一个单独的集合。
     其次:因为 CLR 是直接和程序集打交道的,所以程序集也是组件复用,以及实施安全策略和版本策略的最小单元(安全策略,版本信息等都只能是加在程序集上)。
注意: 程序集是一个逻辑组合,它可以包含很多个文件。 大多数程序集(比如使用 Visual Studio.NET 创建的那些)一般都是单文件程序集,也就是只有一个 .exe 或者 .dll 文件(目前 .NET 的程序集只有这两种格式)。在这种情况下,程序集清单( manifest )直接嵌入到单文件程序集中。但是,你也可以用 “ 程序集生成工具 ” ( Al.exe )来创建多文件程序集。也可以只创建一个只包含清单的程序集。
2. 强命名程序集( Strong Name Assembly )的概念
因为不同的公司可能会开发出有相同名字的程序集来,如果这些程序集都被复制到同一个相同的目录下,最后一个安装的程序集将会代替前面的程序集。这就是著名的 Windows “DLL Hell” 出现的原因。
很明显,简单的用文件名来区分程序集是不够的, CLR 需要支持某种机制来唯一的标识一个程序集。这就是所谓的强命名程序集。
一个强命名程序集包含四个唯一标志程序集的特性:文件名(没有扩展名),版本号,语言文化信息(如果有的话),公有秘钥。
这些信息存储在程序集的清单( manifest )中。清单包含了程序集的元数据,并嵌入在程序集的某个文件中。
下面的字符串标识了四个不同的程序集文件:
“MyType, Version=1.0.1.0, Culture=neutral, PublicKeyToken=bf5779af662fc055”
“MyType, Version=1.0.1.0, Culture=en-us, PublicKeyToken=bf5779af662fc055”
“MyType, Version=1.0.2.0, Culture=neturl, PublicKeyToken=bf5779af662fc055”
“MyType, Version=1.0.2.0, Culture=neutral, PublicKeyToken=dbe4120289f9fd8a”
如果一个公司想唯一的标识它的程序集,那么它必须首先获取一个公钥 / 私钥对,然后将共有秘钥和程序集相关联。不存在两个两个公司有同样的公钥 / 私钥对的情况,正是这种区分使得我们可以创建有着相同名称,版本和语言文化信息的程序集而不引起任何冲突
     与强命名程序集对应的就是所谓的弱命名程序集。(其实就是普通的没有被强命名的程序集)。两种程序集在结构上是相同的。都使用相同的 PE 文件格式, PE 表头, CLR 表头,元数据,以及清单( manifest )。二者之间真正的区别在于:强命名程序集有一个发布者的公钥 / 私钥对签名,其中的公钥 / 私钥对唯一的标识了程序集的发布者。利用公钥 / 私钥对,我们可以对程序集进行唯一性识别、实施安全策略和版本控制策略,这种唯一标识程序集的能力使得应用程序在试图绑定一个强命名程序集时, CLR 能够实施某些 “ 已确知安全 ” 的策略(比如只信任某个公司的程序集)。
3. 如何创建强命名程序集( Strong Name Assembly
创建一个强命名程序集首先需要获得一个用强命名实用工具( Strong Name Utility ,即 SN.exe , .NET SDK 自带)产生的密钥。
下面简要介绍一下 SN.exe 的一些用法。
要产生一个公钥 / 私钥对:
a) SN –k MyCompany.Keys
该命名告诉 SN.exe 创建一个名为 MyCompany.keys 的文件。 MyCompany.keys 文件将包含以对以二进制格式存储的公有密钥和私有密钥。
b) 查看公有密钥:
首先生成一个只包含公有密钥的文件:
SN –p MyCompany.keys MyCompany.PublicKey
然后用 -tp 参数查看: SN –tp MyCompany.PublicKeys
Public key is
0024000004800000940000000602000000240000525341310004000001000100bb7214723ffc13
901343df4b9c464ebf7ef4312b0ae4d31db04a99673e8163768cc0a2a7062e731dbeb83b869f05
09bf8009e90db5c8728e840e782d2cf928dae35c2578ec55f0d11665a30b37f8636c08789976d8
ee9fe9a5c4a0435f0821738e51d6bdd6e6711a5acb620018658cce93df37d7e85f9a0104a58450
53995ce8
Public key token is 2dc940d5439468c2
创建好了公钥 / 私钥对,创建强命名程序集就很容易了。只需要把 System.Reflection.AssemblyKeyFileAttribute 特性 加入到源代码中就可以了: [assembly:AssemblyKeyFile("MyCompany.keys")]
说明:公钥 / 私钥对文件的扩展名可以是任意的(也可以没有),因为编译的时候都是以元数据的格式读取的。
4. 程序集的部署方式
一个程序集有两种部署方式:
a) 私有方式
和应用程序部署在同一目录下的程序集称作私有部署程序集。弱命名程序集只能进行私有部署。
b) 全局方式
全局部署方式将程序集部署在一些 CLR 已确知的地方,当 CLR 搜索程序集时,它会知道到这些地方去找。强命名程序集既可以进行私有部署,也可以进行全局部署。
程序集种类
是否可以进行私有部署
是否可以进行全局部署
普通程序集
强命名程序集
5. 如何部署强命名程序集( Strong Name Assembly )和 GAC
a) GAC 的概念
如果一个 Assembly 要被多个应用程序访问,那么他就必须放在一个 CLR 已确知的目录下,并且 CLR 在探测到有对该 Assembly 的引用时,它必须能自动到该目录下寻找这个程序集。这个已确知的目录称作 GAC ( Global Assembly Cache ),就是全局程序集缓存。它一般位于下面的目录下: :/Windows/Assembly/GAC 。
GAC 的作用就是提供给 CLR 一个已知的确定的目录去寻找引用的程序集。
b) GAC 的内部结构
GAC 是一个特殊的结构化的目录,用 Windows Explorer 浏览你会以为它只是一个包含很多程序集的普通目录。其实不是这样的,在命令行下查看,你会发现它实际上包含很多子目录,子目录的名字和程序集的名称是相同的,但它们都不是实际的程序集,实际的程序集位于程序集名对应的目录下。比如进入 GCFWK 子目录,我们会发现其中又有很多的子目录。
机器内每一个安装到 GAC 的 GCFWK.dll 在 GCFWK 中都会有一个子目录。这里只有一个目录表明只有一个版本的 GCFWK 程序集被安装。实际的程序集保存在每一个对应的版本目录下。目录的名称以下划线的形式分割为 “(Version)_(Culture)_(PublicKeyToken)” 。
GCFWK 的语言文化信息为 netture ,就表示为 1.0.0.0__bf5779af662fc055” 。
表示得意义是:
“GCFWK, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bf5779af662fc055”
如果语言文化信息为 ”ja” ,就表示为 ”1.0.0.0_ja_bf5779af662fc055”
表示得意义是:
“GCFWK, Version=1.0.0.0, Culture=ja, PublicKeyToken=bf5779af662fc055”
c) 部署强命名程序集到 GAC
GAC 包含很多子目录,这些子目录是用一种算法来产生的,我们最好不要手动将程序集拷贝到 GAC 中,相反,我们应使用工具来完成这样的工作。因为这些工具知道 GAC 的内部结构 J
在开发和测试中,最常用的工具就是 GACUtil.exe 。
在 GAC 中注册程序集跟 COM 注册差不多,但相对更容易:
1 .把程序集添加到 GAC 中:
GACUtil /i sample.dll
(参数 /i 是安装的意思)
2 .把程序集移出 GAC
GACUtil /u sample.dll
(参数 /u 就移除的意思)
注意:不能将一个弱命名程序集安装到 GAC 中。
如果你试图把弱命名程序集加入到 GAC 中,会收到错误信息: ” Failure adding assembly to the cache: Attempt to install an assembly without a strong name”
d) 强命名程序集的私有部署
把程序集安装到 GAC 有几个好处。首先, GAC 使得很多程序可以共享程序集,这从整体上减少了使用的物理内存;其次,我们很容易将一个新版的程序集部署到 GAC 中,并通过一种发布者策略(差不多就是一种重定向方法,比如将原来引用版本为 1.0.0.0 程序集的程序,通过更改它的配置文件,转而让程序去引用版本为 2.0.0.0 的程序集)来使用新版本;最后, GAC 还提供了对不同版本程序集的并存( side-by-side )管理方式。但是, GAC 的安全策略通常只允许管理员更改,同时,向 GAC 中安装程序集也破坏了 .NET 框架的简单拷贝部署的许诺。
除了向 GAC 或者以私有部署方式部署强命名程序集之外,我们还可以将强命名程序集部署在仅为一小部分程序知道的某个任意目录下。配置每一个应用程序的 XML 配置文件,让它们指向一个公有目录,这样,在运行时, CLR 将知道到哪里去找这个强命名程序集。但这样又有可能会引发 ”DLL Hell” 的问题,因为没有哪个程序可以控制这个程序集何时被卸载。这在 .NET 中也是不被鼓励的。
6. 并行执行( Side - By - Side
这里是一个强命名程序集的例子:
首先有一个 App.exe 程序集,它绑定这一个版本为 2.0.0.0 的 Calculus.dll 程序集和一个版本为 3.0.0.0 的 AdvMath.dll 程序集。而 AdvMath.dll 程序集同时又绑定着一个版本为 1.0.0.0 的 Calculus.dll 的程序集。如下图:
An application that requires different versions of the Calculus.dll assembly
    CLR 能够将名程相同但路径不同的多个文件加载到同一个地址空间,这在 .NET 中称为并存执行( Side-By-Side )执行,它是解决 Windows 中 ”DLL Hole” 问题的关键技术。
   如 .NET Framework 1.0 和 .NET Framework 1.1 都可以同是在一台机器上运行,这用到的就是并存执行( Side-By-Side )执行。
更多参考:
ms-help://MS.MSDNQTR.2004APR.1033/cpguide/html/cpconSide-by-SideExecutionTop.htm
http://www.microsoft.com/china/msdn/archives/library/dnnetdep/html/sidexsidenet.asp
7. CLR 如何解析类型引用
CLR 在解析一个被引用的类型时,它可以在以下三个地方的其中之一找到该类型:
· 同一个文件
对同一个文件中类型的访问在编译时就已经确定下来了, CRL 直接从该文件中加载被引用的类型。完成加载后,程序将继续运行。
· 不同的文件,相同的程序集
CLR 首先确保被引用的文件在当前程序集清单中的 FileDef 表内。 CLR 然后会在加载程序集清单文件的目录中查找被引用的文件。该文件被加载的同时, CLR 会检查它的散列值以确保文件的完整性,之后便会找到相应的类型成员。完成加载后,程序将继续运行。
· 不同的文件,不同的程序集
当被引用的程序集在一个不同的程序集文件中时, CLR 会首先加载包含被引用程序集的清单所在的文件。如果该文件没有包含所需要的类型, CLR 会根据此清单文件加载适当的文件。这样也会找到相应类型的成员。完成加载后,程序将继续运行。
        如果在解析类型引用的过程中出现任何错误,比如文件找不到,文件不能被加载,散列值不匹配等等,系统将会抛出相应的异常。
下图演示了类型的绑定过程:
程序集种类
是否可以引用命名程序集
是否可以引用命名程序集
普通程序集
强命名程序集
第二部分 实例
下面是一个完整的例子来逐一说明上面所提到的概念,以加深理解。
整个实例包含 7 个文件(在主目录下):
主目录为    …/Assembly         ---- 源程序目录
                  …/Assembly/Bin     ---- 编译结果输出的目录,也就是应用程序主目录。
文件名
类型
说明
App.cs
Code 源文件
主程序,包含程序入口,属于 namespace1
ClassA.cs
Code 源文件
类型 A ,包含一个静态方法,属于 namespace1
ClassB.cs
Code 源文件
类型 B ,包含一个静态方法,属于 namespace2
AssemblyInfo.cs
Code 源文件
包含程序集签名信息,版本信息等
App.Key
公钥 / 私钥对文件
用来给程序集签名,生成强命名程序集
App.PublicKey
只包含共有密钥
只储存共有密钥,用 SN.exe 来查看
App.exe.config
Xml 格式配置文件
App.exe 的应用程序配置文件
源代码
App.cs
namespace namespaceA {
    public class App    {
       static void Main(string[] args) {
          System.Console.WriteLine(ClassA.ShowMe());
          System.Console.WriteLine(namespaceB.ClassB.ShowMe());
       }
    }
}
ClassA.cs
namespace namespaceA
{
public class ClassA   {
    public static string ShowMe()  {
          return "This is ClassA";
       }
    }
}
ClassB.cs
namespace namespaceB
{
    public class ClassB {
       public static string ShowMe()  {
          return "This is ClassB";
       } 
   }
}
AssemblyInfo.cs
// Module: AssemblyInfo.cs
using System.Reflection;
// Set CompanyName, LegalCopyright, and LegalTrademarks
[assembly: AssemblyCompany("App Company")]
[assembly: AssemblyCopyright("Copyright (C) 2004 @ App Company")]
[assembly: AssemblyTrademark("App is a test only program")]
// Set ProductName and ProductVersion
[assembly: AssemblyProduct("App Product")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]
// Set FileVersion and AssemblyVersion
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyTitle("App type assembly")]
[assembly: AssemblyDescription("App Aassembly is a test only assembly")]
// Set Culture
[assembly: AssemblyCulture("")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("App.key")]
[assembly: AssemblyKeyName("")]
App.key 和 App.PublicKey 是二进制格式存储的,不能直接查看。后面例子中会用到。
1. 把源代码编译为托管模块( Managed Module
csc /out:bin/classA.module /t:module classA.cs
参数: /out: 输出路径
       /t: 输出格式。可以有四种,分别是:
library  ---- DLL 程序集
exe ---- 控制台可执行程序(也是程序集的一种)
winexe ---- Windows 可执行程序(同样也是程序集的一种)
module ---- 托管模块(程序集的一部分)
说明:
托管模块的的扩展名可以是任意的(也可以没有),因为编译的时候都是以元数据的格式读取的。
2. 把源代码编译为程序集( Assembly
l 把 ClassB 编译为一个单文件程序集
csc /out:bin/classB.dll /t:library classB.cs
l 把 App.cs , ClassA.module 和 ClassB.dll 编译为一个多文件程序集
csc /out:bin/App.exe /t:exe app.cs /addmodule:bin/classA.module /r:bin/classB.dll
参数:
/addmodule: 把托管模块添加到程序集中
       /r: 添加引用
说明:
上面生成的程序集因为没有经过公钥 / 私有签名,所以生成的是非强命名类型的程序集。
生成的程序集 App.exe 的清单中只包含对 classA.module 托管模块的说明,并不包含 classA.module 的元数据,所以 App.exe 和 classA.moudle 必须在同一目录中。 App.exe 在运行时,如果用到对 classA.module 中类型的引用,则会去 classA.moudel 文件进行查找,如果 classA.moude 文件不存在,则会引发 System.IO.FileNotFoundException 。如果 App.exe 不会用到 class.module 中的类型,则 classA.module 存不存在都不会对 App.exe 的执行产生任何影响(这就是上面提到的 Assembly 的好处之一, Assembly 只是一个逻辑上的组合)。
App.exe 还用到了对 ClassB.dll 的引用,因为 classB.dll 不是一个强命名类型,所以它只能进行私有部署,可以和 App.exe 放在一起,也可以放在主目录下的其他子目录下。(后面通过应用程序更改配置文件,可以重定向指向 classB.dll 的引用)。
3. 更改应用程序配置文件( App.exe.config),重定向对classB.dll的引用。
现在 App.exe , classA.moudle 和 classB.dll 都在 Bin 目录下, app.exe 在运行时会找到所有它需要的类型,所以运行正常。
如果把在 Bin 目录下新建一个目录,比如 sub ,并把 classB.dll 移动到 sub 目录下,再运行 App.exe 就会出错。同样会引发 System.IO.FileNotFoundException 错误,因为 App.exe 运行时需要的 classB 类型找不到。这时候就需要更改添加(如果没有)或更改应用程序配置文件,应用程序配置文件是一个 xml 格式的配置文件,和 web.config 文件的作用差不多,是配置应用程序运行时的行为的。
注意:配置文件的名字必须是应用程序名字再加一个 .config ,且必须在同一目录下
详细信息参考:
ms-help://MS.MSDNQTR.2004APR.1033/cpguide/html/cpconnetapplicationconfigurationscenarios.htm
App.exe.config 文件的内容:
xml version ="1.0" encoding ="utf-8" ?>
< configuration >
      <runtime>
            <assemblyBindingxmlns="urn:schemas-microsoft-com:asm.v1">
                <probingprivatePath="sub"/>
            > 
      <runtime>
>
当App.exe运行时,它就会在主目录下的sub目录中找到classB.dll,然后继续执行。
说明:
CLR需要定位一个程序集时,它将扫描应用程序的几个子目录,下面是才CLR扫描一个程序集时的顺序:
…/Assembly/Bin/classB.DLL.
…/Assembly/Bin/classB/classB.DLL.
…/Assembly/Bin/sub/classB.DLL.
…/Assembly/Bin/sub/classB/classB.DLL.
…/Assembly/Bin/classB.EXE.
…/Assembly/Bin/classB/classB.EXE.
…/Assembly/Bin/sub/classB.EXE.
…/Assembly/Bin/sub/classB/classB.EXE.
注意:
如果App.exe引用的是强命名程序集,CLR会首先在GAC中查找,然后才按照上面的顺序查找。
 
4.创建和查看公钥/私钥对文件
创建公钥/私钥对文件可以用.NET SDK自带的工具(SN.exe)来创建。
首先,创建一个公钥/私钥对文件
SN -k App.key
然后,用这个文件创建只包含共有密钥的文件:
SN -p App.key App.publickey
然后用-tp参数查看
SN –tp App.publickey
5创建强命名程序集
有了公钥/私钥对,创建强命名程序集就很容易了。只需要把System.Reflection.AssemblyKeyFileAttribute特性加到源代码中就可以了。
[assembly: AssemblyKeyFile("App.key")]
一般都加到AssemblyInfo.cs文件中。
现在重新build classB.cs,得到的将是一个强命名的程序集:
csc /out:bin/classB.dll /t:library classB.cs AssemblyInfo.cs
用ILDasm.exe查看,你会发现,在Assembly中的Public Key会有一大串值,这个就是程序集的公有密钥,它保证了整序集的唯一性。
6.把强命名的程序集classB.dll加入到GAC中。
使用工具GACUtil.exe
classB.dll加入到GAC中:
GACUtil /I classB.dll
删除掉classB.dll,然后重新Build App.exe:
csc /out:bin/app.exe /t:exe app.cs /addmodule:bin/classA.module /r:classB.dll
App.exe运行正确,表明classB.dll已经成功加入到GAC中,成为一个共享程序集了。
程序集相互引用的规则:
程序集种类
是否可以引用命名程序集
是否可以引用命名程序集
普通程序集
强命名程序集
classB.dll移出GAC:
GACUtil /u classB
注意:
移出的时候不能指定扩展名(因为在GAC中每一程序集对于对应的都是一个目录,而不是实际的程序集)。
如果classB.dll不是一个强命名的程序集,而你想把app.exe build为一个强命名的,就会出错:
error CS1577: Assembly generation failed -- Referenced assembly 'ClassB' does??????? not have a strong name
你可以试一试。J
7.并行执行(Side-By-Side)的例子
http://www.microsoft.com/china/msdn/archives/library/dnnetdep/html/sidexsidenet.asp

Assembly学习相关推荐

  1. Assembly学习心得

    http://blog.csdn.net/etmonitor/ Assembly学习心得 说明: 最近开始准备把学到的.NET知识重新整理一遍,眼过千遍不如手过一遍,所以我准备记下我的学习心得,已备参 ...

  2. C#程序集Assembly学习随笔(第一版)_AX

    ①什么是程序集? 可以把程序集简单理解为你的.NET项目在编译后生成的*.exe或*.dll文件. 嗯,这个确实简单了些,但我是这么理解的.详细: http://blog.csdn.net/sws83 ...

  3. C#程序集Assembly学习随笔(增补版,附图)_AX

    我一直以为我是个人才,今天才TMD知道,我是个天才!  下面的问题一下就弄出来了. 上篇我讲了下我对Assembly的初步认知. 现在来说说怎么使用已有的Assembly. 比如说我在做一个解决方案时 ...

  4. GP学习整理(一)—Geoprocessing assembly and Geoprocessor managed assembly

    声明:仅做自己学习整理用,内容拷贝自ArcEngine SDK开发文档 Geoprocessing assembly overview In this topic About the Geoproce ...

  5. 活体检测 Domain Generalization via Shuffled Style Assembly for Face Anti-Spoofing 论文学习记录

    论文链接:https://openaccess.thecvf.com/content/CVPR2022/papers/Wang_Domain_Generalization_via_Shuffled_S ...

  6. web.config学习之assembly

    .net framewok2.0 提供了表示Framework的各个部件的大量程序集.这些程序集存储在全局程序集缓存中,该缓存是程序集的版本化存储库,可供计算机上的所有应用程序使用(而不像Bin和Ap ...

  7. C# 特性(Attribute)学习。

    特性(attribute)是被指定给某一声明的一则附加的声明性信息. 在C#中,有一个小的预定义特性集合.在学习如何建立我们自己的定制特性(custom attributes)之前,我们先来看看在我们 ...

  8. CQRS学习——最小单元的Cqrs(CommandEvent)[其一]

    [说明:博主采用边写边思考的方式完成这一系列的博客,所以代码以附件为准,文中代码仅为了说明.] 结构 在学习和实现CQRS的过程中,首要参考的项目是这个[http://www.cnblogs.com/ ...

  9. SCARA——OpenGL入门学习一、二

    参考博客:http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html 简介 最近开始一个机械手臂的安装调试,平面关节型机器人又称SCA ...

最新文章

  1. 9种没结果的爱(未婚者必读)!!!
  2. 转载-对js中new、prototype的理解
  3. Linux线程同步之条件变量
  4. mybatis必知必会一
  5. 需求决定设计,设计来源于需求
  6. [机器学习]一个例子完美解释朴素贝叶斯分类器
  7. Mongo数据库搭建
  8. php 数组元素往后移动,php 二维数组 元素移动
  9. C#通过COM组件操作IE浏览器(三):了解IHTMLDocument2
  10. 过期域名如何助力犯罪分子攻破企业防御
  11. pyecharts折线图坐标轴范围设置_多层折线图,一对一展现数据趋势,图表就应如此美丽...
  12. .net学科-杨中科-Unity3D视频教程
  13. canvas系列 — canvas常用API介绍
  14. WinCC V7.4 过程值归档概述及流程演示
  15. java 抛体运动6_抛体运动的公式
  16. web手机端真机测试
  17. java解压/读取rar文件
  18. 一元域名真假?一元域名注册有哪些风险?
  19. 反思:项目开发中的语言沟通与文档沟通
  20. 网络营销:从战略到战术

热门文章

  1. typora 导出 docx(Pandoc下载)
  2. Android Studio 中Couldn't resolve resource
  3. 计算机毕业设计 SSM+Vue车位租赁系统 停车场管理系统 汽车车位租用系统 停车位临时停车管理系统Java Vue MySQL数据库 远程调试 代码讲解
  4. 1.4 JavaScript_BOM
  5. BAW美式期权定价半解析解
  6. Linux中Wine QQ上线 转变成离线问题的解决(什么时候才能自己修改?)(qq2012出了 那个没有这个BUG)
  7. QGIS数据库菜单栏不显示“数据库”如何修复
  8. 编辑器打开查看的图纸文件指定图层如何进行关闭?
  9. R语言 判断矩阵是否对称半正定
  10. C# asp.net .netcore 单层和双层PDF转为图片