すべての参照を含むアセンブリを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'またはその依存関係の1つを読み込めませんでした。システムは、指定されたファイルを見つけることができません。

重要な部分は依存関係の1つだと思います。

はい、前に行います 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);
        }
    }
}

また、使用LoadFromするFileNotFoundと、アセンブリリゾルバーがGACまたは現在のアプリケーションのbinフォルダーでロードしているアセンブリを見つけようとするため、例外が発生する可能性があります。LoadFile代わりに任意のアセンブリファイルを読み込むために使用します。ただし、これを行う場合は、依存関係を自分で読み込む必要があることに注意してください。


20
この問題を解決するために私が作成したコードgithub.com/jduv/AppDomainToolkitを確認してください。具体的には、このクラスのLoadAssemblyWithReferencesメソッドを見て:github.com/jduv/AppDomainToolkit/blob/master/AppDomainToolkit/...
Jduv

3
私はこの作品が検出されませんでしたほとんどの時間を、中にいくつかの例あなたが実際にはまだにハンドラをアタッチする必要があるAppDomain.CurrentDomain.AssemblyResolveで説明したようにイベントこのMSDNの答え。私の場合、MSTestの下で実行されているSpecRunデプロイメントにフックしようとしていましたが、コードが「プライマリ」AppDomainから実行されない多くの状況に当てはまると思います
-VS

面白いですね。私はそれを調べて、ADTでの作業が少し簡単になるかどうかを確認します。そのコードはしばらくの間少し死んでしまったことに申し訳ありません-私たちは皆日帰りの仕事をしています:)
Jduv 14

@Jduvできれば、あなたのコメントを約100回賛成します。あなたのライブラリは、MSBuildでの動的アセンブリ読み込みで私が抱えていた一見解決できない問題を解決するのに役立ちました。あなたはそれを答えに昇格させるべきです!
フィリップダニエルズ

2
@Jduvは、assembly変数が「MyDomain」からのアセンブリを参照することを確信していますか?var assembly = value.GetAssembly(args[0]);あなたはargs[0]両方のドメインに あなたをロードし、assembly変数はメインアプリケーションドメインからのコピーを参照すると思います
Igor 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/...
クリストフMeißnerに

32
これは機能しません。コードを実行してAppDomain.CurrentDomain.GetAssemblies()を確認すると、ロードしようとしているターゲットアセンブリが、プロキシではなく現在のアプリケーションドメインにロードされていることがわかります。
Jduv 2012年

41
これはまったくナンセンスです。から継承しMarshalByRefObjectても、魔法のように他のすべてに読み込まれるわけではありません。AppDomain参照AppDomainを別AppDomainCreateInstanceAndUnwrap方法でアンラップするときにシリアル化を使用する代わりに、透過的なリモートプロキシを作成するように.NETフレームワークに指示するだけです(一般的な方法はメソッドです)。この回答には30以上の賛成票があるとは信じられません。ここでのコードは、呼び出しの無意味な回り道Assembly.LoadFromです。
アーロンノート、2014年

1
はい、それは完全にナンセンスのように見えますが、28票の投票があり、回答としてマークされています。提供されたリンクは、MarshalByRefObjectについても言及していません。かなり奇妙です。これが実際に何かをするなら、私は誰かがどのように説明するのが好きですか
Mick

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
ニール

11

新しいAppDomainで、AssemblyResolveイベントハンドラーを設定してみてください。このイベントは、依存関係が欠落しているときに呼び出されます。


そうではありません。実際、このイベントを新しいAppDomainに登録している行で例外が発生します。このイベントを現在のAppDomainに登録する必要があります。
user1004959 2013年

クラスがMarshalByRefObjectから継承されている場合に行われます。クラスが[Serializable]属性のみでマークされている場合はそうではありません。
user2126375

5

参照されるアセンブリがGACまたはCLRのプローブパスにない場合は、AppDomain.AssemblyResolveまたはAppDomain.ReflectionOnlyAssemblyResolveイベント(実行している負荷に応じて)を処理する必要があります。

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve


だから私は要求されたアセンブリを手動で示す必要がありますか?新しいAppDomainのAppBaseにもありますか?それをしない方法はありますか?
abatishchev 2009年

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

コードに小さな誤植がいくつかあり、私はそれがうまくいくとは思わなかったことを認めざるを得ませんが、これは私にとって命の恩人でした。トンありがとう。
オーウェンアイボリー

4

キーは、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

このプロジェクトは、クライアントプロジェクトと同様にプロキシプロジェクトを参照し、インターフェイスを実装します。

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分以内に呼び出されなかった場合にドメインを破棄したいとします。

これがお役に立てば幸いです。


こんにちは、私が正しく覚えているとしたら、中心的な問題は、すべての依存関係を再帰的にロードする方法でした。HelloWorldを変更Foo, FooAssemblyして、typeのプロパティを持つタイプのクラスを返すようにコードをテストしてくださいBar, BarAssembly。つまり、合計3つのアセンブリです。引き続き機能しますか?
abatishchev 2018

はい、アセンブリのプローブ段階で列挙された適切なディレクトリが必要です。AppDomainにはApplicationBaseがありますが、テストしていません。また、構成ファイルでは、dllが使用できるapp.configなどのアセンブリプローブディレクトリを指定できるだけでなく、プロパティにコピーするように設定することもできます。また、別のアプリドメインにロードするアセンブリの構築を制御できる場合、参照は、それを探すことを指定したHintPathを取得できます。すべてが失敗した場合、新しいAppDomains AssemblyResolveイベントをサブスクライブし、手動でassembly.Tonsをロードします。
SimperT 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.