複雑な参照ツリー(MyDll.dll-> Microsoft.Office.Interop.Excel.dll-> Microsoft.Vbe.Interop.dll-> Office.dll-を持つ新しいAppDomain
アセンブリに読み込みたい> 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つだと思います。
OK、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);
}
}
}
また、LoadFrom
を使用すると、FileNotFound
例外が発生する可能性があることに注意してください。アセンブリリゾルバーは、GACまたは現在のアプリケーションのbinフォルダーで読み込んでいるアセンブリを見つけようとするためです。代わりに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 イベントハンドラーを設定してみてください。そのイベントは、依存関係が欠落しているときに呼び出されます。
Assemblyインスタンスを呼び出し元ドメインに渡すと、呼び出し元ドメインはそれをロードしようとします!これが例外を受け取る理由です。これは、コードの最後の行で発生します。
domain.Load(AssemblyName.GetAssemblyName(path));
したがって、アセンブリで何をしたいかは、プロキシクラスで行う必要があります。これは、MarshalByRefObjectを継承するクラスです。
呼び出し元ドメインと新しく作成されたドメインの両方がプロキシクラスAssemblyにアクセスできる必要があることを考慮してください。問題がそれほど複雑でない場合は、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);
}
}
}
現在のアプリドメインフォルダーとは異なるフォルダーからアセンブリを読み込む必要がある場合は、特定のdlls検索パスフォルダーで新しいアプリドメインを作成します。
たとえば、上記のコードのアプリドメイン作成行を次のように置き換える必要があります。
var dllsSearchPath = @"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);
これにより、すべてのdllがdllsSearchPathから自動的に解決されます。
参照されるアセンブリがGACまたはCLRのプローブパスにない場合は、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"});
}
キーは、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"}));
}
これを何回かしなければならず、多くの異なる解決策を研究しました。
私が見つけた最もエレガントで簡単な解決策は、そのように実装できます。
インターフェイスには、呼び出したいメンバーの署名が含まれます。
public interface IExampleProxy
{
string HelloWorld( string name );
}
このプロジェクトをクリーンでライトな状態に保つことが重要です。これは、AppDomain
の両方が参照できるプロジェクトであり、クライアントアセンブリから別のドメインにロードするAssembly
を参照できないようにします。
AppDomain
にロードするコードを含むプロジェクトを作成します。このプロジェクトはクライアントprojと同様にプロキシprojを参照し、インターフェイスを実装します。
public interface Example : MarshalByRefObject, IExampleProxy
{
public string HelloWorld( string name )
{
return $"Hello '{ name }'";
}
}
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
を使用するのが好きです。 Assemblyバイトを読み込み、型をインスタンス化して、プロキシ型に簡単にキャストできるobject
を返します。そうでない場合は、厳密に型指定されたコードに動的言語ランタイムを使用して、返されたdynamic
型変数へのオブジェクトは、そのメンバーを直接呼び出します
これにより、クライアントプロジェクトが別のAppDomain
で参照していないアセンブリをロードし、クライアントからそのメンバーを呼び出すことができます。
重要なのは、コードがMarshalByRefObject
を派生させるか、シリアライズ可能であることを確認することです。
`MarshalByRefObjectを使用すると、ドメインのライフタイムを設定できます。たとえば、20分以内にプロキシが呼び出されなかった場合にドメインを破棄したいとします。