[c#] 모든 참조가있는 AppDomain에 어셈블리를 재귀 적으로로드하는 방법은 무엇입니까?

AppDomain복잡한 참조 트리 (MyDll.dll-> Microsoft.Office.Interop.Excel.dll-> Microsoft.Vbe.Interop.dll-> Office.dll-> stdole.dll) 가있는 새 어셈블리 에로드하고 싶습니다.

내가 아는 한, 어셈블리가에로드 될 때 AppDomain해당 참조가 자동으로로드되지 않으며 수동으로로드해야합니다. 그래서 내가 할 때 :

string dir = @"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");

AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);

domain.Load(AssemblyName.GetAssemblyName(path));

그리고 FileNotFoundException:

파일 또는 어셈블리 ‘MyDll, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null’또는 해당 종속성 중 하나를로드 할 수 없습니다. 시스템이 지정된 파일을 찾을 수 없습니다.

핵심 부분은 의존성 중 하나 라고 생각합니다 .

좋아, 전에 다음을 해 domain.Load(AssemblyName.GetAssemblyName(path));

foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
    domain.Load(refAsmName);
}

그러나 FileNotFoundException다른 (참조 된) 어셈블리에서 다시 얻었 습니다.

모든 참조를 재귀 적으로로드하는 방법은 무엇입니까?

루트 어셈블리를로드하기 전에 참조 트리를 만들어야합니까? 어셈블리를로드하지 않고 참조를 얻는 방법은 무엇입니까?



답변

CreateInstanceAndUnwrap프록시 개체가 외부 응용 프로그램 도메인에서 실행되기 전에 호출해야 합니다.

 class Program
{
    static void Main(string[] args)
    {
        AppDomainSetup domaininfo = new AppDomainSetup();
        domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
        Evidence adevidence = AppDomain.CurrentDomain.Evidence;
        AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);

        Type type = typeof(Proxy);
        var value = (Proxy)domain.CreateInstanceAndUnwrap(
            type.Assembly.FullName,
            type.FullName);

        var assembly = value.GetAssembly(args[0]);
        // AppDomain.Unload(domain);
    }
}

public class Proxy : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFile(assemblyPath);
        }
        catch (Exception)
        {
            return null;
            // throw new InvalidOperationException(ex);
        }
    }
}

또한 사용하는 경우 Assembly resolver가 GAC 또는 현재 응용 프로그램의 bin 폴더에서로드중인 어셈블리를 찾으려고 시도하므로 예외가 LoadFrom발생할 가능성이 FileNotFound있습니다. LoadFile대신 임의의 어셈블리 파일을로드하는 데 사용 합니다.하지만 이렇게하면 종속성을 직접로드해야합니다.


답변

http://support.microsoft.com/kb/837908/en-us

C # 버전 :

중재자 클래스를 만들고 다음에서 상속합니다 MarshalByRefObject.

class ProxyDomain : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFrom(assemblyPath);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(ex.Message);
        }
    }
}

클라이언트 사이트에서 호출

ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);


답변

어셈블리 인스턴스를 호출자 도메인으로 다시 전달하면 호출자 도메인에서로드를 시도합니다! 이것이 예외가 발생하는 이유입니다. 이것은 마지막 코드 줄에서 발생합니다.

domain.Load(AssemblyName.GetAssemblyName(path));

따라서 어셈블리로 수행하려는 작업은 MarshalByRefObject 를 상속하는 클래스 인 프록시 클래스에서 수행해야합니다 .

호출자 도메인과 새로 생성 된 도메인이 모두 프록시 클래스 어셈블리에 액세스 할 수 있어야합니다. 문제가 너무 복잡하지 않은 경우 ApplicationBase 폴더를 변경하지 않고 그대로 두는 것이 좋습니다. 그러면 호출자 도메인 폴더와 동일합니다 (새 도메인은 필요한 어셈블리 만로드합니다).

간단한 코드 :

public void DoStuffInOtherDomain()
{
    const string assemblyPath = @"[AsmPath]";
    var newDomain = AppDomain.CreateDomain("newDomain");
    var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);

    asmLoaderProxy.GetAssembly(assemblyPath);
}

class ProxyDomain : MarshalByRefObject
{
    public void GetAssembly(string AssemblyPath)
    {
        try
        {
            Assembly.LoadFrom(AssemblyPath);
            //If you want to do anything further to that assembly, you need to do it here.
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(ex.Message, ex);
        }
    }
}

현재 앱 도메인 폴더와 다른 폴더에서 어셈블리를로드해야하는 경우 특정 dll 검색 경로 폴더를 사용하여 새 앱 도메인을 만듭니다.

예를 들어, 위 코드의 앱 도메인 생성 줄은 다음과 같이 바꿔야합니다.

var dllsSearchPath = @"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);

이렇게하면 모든 dll이 dllsSearchPath에서 자동으로 확인됩니다.


답변

새 AppDomain에서 AssemblyResolve 이벤트 처리기를 설정해보십시오 . 종속성이 누락되면 해당 이벤트가 호출됩니다.


답변

참조 된 어셈블리가 GAC 또는 CLR의 검색 경로에없는 경우 AppDomain.AssemblyResolve 또는 AppDomain.ReflectionOnlyAssemblyResolve 이벤트 (수행중인로드에 따라 다름)를 처리해야합니다.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve


답변

@ user1996230의 대답을 이해하는 데 시간이 좀 걸렸으므로 더 명확한 예를 제공하기로 결정했습니다. 아래 예제에서는 다른 AppDomain에로드 된 개체에 대한 프록시를 만들고 다른 도메인에서 해당 개체에 대한 메서드를 호출합니다.

class ProxyObject : MarshalByRefObject
{
    private Type _type;
    private Object _object;

    public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
    {
        assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
        _type = assembly.GetType(typeName);
        _object = Activator.CreateInstance(_type, args); ;
    }

    public void InvokeMethod(string methodName, object[] args)
    {
        var methodinfo = _type.GetMethod(methodName);
        methodinfo.Invoke(_object, args);
    }
}

static void Main(string[] args)
{
    AppDomainSetup setup = new AppDomainSetup();
    setup.ApplicationBase = @"SomePathWithDLLs";
    AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
    ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
    proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
    proxyObject.InvokeMethod("foo",new object[] { "bar"});
}


답변

Key는 AppDomain에서 발생한 AssemblyResolve 이벤트입니다.

[STAThread]
static void Main(string[] args)
{
    fileDialog.ShowDialog();
    string fileName = fileDialog.FileName;
    if (string.IsNullOrEmpty(fileName) == false)
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        if (Directory.Exists(@"c:\Provisioning\") == false)
            Directory.CreateDirectory(@"c:\Provisioning\");

        assemblyDirectory = Path.GetDirectoryName(fileName);
        Assembly loadedAssembly = Assembly.LoadFile(fileName);

        List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();

        foreach (var type in assemblyTypes)
        {
            if (type.IsInterface == false)
            {
                StreamWriter jsonFile = File.CreateText(string.Format(@"c:\Provisioning\{0}.json", type.Name));
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
                jsonFile.Close();
            }
        }
    }
}

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    string[] tokens = args.Name.Split(",".ToCharArray());
    System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
    return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}