2014-03-25 5 views
0

에로드되지 - 나는 운없이이 관련 StackOverflow의 질문을 검토 : How to Load an Assembly to AppDomain with all references recursively?동적으로로드 조립이 중복되지 않습니다 새 응용 프로그램 도메인

나는 두 개의 콘솔 응용 프로그램을 가지고있다. AssemblyLoaderTest.exe 및 testapp.exe

  1. 내가 동적으로 testapp.exe
  2. 내에서 클래스에서 방법을 testapp.exe을로드하고 전화를 AssemblyLoaderTest.exe를 사용하려고하는 것은 지금까지의 코드가 작동 - 방법 " testapp.exe의 "TestWrite()"가 올바르게 실행되고 outputsuccess.txt가 쓰여집니다. , testapp.exe는 동일한 AppDomain에로드됩니다. "CallMethodFromDllInNewAppDomain"은 항상 false를 반환하기 때문에 검증되었습니다. 새로운 AppDomain에 testapp.exe를로드하려고합니다.

내 질문 : 아래 예제 코드를 수정하여 새 AppDomain에 testapp.exe를로드하면 결과적으로 "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")과 같은 파일 작업은 현재 보조 디렉토리의 작성자가 의도 한 경로가 아닌 현재 디렉토리에 대한 경로를 확인합니다. (제외 : 항상 이런 이유로 수동으로 경로를 확인.) 나는 GetMethod 같은 간단한 반사 작업은 CreateInstanceFromAndUnwrap에 의해 반환로 MarshalByRefObject를 프록시에서 작동하지 않습니다 생각

  2. . 따라서 호출하려면 좀 더해야합니다.당신은 기본 및 보조 어셈블리의 소유자 인 경우

    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

나는 방금 전멸했고, 나의 지위를 완전히 재창조했다. 귀하의 질문에 대한 대답입니다. –