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


113

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다른 (참조 된) 어셈블리에서 다시 얻었 습니다.

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

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


1
이전에 이와 같은 어셈블리를 여러 번로드했지만 모든 참조를 수동으로로드 할 필요가 없었습니다. 이 질문의 전제가 옳은지 모르겠습니다.
Mick

답변:


68

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대신 임의의 어셈블리 파일을로드하는 데 사용 합니다.하지만 이렇게하면 종속성을 직접로드해야합니다.


20
이 문제를 해결하기 위해 작성한 코드를 확인하십시오 : github.com/jduv/AppDomainToolkit . 특히,이 클래스의 LoadAssemblyWithReferences 방법을 살펴 : github.com/jduv/AppDomainToolkit/blob/master/AppDomainToolkit/...
Jduv

3
나는이 작품 있지만 것으로 나타났습니다 대부분 의 시간을,에 어떤 경우에 당신이 실제로 여전히에 핸들러를 연결해야 AppDomain.CurrentDomain.AssemblyResolve에 설명 된대로 사건 이 MSDN의 대답 . 필자의 경우 MSTest에서 실행되는 SpecRun 배포에 연결하려고했지만 코드가 "기본"AppDomain (VS 확장, MSTest 등)에서 실행되지 않을 수있는 많은 상황에 적용되는 것
같습니다

아 흥미 롭군요. 나는 그것을 조사하고 ADT를 통해 작업하기를 약간 더 쉽게 만들 수 있는지 살펴볼 것입니다. 잠시 동안 코드가 약간 죽었다는 점을 유감스럽게 생각합니다.
Jduv 2014

@Jduv 내가 할 수 있다면 당신의 댓글을 약 100 번 찬성 할 것입니다. 귀하의 라이브러리는 MSBuild에서 동적 어셈블리를로드 할 때 발생하는 해결 불가능한 문제를 해결하는 데 도움이되었습니다. 답변으로 홍보해야합니다!
Philip Daniels

2
@Jduv assembly변수가 "MyDomain"의 어셈블리를 참조하게됩니까? 나는으로 생각하는 var assembly = value.GetAssembly(args[0]);당신을로드 할 args[0]두 도메인에와 assembly변수는 기본 응용 프로그램 도메인에서 사본을 참조합니다
이고르 Bendrup을

14

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

6
이 솔루션은 새 AppDomain을 만드는 컨텍스트에 어떻게 적용됩니까? 누군가 설명 할 수 있습니까?
Tri Q Tran

2
A MarshalByRefObject는 appdomains 주위에 전달 될 수 있습니다. 따라서 Assembly.LoadFrom새로운 appdomain에서 어셈블리를로드하려고 시도하는 것입니다. 호출 개체가 해당 appdomain간에 전달 될 수있는 경우에만 가능합니다. 여기에 설명 된대로 원격이라고도합니다. msdn.microsoft.com/en-us/library/…
Christoph Meißner 2012 년

32
이것은 작동하지 않습니다. 코드를 실행하고 AppDomain.CurrentDomain.GetAssemblies ()를 확인하면로드하려는 대상 어셈블리 가 프록시 도메인이 아닌 현재 애플리케이션 도메인에 로드 된 것을 볼 수 있습니다.
Jduv

41
이건 완전 넌센스입니다. 에서 상속 MarshalByRefObject한다고해서 마술처럼 서로로드되지는 않습니다 AppDomain. .NET 프레임 워크 AppDomain에 서로 참조를 풀 때 직렬화를 사용하는 대신 투명한 원격 프록시를 만들도록 지시합니다 AppDomain(일반적인 방법은 CreateInstanceAndUnwrap메서드). 이 답변에 30 개 이상의 찬성표가 있다는 것을 믿을 수 없습니다. 여기에있는 코드는 Assembly.LoadFrom.
Aaronaught 2014 년

1
예, 완전히 말도 안되는 것처럼 보이지만 28 표가 있으며 답변으로 표시됩니다. 제공된 링크는 MarshalByRefObject를 언급하지도 않습니다. 아주 기괴합니다. 이 실제로 아무것도하지 않으면 어떻게 설명 할 누군가를 사랑 것

12

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

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에서 자동으로 확인됩니다.


프록시 클래스를 사용하여 어셈블리를로드해야하는 이유는 무엇입니까? Assembly.LoadFrom (string)을 사용하여로드하는 것과 다른 점은 무엇입니까? CLR의 관점에서 기술적 인 세부 사항에 관심이 있습니다. 답변 해 주시면 매우 감사하겠습니다.
Dennis Kassel

호출자 도메인에 새 어셈블리가로드되지 않도록하려면 프록시 클래스를 사용합니다. Assembly.LoadFrom (string)을 사용하는 경우 호출자 도메인은 "[AsmPath]"에서 어셈블리를 검색하지 않기 때문에 새 어셈블리 참조를로드하려고 시도하지만 찾을 수 없습니다. ( msdn.microsoft.com/en-us/library/yx7xezcf%28v=vs.110%29.aspx )
Nir

11

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


그렇지 않습니다. 실제로 새 AppDomain에이 이벤트를 등록하는 줄에 예외가 발생합니다. 현재 AppDomain에이 이벤트를 등록해야합니다.
user1004959

클래스가 MarshalByRefObject에서 상속되면 수행됩니다. 클래스가 [Serializable] 속성으로 만 표시된 경우에는 그렇지 않습니다.
user2126375

5

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

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve


그래서 수동으로 요청 된 어셈블리를 표시해야합니까? 새로운 AppDomain의 AppBase에도 있습니까? 그렇게하지 않는 방법이 있습니까?
abatishchev

5

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

코드에 약간의 오타가 있고, 그것이 효과가있을 것이라고 믿지 않았다는 것을 인정해야합니다. 그러나 이것은 저에게 생명의 은인이었습니다. 정말 감사합니다.
Owen Ivory

4

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

0

이 작업을 여러 번해야했고 다양한 솔루션을 연구했습니다.

내가 가장 우아하고 달성하기 쉬운 솔루션은 그렇게 구현할 수 있습니다.

1. 간단한 인터페이스를 만들 수있는 프로젝트 만들기

인터페이스에는 호출하려는 모든 구성원의 서명이 포함됩니다.

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

이 프로젝트를 깨끗하고 가볍게 유지하는 것이 중요합니다. 둘 다 AppDomain참조 할 수 있는 프로젝트이며 Assembly클라이언트 어셈블리에서 별도의 도메인에로드하려는 항목을 참조하지 않도록합니다.

2. 이제로드하려는 코드가있는 프로젝트를 개별적으로 AppDomain만듭니다.

클라이언트 proj와 마찬가지로이 프로젝트는 프록시 proj를 참조하고 인터페이스를 구현합니다.

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

3. 다음으로 클라이언트 프로젝트에서 다른 AppDomain.

이제 우리는 새로운 AppDomain. 어셈블리 참조의 기준 위치를 지정할 수 있습니다. 검색은 GAC와 현재 디렉터리 및 AppDomain기본 위치 에서 종속 어셈블리를 확인합니다 .

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

필요한 경우 어셈블리를로드하는 다양한 방법이 있습니다. 이 솔루션으로 다른 방법을 사용할 수 있습니다. 어셈블리 정규화 된 이름이 있으면 CreateInstanceAndUnwrap어셈블리 바이트를로드 한 다음 유형을 인스턴스화하고 object프록시 유형으로 간단히 캐스트 할 수 있는 을 반환 하거나 강력한 유형의 코드로 변환 할 수없는 경우를 사용하고 싶습니다. 동적 언어 런타임을 사용하고 반환 된 개체를 dynamic형식화 된 변수에 할당 한 다음 해당 변수에 대한 멤버를 직접 호출합니다.

거기에 있습니다.

이를 통해 클라이언트 프로젝트가 별도의 참조가없는 어셈블리를로드 할 수 있습니다. AppDomain 멤버를 호출 할 수 있습니다.

테스트하기 위해 Visual Studio의 모듈 창을 사용하고 싶습니다. 클라이언트 어셈블리 도메인과 해당 도메인에로드 된 모든 모듈과 새 앱 도메인, 해당 도메인에로드 된 어셈블리 또는 모듈이 표시됩니다.

핵심은 코드가 다음 중 하나를 파생하는지 확인하는 것입니다. MarshalByRefObject 되거나 직렬화 입니다.

`MarshalByRefObject를 사용하면 도메인의 수명을 구성 할 수 있습니다. 예를 들어 프록시가 20 분 내에 호출되지 않은 경우 도메인이 파괴되도록 할 수 있습니다.

이게 도움이 되길 바란다.


안녕하세요, 내가 올바르게 기억한다면 핵심 문제는 모든 종속성을 재귀 적으로로드하는 방법이므로 질문입니다. 유형 Foo, FooAssembly의 속성이있는 유형의 클래스 Bar, BarAssembly, 즉 총 3 개의 어셈블리 를 반환하도록 HelloWorld를 변경하여 코드를 테스트하십시오 . 계속 작동할까요?
abatishchev

예, 어셈블리 검사 단계에서 열거 된 적절한 디렉터리가 필요합니다. AppDomain에는 ApplicationBase가 있지만 테스트하지는 않았습니다. 또한 config 파일은 dll이 사용할 수 있고 속성에서 복사하도록 설정할 수있는 app.config와 같은 어셈블리 검색 디렉터리를 지정할 수 있습니다. 또한 별도의 앱 도메인에서로드하려는 어셈블리 빌드를 제어 할 수있는 경우 참조는 찾을 수있는 HintPath를 얻을 수 있습니다. 모든 것이 실패하면 새로운 AppDomains AssemblyResolve 이벤트를 구독하고 어셈블리를 수동으로로드하게됩니다.
SimperT
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.