1. 程式人生 > >unity3d動態加載dll的API以及限制

unity3d動態加載dll的API以及限制

軟件 ddc line tel 同時 eat 都是 version tails

Unity3D的坑系列:動態加載dll

一、使用限制

現在參與的項目是做MMO手遊,目標平臺是Android和iOS,iOS平臺不能動態加載dll(什麽原因找喬布斯去),可以直接忽略,而在Android平臺是可以動態加載dll的,有了這個就可以實現代碼更新,不過實際上,在unity裏要用上動態加載dll是有很多限制的(不了解的話就是坑)。

限制1:在Android手機裏動態加載dll不能使用Assembly.LoadFile(string path),只能使用Assembly.Load(byte[] rawAssembly)這個接口,所以要自己想辦法先讀出來。

限制2:動態加載的腳本不能在編輯器裏掛在prefab上。

限制3:如果腳本在動態dll裏,調用AddComponent()掛此腳本上prefab上時不能使用AddComponent(“SomeScript”)的方式調用,要用AddComponent(Type.GetType(“SomeScript”))。

限制4:在動態dll裏使用[RequireComponent(typeof(SomeScript))]無效,所以不能使用。

我目前主要就是遇到這些坑,以後有什麽新發現再進行補充。WINDOWS下LoadFile()測試可行。

修正&補充:在iOS下不是都不能動態加載dll,越獄的手機可以實現,具體可參考文章:http://blog.csdn.net/lucky_06/article/details/9093879,親測沒問題。

再次修正:iOS實際上還是不能實現真正的代碼熱更新,使用上述方法只能調用外部dll的接口,但執行邏輯還是在native code裏面。簡單的說,之前我測試的時候是項目裏面有a.dll的代碼,然後再Assembly.Load a.dll進來,調用a.dll裏面的方法是可以的,但實際是調用了項目裏的代碼,如果項目裏沒有a.dll的代碼,調用會報錯,以上。

二、C#反射知識及API

一些關於C#反射的知識,估計也就最多達到使用API的程度,至於要深入了解,以現在的水平估計很難做到,所以下面此篇文章,以作為一個階段的總結。

對於反射的總結,我想從以下幾個方面展開,首先是反射程序集,模塊,類的成員以及成員的一些信息;接下來就是動態調用類的成員方法;第三個方面就動態產生程序集,模塊和類以及類的成員。好了,現在就讓我們從反射各種信息開始吧

在C#中,我們要使用反射,首先要搞清楚以下命名空間中幾個類的關系:

System.Reflection命名空間

(1) AppDomain:應用程序域,可以將其理解為一組程序集的邏輯容器

(2) Assembly:程序集類

(3) Module:模塊類

(4) Type:使用反射得到類型信息的最核心的類

他們之間是一種從屬關系,也就是說,一個AppDomain可以包含N個Assembly,一個Assembly可以包含N個Module,而一個Module可以包含N個Type.

AppDomain這個類我們等下再來講解。我們先關註Assembly個類

在程序中,如果我們要動態加載一個程序集怎麽辦呢?有幾種方式可以使用,分別是Load、LoadFrom和LoadWithPartialName三個Assembly的靜態方法.

先來講解Assembly.Load方法,該方法會有多個重載版本,其中一個就是提供程序集的詳細信息,即程序集的標識,包括程序集的名稱,版本,區域信息,公有密鑰標記,全部都是以一個字符串的形式提供,例如:"MyAssembly,Version=1.0.0.0,culture=zh-CN,PublicKeyToken=47887f89771bc57f”.

那麽,使用Assembly.Load加載程序集的順序是怎樣的呢?首先它會去全局程序集緩存查找,然後到應用程序的根目錄查找,最後會到應用程序的私有路徑查找。

當然,如果你使用的是弱命名程序集,也即只給出程序集的名稱,那麽這個時候,CLR將不會在程序集上應用任何安全或者部署策略,而且Load也不會到全局緩存程序集中查找程序集。

測試加載弱命名程序集的例子如下:

(1) 新建一個控制臺應用程序的工程,同時勾選創建解決方案

(2) 在解決方案中新建一個類庫的項目,隨便寫一個類和一個方法

(3) 在控制臺項目中,首先不添加引用,直接在Main方法中添加如下代碼:

Assembly assembly = Assembly.Load("MyAssembly");

if (assembly != null)

{ Console.WriteLine("加載成功"); }

執行程序,會拋出異常,說找不到該程序集。什麽原因呢?因為我們使用的是弱命名程序集,Load方法不會去全局程序集緩存中查找,而該應用程序目錄下又沒有該程序集,所以程序找不到。這個時候,我們把程序稍微改一下,不用添加代碼,只需添加對MyAssembly的引用,重新運行程序,加載成功了。

接下來,我們就要看看Load怎麽加載強命名程序集了,這個步驟稍微有些復雜。還是剛才的項目,找到MyAssembly.dll程序集所在的目錄,一般在bin"Debug目錄下

(1)生成密鑰對文件 sn –k MyAssemblyKey.keys

你也可以自己隨便起一個密鑰對文件名

(2)生成公鑰文件

sn –p MyAssemblyKey.keys MyAssemblyPublicKey.PublicKey

註:查看公鑰命令:sn –tp MyAssemblyPublicKey.PublicKey

(3)創建強命名程序集。

很簡單,只需要在聲明命名空間的那句代碼上加上如下特性:

[assembly:AssemblyKeyFileAttribute(@”D:"Test"MyAssemblyKey.keys”)]

(4) 編譯項目

(5) 將程序集添加到程序集全局緩存

gacutil –i MyAssembly.dll

這個時候,轉到加載程序集的項目中,將Load方法中的參數改為”程序集名,Version=版本,culture=區域信息,PublicKeyToken=公鑰“,然後再去掉對程序集的引用,我們會發現,程序運行成功。表明Load到全局緩存區查找到了該程序集。

使用Load方法加載程序集,特別是強命名程序集,能在程序集上應用安全和部署策略,推薦使用該方法動態加載程序集,至於LoadFrom和LoadWithPartialName。

首先我們還是來看看LoadFrom方法,這個方法的原理是這樣的:我們如果要使用它來動態加載程序集,必須告訴它程序集的路徑,也即在哪個目錄下面,CLR會去加載與你指定的路徑完全匹配的程序集。記住,當我們指定程序集路徑時,不能包括任何關於程序集強命名的信息,所以,CLR不會在我們指定的程序集文件上應用任何策略,而且也不會去任何其他的地方搜索程序集,簡言之,它就是指哪打哪,呵呵。

例如:你有個程序集在D:/Test/MyAssembly.dll,你要用Assembly.LoadFrom加載該程序集,代碼就如下:

Assembly assembly = Assembly.LoadFrom(@”D:/Test/MyAssembly.dll”);

對於,LoadWithParitalName方法,推薦大家最好不要使用它,因為程序無法確定最終要去加載哪個程序集的版本,所以我們這裏只是簡單的介紹一下它的工作原理:你可以傳遞一個程序集標識給它,包括程序集名稱,至於其他信息是可選的(區域信息,公有密鑰等),該方法執行時,會首先檢查應用程序中配置文件的qualifyAssembly節點,如果存在,則把該部分名稱的程序集替換成完全的程序集標識,如果不存在,則使用程序集名稱先到應用程序根目錄下查找,然後是私有目錄,沒有找到的話,就到程序集全局緩存中查找。簡單過程如下:

應用程序根目錄 -> 應用程序私有目錄 -> 程序集全局緩存.

Assembly.Load()方法,Assembly.LoadFrom()方法,Assembly.LoadFile()方法的區別!

1,Assembly.Load()

這個方法通過程序集的長名稱(包括程序集名,版本信息,語言文化,公鑰標記)來加載程序集的,會加載此程序集引用的其他程序集,一般情況下都應該優先使用 這個方法,他的執行效率比LoadFrom要高很多,而且不會造成重復加載的問題(原因在第2點上說明)

使用這個方法的時候, CLR會應用一定的策略來查找程序集,實際上CLR按如下的順序來定位程序集:

⑴如果程序集有強名稱,在首先在全局程序集緩(GAC)中查找程序集。

⑵如果程序集的強名稱沒有正確指定或GAC中找不到,那麽通過配置文件中的<codebase>元素指定的URL來查找

⑶如果沒有指定強名稱或是在GAC中找不到,CLR會探測特定的文件夾:

假設你的應用程序目錄是C:/AppDir,<probing>元素中的privatePath指定了一個路徑Path1,你要定位的程序集是AssemblyName.dll則CLR將按照如下順序定位程序集

C:/AppDir/AssemblyName.dll

C:/AppDir/AssemblyName/AssemblyName.dll

C:/AppDir/Path1/AssemblyName.dll

C:/AppDir/Path1/AssemblyName/AssemblyName.dll

如果以上方法不能找到程序集,會發生編譯錯誤,如果是動態加載程序集,會在運行時拋出異常!

2,Assembly.LoadFrom()

這個方法從指定的路徑來加載程序集,實際上這個方法被調用的時候,CLR會打開這個文件,獲取其中的程序集版本,語言文化,公鑰標記等信息,把他們傳遞給 Load方法,接著,Load方法采用上面的策略來查找程序集。如果找到了程序集,會和LoadFrom方法中指定的路徑做比較,如果路徑相同,該程序集 會被認為是應用程序的一部分,如果路徑不同或Load方法沒有找到程序集,那該程序集只是被作為一個“數據文件”來加載,不會被認為是應用程序的一部分。 這就是在第1點中提到的Load方法比LoadFrom方法的執行效率高的原因。另外,由於可能把程序集作為“數據文件”來加載,所以使用 LoadFrom從不同路徑加載相同程序集的時候會導致重復加載。當然這個方法會加載此程序集引用的其他程序集。

3,Assembly.LoadFile()

這個方法是從指定的文件來加載程序集,和上面方法的不同之處是這個方法不會加載此程序集引用的其他程序集!

結論:一般大家應該優先選擇Load方法來加載程序集,如果遇到需要使用LoadFrom方法的時候,最好改變設計而用Load方法來代替!

另:Assembly.LoadFile 與 Assembly.LoadFrom的區別

1、Assembly.LoadFile只載入相應的dll文件,比如Assembly.LoadFile("abc.dll"),則載入abc.dll,假如abc.dll中引用了def.dll的話,def.dll並不會被載入。

Assembly.LoadFrom則不一樣,它會載入dll文件及其引用的其他dll,比如上面的例子,def.dll也會被載入。

2、用Assembly.LoadFrom載入一個Assembly時,會先檢查前面是否已經載入過相同名字的Assembly,比如abc.dll有兩個版本(版本1在目錄1下,版本2放在目錄2下),程序一開始時載入了版本1,當使用Assembly.LoadFrom("2//abc.dll")載入版本2時,不能載入,而是返回版本1。Assembly.LoadFile的話則不會做這樣的檢查,比如上面的例子換成Assembly.LoadFile的話,則能正確載入版本2。

LoadFile:加載指定路徑上的程序集文件的內容。LoadFrom: 根據程序集的文件名加載程序集文件的內容。

區別:

LoadFile 方法用來來加載和檢查具有相同標識但位於不同路徑中的程序集.但不會加載程序的依賴項。

LoadFrom 不能用於加載標識相同但路徑不同的程序集。

unity3d動態加載dll的API以及限制