2017-12-08 33 views
2

ターゲットアプリケーションのアセンブリをリフレクションによって解析するWPFツールを構築しています。AppDomainを使用してアセンブリを一時的にロードする

私はこれまでターゲットアセンブリをロードするのにAssembly.Loadなどを使用していました。これにはいくつかの制限がありますが、ターゲットアプリケーションを再構築し、新しく作成されたアセンブリを再解析するためのツールを「リフレッシュ」できるようにしたいという点を除いて、これは問題ありません。これは、アセンブリが荷重でロックされ、ツールが終了するまで解放されないため、現在は機能しません。これにより、新しく組み立てられたアセンブリを再ロードすることも排除されます。

私は一時的なAppDomainを作成し、その中にアセンブリをロードし、やりたいと思うようにしてから、ドメインをアンロードすることができます。

私が抱えている問題は、動作させることができないということです。私は明示的に

  • エラーを作成し

    現在のアプリケーションドメインに
    • ロードではなく、1ロード要求されたアセンブリツールのアセンブリをロード
    • エラー:私は、多くのバリエーションを試してみましたが、のような結果を得るました(?)ここで提案次たとえば

    、:Create custom AppDomain and add assemblies to it

    は、私はこのようにSimpleAssemblyLoaderを作成しました:「ファイルが見つかりません」例外を報告

    private static AppDomain MakeDomain(string name, string targetPath, string toolPath) 
        { 
         var appDomain = 
          AppDomain.CreateDomain(name, AppDomain.CurrentDomain.Evidence, new AppDomainSetup 
           { 
            ApplicationBase = targetPath, 
            PrivateBinPath = toolPath, 
            LoaderOptimization = LoaderOptimization.MultiDomainHost 
           }, 
           new PermissionSet(PermissionState.Unrestricted)); 
         return appDomain; 
        } 
    
        /// <summary> 
        /// 
        /// </summary> 
        /// <param name="targetPath">Location of assemblies to analyze</param> 
        /// <param name="toolPath">Location of this tool</param> 
        /// <param name="file">Filename of assembly to analyze</param> 
        /// <returns></returns> 
        public string[] Test(string targetPath, string toolPath, string file) 
        { 
         var dom = MakeDomain("TestDomain", targetPath, toolPath); 
         var assemblyLoader = (SimpleAssemblyLoader)dom.CreateInstanceAndUnwrap(typeof(SimpleAssemblyLoader).Assembly.FullName, typeof(SimpleAssemblyLoader).FullName); 
    
         var path = Path.Combine(targetPath, file); 
    
         var assembly = assemblyLoader.LoadFrom(path); 
    
         var types = assembly.GetTypes(); 
    
         List<string> methods = new List<string>(); 
    
         foreach (var type in types) 
         { 
          foreach (var method in type.GetMethods(BindingFlags.Instance|BindingFlags.Public)) 
          { 
           methods.Add(method.Name); 
          } 
         } 
    
         AppDomain.Unload(dom); 
    
         return methods.ToArray(); 
        } 
    

    ...ツールアプリケーションが起動し、それはSimpleAssemblyLoaderをインスタンス化に失敗し、:

    public class SimpleAssemblyLoader : MarshalByRefObject 
    { 
        public Assembly Load(string path) 
        { 
         ValidatePath(path); 
    
         return Assembly.Load(path); 
        } 
    
        public Assembly LoadFrom(string path) 
        { 
         ValidatePath(path); 
    
         return Assembly.LoadFrom(path); 
        } 
    
        public Assembly UnsafeLoadFrom(string path) 
        { 
         ValidatePath(path); 
    
         return Assembly.UnsafeLoadFrom(path); 
        } 
    
        private void ValidatePath(string path) 
        { 
         if (path == null) throw new ArgumentNullException(nameof(path)); 
         if (!System.IO.File.Exists(path)) 
          throw new ArgumentException($"path \"{path}\" does not exist"); 
        } 
    } 
    
    ...ので、呼び出し元のメソッドでそれを使用しますツールのアセンブリに関連付けられている - 明らかに、ツールのアセンブリを新しいドメインにロードしようとしています(?)。

    私は間違っていますが、どうすれば修正できますか?

  • +0

    ツールパスとは何ですか?ツールを使ったディレクトリへのパス? – Evk

    +0

    @エビックはい。 PrivateBinPathを使用する必要がある場合、または私がそれを悪用している場合、私には不明です。あなたの質問に答えてサンプルコードを編集しました。 –

    答えて

    1

    ここにいくつかの問題があります。まず、ドキュメントに記載されているように、に対して、PrivateBinPathが相対的である必要があります。それ以外の場合は無視されます。これはあなたの場合に起こることです。それは無視され、ApplicationBaseはツールディレクトリではなくターゲットアプリケーションのディレクトリを指しているので、アプリドメインにはSimpleAssemblyLoaderのロード先が分かりません。あなたが国会をCHにロードされている

    var assembly = assemblyLoader.LoadFrom(path); 
    

    を呼び出すと

    public Assembly LoadFrom(string path) { 
        ValidatePath(path); 
        return Assembly.LoadFrom(path); 
    } 
    

    private static AppDomain MakeDomain(string name, string toolPath) 
    { 
        var appDomain = 
         AppDomain.CreateDomain(name, AppDomain.CurrentDomain.Evidence, new AppDomainSetup 
          { 
           ApplicationBase = toolPath,       
           LoaderOptimization = LoaderOptimization.MultiDomainHost 
          }, 
          new PermissionSet(PermissionState.Unrestricted)); 
        return appDomain; 
    } 
    

    それとも

    private static AppDomain MakeDomain(string name) { 
        var appDomain = 
         AppDomain.CreateDomain(name, AppDomain.CurrentDomain.Evidence, new AppDomainSetup { 
           ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, 
           LoaderOptimization = LoaderOptimization.MultiDomainHost 
          }, 
          new PermissionSet(PermissionState.Unrestricted)); 
        return appDomain; 
    } 
    

    第二の問題:この問題を解決するには、ちょうどtoolPathApplicationBaseなどを使用ildアプリケーションドメインこのアセンブリを結果として返すとき(var assembly) - 現在のアプリケーションドメインもそれをロードします(このアセンブリがどこに配置されているか分かりません)。現在のドメインと子ドメインの両方にロードされる(悪い)か、現在のドメインにロードできないため、このようなアセンブリを返すべきではありません。代わりに - 子ドメインで完全にそれを実行して結果を返すためにMarshalByRefObjectにあなたの全体の方法を置く:

    public class WpfInspector : MarshalByRefObject { 
        public string[] Inspect(string path) { 
         ValidatePath(path); 
         var assembly = Assembly.LoadFrom(path); 
    
         var types = assembly.GetTypes(); 
    
         List<string> methods = new List<string>(); 
    
         foreach (var type in types) { 
          foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public)) { 
           methods.Add(method.Name); 
          } 
         } 
         return methods.ToArray(); 
        } 
    
        private void ValidatePath(string path) { 
         if (path == null) throw new ArgumentNullException(nameof(path)); 
         if (!System.IO.File.Exists(path)) 
          throw new ArgumentException($"path \"{path}\" does not exist"); 
        } 
    } 
    

    その後

    var dom = MakeDomain("TestDomain", toolPath);    
    var inspector = (WpfInspector)dom.CreateInstanceAndUnwrap(typeof(WpfInspector).Assembly.FullName, typeof(WpfInspector).FullName); 
    
    var path = Path.Combine(targetPath, file); 
    var methods = inspector.Inspect(path); 
    AppDomain.Unload(dom); 
    
    +0

    ありがとうございます、私は私の問題を解決するために必要なものを提供しました。あなたの答えにちょうど1つの修正: 'Assembly.LoadFrom'は依然として親ドメインにロードされています。 'AppDomain.Load'メソッドを使用する必要があります。これはもう少し作業が必要ですが、別のSOスレッドでこれを行う方法をすでに知っていました。 Assembly.LoadFromは私が試みていたすべての異なる順列から私の質問にうっとりしました。 –

    +0

    @CRobinsonうまくいけば動作します。しかし、 'AppDomain.Load'は、他のアプリケーションドメインに何かをロードするのに使うのはお勧めできません。あなたの目標にどのように使用しているのか分かりません。また、Assembly.LoadFrom'は、そのアセンブリやタイプを返さない限り、親appドメインに読み込まれません。たとえば、テスト中に、アプリドメインがアンロードされた後でターゲットファイルを自由に変更することができました。ターゲットアセンブリは親アプリケーションドメインにロードされず、ロックされませんでした。もちろん、子ドメイン( 'MarshalByRefObject'から)で実行された' Assembly.LoadFrom'を意味します。 – Evk

    関連する問題