AppDomains全体のメソッドパラメーターとして参照を渡すにはどうすればよいですか?
-
05-10-2019 - |
質問
私は次のコードを動作させようとしています(すべてが同じアセンブリで定義されています)。
namespace SomeApp{
public class A : MarshalByRefObject
{
public byte[] GetSomeData() { // }
}
public class B : MarshalByRefObject
{
private A remoteObj;
public void SetA(A remoteObj)
{
this.remoteObj = remoteObj;
}
}
public class C
{
A someA = new A();
public void Init()
{
AppDomain domain = AppDomain.CreateDomain("ChildDomain");
string currentAssemblyPath = Assembly.GetExecutingAssembly().Location;
B remoteB = domain.domain.CreateInstanceFromAndUnwrap(currentAssemblyPath,"SomeApp.B") as B;
remoteB.SetA(someA); // this throws an ArgumentException "Object type cannot be converted to target type."
}
}
}
私がやろうとしているのは、最初のアプリドメインで作成された「a」インスタンスの参照を子ドメインに渡し、子ドメインに最初のドメインでメソッドを実行することです。 「b」コードのある時点で、「remoteobj.getsomedata()」を呼び出します。これは、最初のAppDomainで「getSomedata」メソッドから「getSomedata」メソッドから「計算」する必要があるため、行う必要があります。例外を回避するためにどうすればよいですか、それとも同じ結果を達成するために何ができますか?
解決
この問題を複製できますが、testdriven.netおよび/またはxunit.netに関連しているようです。 c.init()をテスト方法として実行すると、同じエラーメッセージが表示されます。ただし、コンソールアプリケーションからc.init()を実行した場合、例外は得られません。
ユニットテストからc.init()を実行しているのと同じことを見ていますか?
編集:nunitとtestdriven.netを使用して問題を複製することもできます。また、testdriven.netの代わりにヌニットランナーを使用してエラーを複製することもできます。そのため、問題はテストフレームワークを介してこのコードを実行することに関連しているようですが、理由はわかりません。
他のヒント
実際の根本的な原因は、DLLが2つの異なるアプリドメインの異なる場所からロードされていたことです。これにより、.NETはそれらが異なるアセンブリであると考えるようになります。これはもちろん、タイプが異なることを意味します(同じクラス名、名前空間などがある場合でも)。
Jeffのテストがユニットテストフレームワークを実行したときに失敗した理由は、単位テストフレームワークが一般に「True」に設定されたShadowCopyを使用してAppDomainを作成するためです。しかし、手動で作成されたAppDomainは、デフォルトでShadowCopy = "False"になります。これにより、DLLはさまざまな場所からロードされ、「オブジェクトタイプをターゲットタイプに変換できません」につながります。エラー。
更新:さらにテストした後、2つのアプリドメイン間でアプリケーションベースが異なることにかかっているようです。それらが一致する場合、上記のシナリオは機能します。それらが違う場合はそうではありません(dllがWindbgを使用して同じディレクトリから両方のアプリドメインにロードされていることを確認しましたが)。また、両方のAppDomainsでShadowCopy = "true"をオンにすると、失敗します別のメッセージがあります:「System.InvalidCastException:オブジェクトはiconvertibleを実装する必要があります」。
update2:さらに読むことで、それが関連していると信じるようになります コンテキストをロードします. 。 「from」メソッド(Assembly.loadfrom、またはappdomain.createinstancefromwrap)のいずれかを使用する場合、アセンブリが通常のロードパス(アプリケーションベースまたはプロービングパスのいずれか)で見つかった場合、デフォルトにロードされますコンテキストをロードします。アセンブリがそこに見つからない場合、それはロードからのコンテキストにロードされます。したがって、両方のAppDomainが一致するアプリケーションベースを持っている場合、「From」メソッドを使用しても、どちらもそれぞれのAppDomainのデフォルトロードコンテキストにロードされます。ただし、アプリケーションベースが異なる場合、1つのAppDomainはデフォルトの負荷コンテキストにアセンブリを持ち、もう1つはアセンブリがロードからのコンテキストにあります。
これは@russellmcclureへのコメントですが、コメントのために複雑であるため、これを答えとして投稿します。
私はASP.NETアプリケーションの中にあり、Shadow-Copy(これも問題も解決する)をオフにすることは実際には選択肢ではありませんが、次の解決策を見つけました。
AppDomainSetup adSetup = new AppDomainSetup();
if (AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles == "true")
{
var shadowCopyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (shadowCopyDir.Contains("assembly"))
shadowCopyDir = shadowCopyDir.Substring(0, shadowCopyDir.LastIndexOf("assembly"));
var privatePaths = new List<string>();
foreach (var dll in Directory.GetFiles(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, "*.dll"))
{
var shadowPath = Directory.GetFiles(shadowCopyDir, Path.GetFileName(dll), SearchOption.AllDirectories).FirstOrDefault();
if (!String.IsNullOrWhiteSpace(shadowPath))
privatePaths.Add(Path.GetDirectoryName(shadowPath));
}
adSetup.ApplicationBase = shadowCopyDir;
adSetup.PrivateBinPath = String.Join(";", privatePaths);
}
else
{
adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
adSetup.PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
}
これにより、メインアプリドメインのShadow-Copyディレクトリをアプリケーションベースとして使用し、Shadow-Copyが有効になっている場合はすべてのShadow-Copiedアセンブリをプライベートパスに追加します。
誰かがこれを行うより良い方法があるなら、教えてください。