developer tip

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

optionbox 2020. 8. 10. 07:55
반응형

모든 참조가있는 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);

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


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

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);
        }
    }
}

If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.

For example, the app domain creation line from the above code should be replaced with:

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

This way, all the dlls will automaically be resolved from dllsSearchPath.


You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve


It took me a while to understand @user1996230's answer so I decided to provide a more explicit example. In the below example I make a proxy for an object loaded in another AppDomain and call a method on that object from another domain.

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"});
}

The Key is the AssemblyResolve event raised by the AppDomain.

[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"}));
}

I have had to do this several times and have researched many different solutions.

The solution I find in most elegant and easy to accomplish can be implemented as such.

1. Create a project that you can create a simple interface

the interface will contain signatures of any members you wish to call.

public interface IExampleProxy
{
    string HelloWorld( string name );
}

Its important to keep this project clean and lite. It is a project that both AppDomain's can reference and will allow us to not reference the Assembly we wish to load in seprate domain from our client assembly.

2. Now create project that has the code you want to load in seperate AppDomain.

This project as with the client proj will reference the proxy proj and you will implement the interface.

public interface Example : MarshalByRefObject, IExampleProxy
{
    public string HelloWorld( string name )
    {
        return $"Hello '{ name }'";
    }
}

3. Next, in the client project, load code in another AppDomain.

So, now we create a new AppDomain. Can specify the base location for assembly references. Probing will check for dependent assemblies in GAC and in current directory and the AppDomain base loc.

// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
    ApplicationBase = System.Environment.CurrentDirectory
};

Evidence adevidence = AppDomain.CurrentDomain.Evidence;

AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);

// assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";

// Optional - get a reflection only assembly type reference
var @type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName ); 

// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );

// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( @type.Assembly.Name, @type.Name );    

// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );

// unload the `AppDomain`
AppDomain.Unload( exampleDomain );

if you need to, there are a ton of different ways to load an assembly. You can use a different way with this solution. If you have the assembly qualified name then I like to use the CreateInstanceAndUnwrap since it loads the assembly bytes and then instantiates your type for you and returns an object that you can simple cast to your proxy type or if you not that into strongly-typed code you could use the dynamic language runtime and assign the returned object to a dynamic typed variable then just call members on that directly.

There you have it.

This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomain and call members on it from client.

To test, I like to use the Modules window in Visual Studio. It will show you your client assembly domain and what all modules are loaded in that domain as well your new app domain and what assemblies or modules are loaded in that domain.

The key is to either make sure you code either derives MarshalByRefObject or is serializable.

`MarshalByRefObject will allow you to configure the lifetime of the domain its in. Example, say you want the domain to destroy if the proxy hasnt been called in 20 minutes.

I hope this helps.

참고URL : https://stackoverflow.com/questions/658498/how-to-load-an-assembly-to-appdomain-with-all-references-recursively

반응형