U3D开发:程序集

程序集

        程序集是.net中的概念,程序集可以看作是给一堆相关类打一个包,相当于java中的jar包。在.Net 中,程序集(Assembly)中保存了元数据(MetaData)信息,因此就可以通过分析元数据来获取程序集中的内容,比如类,方法,属性等,这大大方便了在运行时去动态创建实例。

        程序集是代码进行编译是的一个逻辑单元,把相关的代码和类型进行组合,然后生成PE文件(例如可执行文件**.exe和类库文件.dll**)。

        对于静态程序集可以生成单个或多个文件,而动态程序集是存在于内存中的。在C#中程序集处处可见,因为任何基于.NET的代码在编译时都至少存在一个程序集。
        基于.NET框架的.dll库是一个完整的程序集,需要事先引用对应的类库。从代码的结构上看,一个程序集可以包含一个或多个命名空间,而每个命名空间中又可以包含子命名空间或类型列表。由于程序集在编译后可以生成多个模块文件,因此一个物理文件并不代表它就是一个程序集,一个程序集并不一定只有一个文件。在VS开发环境中,一个解决方案可以包含多个项目,而每个项目就是一个程序集。

应用程序结构:包含 应用程序域(AppDomain),程序集(Assembly),模块(Module),类型(Type),成员(EventInfo、FieldInfo、MethodInfo、PropertyInfo) 几个层次。

程序集包含:

  • 资源文件
  • 类型元数据(描述在代码中定义的每一类型和成员,二进制形式)
  • IL代码(这些都被封装在exe或dll中)

  

         使用.NET建立的可执行程序 *.exe,并没有直接承载到进程当中,而是承载到应用程序域(AppDomain)当中。在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(*.exe)或者多个程序集(*.dll),这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。处理asp.net所涉及的类大多数定义在System.Web程序集中。

  1. 当exe程序集加载完毕,.Net会在当前进程中创建一个默认应用程序域,这个应用程序域的名称与程序集名称相同。默认应用程序域不能被卸载,并且与其所在的进程同生共灭。
  2. 应用程序域只是允许它所加载的程序集访问由.Net Runtime所提供的服务。这些服务包括托管堆(Managed Heap),垃圾回收器(Garbage collector),JIT 编译器等.Net底层机制.
  3. 一个应用程序域中如果出现了致命错误导致崩溃,只会影响其本身,而不会影响到其他应用程序域。实现了错误隔离。

        应用程序域是进程中承载程序集的逻辑分区,在应用程序域中存在更细粒度的用于承载.NET对象的实体,即.NET上下文Context。

        所有的.NET对象都存在于.NET上下文当中,每个AppDomain当中至少存在一个默认上下文(context0)。

 exe与dll的区别

        exe可以运行,dll不能直接运行,因为exe中有一个main函数(入口函数)。

        Untiy中在Assets文件夹中编写脚本,如果没有进行自定义操作,会默认编译到 Assembly-CSharp.dll 中,如果不分多个程序集,就会发现只要改了一行代码就会编译好久的现象。

        开发过程中如果进行了编辑器扩展,创建了Editor文件夹,并在该目录下编写了脚本,则默认编译到Assembly-CSharp-Editor.dll文件中。

        DLL文件的生成路径:项目路径\Library\ScriptAssemblies\xxxx.dll

使用程序集的好处

        
  1. 开发者可以自定义程序集,定义明晰的依赖关系,可以确保脚本更改后,只会重新生成必需的程序集,减少编译时间
  2. 可以跨项目进行程序的复用,加快开发效率
  3. 支持跨语言的编程,例如可以在unity中使用C++语言编辑的DLL文件

 如何在Unity中使用程序集:请看Unity关于程序集(Assembly )的那些事_unity assembly-CSDN博客

加载

 要从程序集的元数据中获取程序集的信息,首先需要加载程序集。

手动加载

System.Reflection.Assembly 

        Load()方法接收一个String或AssemblyName类型作为参数,这个参数实际上是需要加载的程序集的强名称(名称,版本,语言,公钥标记)。

        LoadFrom()方法可以从指定文件中加载程序集,通过查找程序集的AssemblyRef元数据表,得知所有引用和需要的程序集,然后在内部调用Load()方法进行加载。例如:Assembly.LoadFrom(@"C:\ABC\Test.dll");

        LoadFrom()首先会打开程序集文件,通过GetAssemblyName方法得到程序集名称,然后关闭文件,最后将得到的AssemblyName对象传入Load()方法中.

        随后,Load()方法会再次打开这个文件进行加载。所以,LoadFrom()加载一个程序集时,会多次打开文件,造成了效率低下的现象(与Load相比)

        LoadFrom()会直接返回Load()的结果——一个Assembly对象的引用

        如果目标程序集已经加载过,LoadFrom()不会重新进行加载

        LoadFrom支持从一个URL加载程序集(如"www.abc.com/test.dll"),…

public static Assembly Load(string assemblyString)
public static Assembly LoadFrom(string assemblyFile)

class Program
{
    static void Main(string[] args)
    {
        Assembly assembly = Assembly.Load("System.Data,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089");
        foreach (Type type in assembly.GetExportedTypes())
        {
        Console.WriteLine(type.FullName);
        }
        Console.ReadLine();

        Assembly loadedAssembly2 = Assembly.LoadFrom("path_to_your_assembly.dll"); // 需要替换为你的程序集文件路径  
        Type loadedType2 = loadedAssembly2.GetType("Namespace.TypeName"); // 需要替换为你的类型名称  
        object instance = Activator.CreateInstance(loadedType2);  
        method = loadedType2.GetMethod("MethodName"); // 需要替换为你的方法名称  
        method.Invoke(instance, null); // 执行方法  
    }
}

        使用Load方法加载程序集必须传递程序集的全名称,程序集的全名称由四个部分组成(程序集的简单名称、程序集的版本、程序集的语言文化,程序集的公钥标记)。程序集的实例属性FullName就是程序集的全名称。

        LoadFrom方法允许从本地和远程网络加载程序集,在通过远程网络加载的是时候需要注意,不能通过FTP协议远程加载程序集。

System.AppDomain

        在.Net 中,将应用程序域封装为了AppDomain类,这个类提供了应用程序域的各种操作,包含 加载程序集、创建对象、创建应用程序域 等。通常的编程情况下下,我们几乎从不需要对AppDomain进行操作。

        除了使用Assembly类来加载程序集之外,还可以利用System.AppDomain类的实例方法public Assembly Load(string assemblyString)来完成。该实例方法有许多重载版本,这里需要注意,由于Assembly不是派生自MarshalByRefObject类,所以使用Load方法得到的程序集是按值封送。

public Assembly Load(string assemblyString);
// 获取当前运行的代码所在的应用程序域
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomain currentDomain = Thread.GetDomain();

// 获取应用程序域的名称
string name = AppDomain.CurrentDomain.FriendlyName;

// 从当前应用程序域中创建新应用程序域
AppDomain newDomain = AppDomain.CreateDomain("New Domain");

// 配置应用程序域(在创建前)
AppDomainSetup domainInfo = new AppDomainSetup();
domainInfo.ApplicationBase = "e:\\AttributeTest";
domainInfo.ConfigurationFile = "e:\\AttributeTest\\1.exe.config";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, domainInfo);

// 判断是否为默认应用程序域
newDomain.IsDefaultAppDomain()

// 卸载应用程序集
AppDomain.Unload(newDomain);

// 在新的应用程序域中执行程序集, 加载可执行程序
AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
newAppDomain.ExecuteAssembly("Example.exe");//或使用ExecuteAssemblyByName
AppDomain.Unload(newAppDomain);

/**
如果代表当前AppDomain的域为A,Load方法调用在AppDomain域B中,那么被Load加载的程序集会被域A和域B都加载
这段代码中,MyAssembly程序集将会被加载到两个AppDomain中,
一个是运行代码所在的AppDomain,另一个是新创建的AppDomain。
这是因为Assembly是按值封送的,所以CLR将会在调用线程的AppDomain中重新加载程序集。
*/
AppDomain ad = AppDomain.CreateDomain("ChildDomain");
ad.Load("MyAssembly");

 两种方式的性能区别

        System.AppDomain的Load方法得到的程序集是按值封送的,缺点是程序集将会被加载到两个AppDomain中。

        原因是:加载域不一样,一个是运行代码所在的AppDomain; 一个是新创建的AppDomain。所以CLR将会在调用线程的AppDomain中重新加载程序集。

自动加载

 BinaryFormart + FileStream 配合

第一步,加载assetBundle资源,assetBundle名称为Test:

//将本地文件转为字节
FileStream fs = new FileStream(Application.dataPath + "/Test", FileMode.Open, FileAccess.Read);
 
byte[] buffur = new byte[fs.Length];
 
fs.Read(buffur, 0, (int)fs.Length);
 
fs.Close();

第二步,加载程序集及获取指定类型

public GameObject _gameObjectRoot;
    
public TextAsset _script;
    
public GameObject _gameobject;
 
StartCoroutine(BeginGetAsset(buffur));
 
IEnumerator BeginGetAsset(byte[] bytes)
    {
        //从内存中获取资源
        AssetBundleCreateRequest acr = AssetBundle.CreateFromMemory(bytes);
        yield return acr;
        _assetBundle = acr.assetBundle;
        //加载资源中的预设
        _gameObjectRoot = (GameObject)_assetBundle.LoadAsset("GameObjectRoot.prefab");
        //将预设创建在场景内
        yield return _gameobject = Instantiate(_gameObjectRoot);
        //获取到assetBundle中的test.DLL
        _script = (TextAsset)_assetBundle.LoadAsset("test", typeof(TextAsset));
        //C#反射取出程序集
        System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(_script.bytes);
        //加载程序集中的脚本Mytest.cs
        _gameobject.AddComponent(assembly.GetType("Mytest"));
    }

 反射

优点:

允许在运行时发现并使用编译时还不了解的类型及其成员。

缺点:

一、编译时无法保证类型安全。举个例子: Type,GetType( int)编译时不会报错,运行时会报错。因为CLR只知道“System.int32”。

二、速度慢

1. 依据目标类型的字符串搜索扫描程序集的元数据的过程耗时。

2. 反射调用方法或属性比较耗时。原因在于首先必须将实参打包(pack)成数组: 在内部,反射必须将这些实参解包(unpack)到线程栈上。优化:使用多态避免反射操作。

文章参考:

 Unity关于程序集(Assembly )的那些事_unity assembly-CSDN博客

 程序集和反射(C#) - 邹琼俊 - 博客园 (cnblogs.com)

应用程序域 System.AppDomain,动态加载程序集 - springsnow - 博客园 (cnblogs.com)

Unity热更新之C#反射加载程序集_unity 加载程序集-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值