2014-03-25 56 views
0

不加載這不是一個重複的 - 我已對相關的StackOverflow問題,沒有運氣:How to Load an Assembly to AppDomain with all references recursively?動態加載大會在新的AppDomain

我有兩個控制檯應用程序。 AssemblyLoaderTest.exe和testapp.exe

  1. 我試圖用AssemblyLoaderTest.exe動態地從一個類中testapp.exe
  2. 加載testapp.exe和調用一個方法到目前爲止代碼工作 - 方法「 Testapp.exe中的「TestWrite()」正確執行(並且寫入outputsuccess.txt),但是,testapp.exe加載到同一AppDomain中,這經驗證是因爲「CallMethodFromDllInNewAppDomain」始終返回false。我試圖在新AppDomain中加載testapp.exe。

我的問題:如何修改下面的代碼,以便testapp.exe加載到新的AppDomain中,結果「CallMethodFromDllInNewAppDomain」返回true?謝謝!

下面的代碼。兩者都可以簡單地複製到VS中的新控制檯應用程序中並執行/編譯。

控制檯應用程序#1:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using System.Security.Policy; 

namespace AssemblyLoaderTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      List<object> parameters = new List<object>(); 
      parameters.Add("Test from console app"); 
      bool loadedInNewAppDomain = DynamicAssemblyLoader.CallMethodFromDllInNewAppDomain(@"c:\temp\testapp.exe", "testapp.TestClass", "TestWrite", parameters); 
     } 
    } 
    public static class DynamicAssemblyLoader 
    { 
     public static string ExeLoc = ""; 
     public static bool CallMethodFromDllInNewAppDomain(string exePath, string fullyQualifiedClassName, string methodName, List<object> parameters) 
     { 
      ExeLoc = exePath; 
      List<Assembly> assembliesLoadedBefore = AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>(); 
      int assemblyCountBefore = assembliesLoadedBefore.Count; 
      AppDomainSetup domaininfo = new AppDomainSetup(); 
      Evidence adevidence = AppDomain.CurrentDomain.Evidence; 
      AppDomain domain = AppDomain.CreateDomain("testDomain", adevidence, domaininfo); 
      AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); 
      domain.CreateInstanceFromAndUnwrap(exePath, fullyQualifiedClassName); 
      List<Assembly> assemblies = domain.GetAssemblies().ToList<Assembly>(); 
      string mainExeName = System.IO.Path.GetFileNameWithoutExtension(exePath); 
      Assembly assembly = assemblies.FirstOrDefault(c => c.FullName.StartsWith(mainExeName)); 
      Type type2 = assembly.GetType(fullyQualifiedClassName); 
      List<Type> parameterTypes = new List<Type>(); 
      foreach (var parameter in parameters) 
      { 
       parameterTypes.Add(parameter.GetType()); 
      } 
      var methodInfo = type2.GetMethod(methodName, parameterTypes.ToArray()); 
      var testClass = Activator.CreateInstance(type2); 
      object returnValue = methodInfo.Invoke(testClass, parameters.ToArray()); 
      List<Assembly> assembliesLoadedAfter = AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>(); 
      int assemblyCountAfter = assembliesLoadedAfter.Count; 
      if (assemblyCountAfter > assemblyCountBefore) 
      { 
       // Code always comes here 
       return false; 
      } 
      else 
      { 
       // This would prove the assembly was loaded in a NEW domain. Never gets here. 
       return true; 
      } 
     } 
     public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 
     { 
      // This is required I've found 
      return System.Reflection.Assembly.LoadFrom(ExeLoc); 
     } 
    } 
} 

控制檯應用程序#2:

using System; 
namespace testapp 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine("Hello from console"); 
     } 
    } 
    [Serializable] 
    public class TestClass : MarshalByRefObject 
    { 
     public void TestWrite(string message) 
     { 
      System.IO.File.WriteAllText(@"outputsuccess.txt", message); 
     } 
    } 

} 

回答

3

使用這個類。以下是一些注意事項:

  1. 此類明確設置進程的當前目錄和獨立應用程序域的應用程序基本路徑。這不是完全必要的,但它會讓你的生活變得更加輕鬆。

    1. 如果不設置該應用基本路徑到包含次級組件的目錄,則運行時將嘗試解決次級組件的任何依賴針對作爲主組件中,相同的應用程序基本路徑,其可能沒有輔助程序集的依賴關係。您可以使用AssemblyResolve事件來正確手動解決依賴性問題,但設置應用程序基本路徑是一種更簡單,更容易出錯的方法。

    2. 如果您未設置Environment.CurrentDirectory,那麼諸如File.WriteAllText("myfile.txt", "blah")之類的文件操作將解析針對當前目錄的路徑,這可能不是次要程序集的作者所期望的。 (旁白:始終手動解決路徑這個原因。)

  2. 相信喜歡GetMethod簡單反映操作不會如CreateInstanceFromAndUnwrap返回一個MarshalByRefObject的代理工作。所以你需要做更多的調用。

    1. 如果主要和次級組件的所有者,可以創建用於調用接口 - 把接口在共享組件,在限定的界面處的跨域呼叫,實現接口,在目標類型上執行domain.CreateInstanceFromAndUnwrap並將結果轉換爲接口,然後可以使用該接口跨域邊界調用。

    2. 下面的解決方案提供了一種侵入性較小的替代方法 - 您不必擁有此技術的輔助裝配。這個想法是,主域在二級域中創建一個衆所周知的中介對象(InvokerHelper),該中介從二級域內執行必要的反射

下面是一個完整的實現:

// Provides a means of invoking an assembly in an isolated appdomain 
public static class IsolatedInvoker 
{ 
    // main Invoke method 
    public static void Invoke(string assemblyFile, string typeName, string methodName, object[] parameters) 
    { 
     // resolve path 
     assemblyFile = Path.Combine(Environment.CurrentDirectory, assemblyFile); 
     Debug.Assert(assemblyFile != null); 

     // get base path 
     var appBasePath = Path.GetDirectoryName(assemblyFile); 
     Debug.Assert(appBasePath != null); 

     // change current directory 
     var oldDirectory = Environment.CurrentDirectory; 
     Environment.CurrentDirectory = appBasePath; 
     try 
     { 
      // create new app domain 
      var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, appBasePath, null, false); 
      try 
      { 
       // create instance 
       var invoker = (InvokerHelper) domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InvokerHelper).FullName); 

       // invoke method 
       var result = invoker.InvokeHelper(assemblyFile, typeName, methodName, parameters); 

       // process result 
       Debug.WriteLine(result); 
      } 
      finally 
      { 
       // unload app domain 
       AppDomain.Unload(domain); 
      } 
     } 
     finally 
     { 
      // revert current directory 
      Environment.CurrentDirectory = oldDirectory; 
     } 
    } 

    // This helper class is instantiated in an isolated app domain 
    private class InvokerHelper : MarshalByRefObject 
    { 
     // This helper function is executed in an isolated app domain 
     public object InvokeHelper(string assemblyFile, string typeName, string methodName, object[] parameters) 
     { 
      // create an instance of the target object 
      var handle = Activator.CreateInstanceFrom(assemblyFile, typeName); 

      // get the instance of the target object 
      var instance = handle.Unwrap(); 

      // get the type of the target object 
      var type = instance.GetType(); 

      // invoke the method 
      var result = type.InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, instance, parameters); 

      // success 
      return result; 
     } 
    } 
} 
+0

邁克爾,感謝您的評論。 (2)將不可能用於我的用例(因爲這不允許我通過名稱調用類/方法)。 (3) - 我添加了domaininfo.ApplicationBase = System.IO.Path.GetDirectoryName(exePath);,但實際上它根本沒有做任何事情(outputsuccess.txt仍然寫入父AppDomain的當前目錄)。 – BlueSky

+0

如果您可以編輯我現有的代碼並使其工作,將不勝感激。關於這個主題的理論有很多討論,但還沒有找到一個有效的代碼示例。謝謝! – BlueSky

+1

我剛剛完全清除並重新創建了我的帖子。這應該回答你的問題。 –

相關問題