2013-08-01 47 views
17

お互いを参照するC#プロジェクトとC++/CLIプロジェクトの両方で大きな.NETソリューションがあります。 私たちはまた、いくつかのユニットテストプロジェクトを持っています。私たちは最近、Visual Studio 2010 & .NET 4.0からVisual Studio 4.5 & .NET 4.5にアップグレードしました。ユニットテストを実行しようとすると、テスト中にいくつかのDLLがロードされているようです。新しいAppDomainで混在C#とC++/CLI dllをロードしようとすると、間違ったAppplicationBaseからDLLがロードされる

この問題は、単体テストが別のAppDomainで実行されるために発生します。ユニットテストプロセス(たとえばnunit-agent.exe)は、AppBaseをテストプロジェクトの場所に設定して新しいAppDomainを作成しますが、Fusion Logによれば、DLLのいくつかは、AppDomainのAppBaseではなくAppBaseとしてnunitの実行可能ディレクトリ。

私は簡単なシナリオで問題を再現することができました。これにより、新しいAppDomainが作成され、テストがそこで実行されます。ここでは(私は無実を保護するためにユニットテストクラス、メソッド、およびDLLの場所の名称を変更)、それがどのように見えるかです:

class Program 
{ 
    static void Main(string[] args) 
    { 

     var setup = new AppDomainSetup { 
      ApplicationBase = "C:\\DirectoryOfMyUnitTestDll\\" 
     }; 

     AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup); 
     ObjectHandle handle = Activator.CreateInstanceFrom(domain, typeof(TestRunner).Assembly.CodeBase, typeof(TestRunner).FullName); 
     TestRunner runner = (TestRunner)handle.Unwrap(); 
     runner.Run(); 

     AppDomain.Unload(domain); 
    } 

} 

public class TestRunner : MarshalByRefObject 
{ 
    public void Run() 
    { 
     try 
     { 
      HtmlTransformerUnitTest test = new HtmlTransformerUnitTest(); 
      test.SetUp(); 
      test.Transform_HttpEquiv_Refresh_Timeout(); 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e); 
     } 
    } 
} 

これは、ユニットテストを実行しようとしたとき、私が得る例外です。

 
System.TypeInitializationException: The type initializer for '' threw an exception. 
---> .ModuleLoadExceptionHandlerException: A nested exception occurred after the primary exception that caused the C++ module to fail to load. 
---> System.TypeInitializationException: The type initializer for '' threw an exception. 
---> .ModuleLoadException: The C++ module failed to load during vtable initialization. 
---> System.IO.FileNotFoundException: Could not load file or assembly 'CSharpDll, Version=8.80.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. 
    at [email protected]@[email protected]@[email protected]@@YMXXZ() 
    at _initterm_m((fnptr)* pfbegin, (fnptr)* pfend) in f:\dd\vctools\crt_bld\self_x86\crt\src\puremsilcode.cpp:line 219 
    at .LanguageSupport.InitializeVtables(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 331 
    at .LanguageSupport._Initialize(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 491 
    at .LanguageSupport.Initialize(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 702 
    --- End of inner exception stack trace --- 
    at .ThrowModuleLoadException(String errorMessage, Exception innerException) in f:\dd\vctools\crt_bld\self_x86\crt\src\minternal.h:line 194 
    at .LanguageSupport.Initialize(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 712 
    at .cctor() in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 754 
    --- End of inner exception stack trace --- 
    at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo) 
    at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode) 
    at .DoCallBackInDefaultDomain(IntPtr function, Void* cookie) in f:\dd\vctools\crt_bld\self_x86\crt\src\minternal.h:line 406 
    at .DefaultDomain.Initialize() in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 277 
    at .LanguageSupport.InitializeDefaultAppDomain(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 342 
    at .LanguageSupport._Initialize(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 539 
    at .LanguageSupport.Initialize(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 702 
    --- End of inner exception stack trace --- 
    at .ThrowNestedModuleLoadException(Exception innerException, Exception nestedException) in f:\dd\vctools\crt_bld\self_x86\crt\src\minternal.h:line 184 
    at .LanguageSupport.Cleanup(LanguageSupport* , Exception innerException) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 662 
    at .LanguageSupport.Initialize(LanguageSupport*) in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 710 
    at .cctor() in f:\dd\vctools\crt_bld\self_x86\crt\src\mstartup.cpp:line 754 
    --- End of inner exception stack trace --- 

これは私が見ているものです:あなたが見ることができるように、問題はC++のdllが初期化されて起こるとC#のDLLをロードしよう(私はCPlusPlusDllとCSharpDllに関与するDLLの名前を変更しました) Fusionのログに(私ではなく、元のSomeDLL.dllするDLLの名前を変更した):

 
*** Assembly Binder Log Entry (8/1/2013 @ 01:47:48 PM) *** 

The operation failed. 
Bind result: hr = 0x80070002. The system cannot find the file specified. 

Assembly manager loaded from: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll 
Running under executable c:\users\yshany\documents\visual studio 2012\Projects\MyTester\MyTester\bin\Debug\MyTester.exe 
--- A detailed error log follows. 

=== Pre-bind state information === 
LOG: User = WF-IL\yshany 
LOG: DisplayName = SomeDLL, Version=8.80.0.0, Culture=neutral, PublicKeyToken=null 
(Fully-specified) 
LOG: Appbase = file:///c:/users/yshany/documents/visual studio 2012/Projects/MyTester/MyTester/bin/Debug/ 
LOG: Initial PrivatePath = NULL 
LOG: Dynamic Base = NULL 
LOG: Cache Base = NULL 
LOG: AppName = MyTester.exe 
Calling assembly : (Unknown). 
=== 
LOG: This bind starts in default load context. 
LOG: Using application configuration file: c:\users\yshany\documents\visual studio 2012\Projects\MyTester\MyTester\bin\Debug\MyTester.exe.Config 
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config. 
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind). 
LOG: Attempting download of new URL file:///c:/users/yshany/documents/visual studio 2012/Projects/MyTester/MyTester/bin/Debug/SomeDLL.DLL. 
LOG: Attempting download of new URL file:///c:/users/yshany/documents/visual studio 2012/Projects/MyTester/MyTester/bin/Debug/SomeDLL/SomeDLL.DLL. 
LOG: Attempting download of new URL file:///c:/users/yshany/documents/visual studio 2012/Projects/MyTester/MyTester/bin/Debug/SomeDLL.EXE. 
LOG: Attempting download of new URL file:///c:/users/yshany/documents/visual studio 2012/Projects/MyTester/MyTester/bin/Debug/SomeDLL/SomeDLL.EXE. 
LOG: All probing URLs attempted and failed. 

あなたが見ることができるように、問題はMyTester.exeが存在する場所のappBaseではなく、ということですSomeDLL.dllが存在する場所(これは、単体テストdllと同じ場所です)。これは、上記の例外で言及した両方のDLLを含むいくつかのDLLで発生します。

私はまた、より単純な単体テストプロジェクト(3つのプロジェクト - 別のC#プロジェクトを参照するC++/CLIプロジェクトを参照するC#プロジェクトの小さなVS2012ソリューション)で再現しようとしましたが、問題は再現せず、機能しました完璧。以前に述べたように、VS2012 & .NET 4.5にアップグレードする前に単体テストは正常でした。

どうすればよいですか? ありがとう!

+0

はそれだけでNUnitの-テストランナーで発生していunamanaged C++クラスからコンストラクタを削除することができますか?あなたはMSTestでそれを再現できますか? –

+0

NUnit、MSTest、そしてここで書いたTesterプログラムでも起こります。 –

+0

この難読化は私たちがあなたを助けるのに役立つものではありません。 "CSharpDll"と "SomeDLL"の関係は何ですか? –

答えて

11

これは.NET 4.5のバグです。

NUnitは単体テストを実行するための新しいアプリケーションドメインを作成します。単体テストアセンブリまたはその参照のいずれかが混合モードアセンブリである場合、特定の条件下では、既定のアプリケーションドメインにも混在モードアセンブリの参照をロードしようとします。

ランタイムでは、混合モードアセンブリのアンマネージC++コードを初期化して、そのアセンブリ内の他のものを実行する必要があります。これは自動的にコンパイルされたLanguageSupportクラスを介して行われます(ソースコードはVisual Studioと共に配布されます)。 LanguageSupport::Initializeは、NUnitで作成されたappdomainのコンテキストで、混合モード単体テストアセンブリのコンパイラが生成した.moduleクラスの静的コンストラクタで最初に実行されます。 LanguageSupportは、デフォルトのappdomain内の同じ静的コンストラクタを再度トリガーし、最後にLanguageSupport::Initializeを再度呼び出します。ここではマイナスのエラー処理のものの上から同じコールスタックです:

at _initterm_m((fnptr)* pfbegin, (fnptr)* pfend) 
    at .LanguageSupport.InitializeVtables(LanguageSupport*) 
    at .LanguageSupport._Initialize(LanguageSupport*) 
    at .LanguageSupport.Initialize(LanguageSupport*) 
    at .LanguageSupport.Initialize(LanguageSupport*) 
    at .DoCallBackInDefaultDomain(IntPtr function, Void* cookie) 
    at .LanguageSupport.InitializeDefaultAppDomain(LanguageSupport*) 
    at .LanguageSupport._Initialize(LanguageSupport*) 
    at .LanguageSupport.Initialize(LanguageSupport*) 
    at .LanguageSupport.Initialize(LanguageSupport*) 

NUnitのは、実際にユニットテストアセンブリとその参照をロードすることに成功している作成するのAppDomain(あなたが他の問題を持っていないと仮定)が、デフォルトのappdomainの2番目のLanguageSupport初期化に失敗しています。

混合モードアセンブリ用のILをダンプすると、アンマネージクラスの中には静的な初期化メソッドが自動的に生成されていることがわかりました。これらは、呼び出しの先頭から2番目のInitializeVtablesメソッドで呼び出されるメソッドの1つですスタック。試行錯誤の後、アンマネージクラスにコンストラクタがあり、シグネチャに.NET型の仮想メソッドが少なくとも1つあれば、コンパイラはそのクラスの静的イニシャライザを発行することを発見しました。

は、これらの静的初期化関数を呼び出します。イニシャライザが実行されると、CLRはアンマネージクラスの仮想メソッドのシグネチャにあるインポートされた型を含む参照をロードしようとしているようです。既定のappdomainには、アプリケーションベースで単体テストアセンブリとその参照が含まれていないため、呼び出しが失敗し、上記のエラーが生成されます。

さらに、実行される別の非vtableイニシャライザがある場合にのみエラーが発生します(とにかく、私が作ったおもちゃアプリ)。

class DomainDumper { 
public: 
    DomainDumper() { 
     Console::WriteLine("Dumper called from appdomain {0}", 
     AppDomain::CurrentDomain->Id); 
    } 
}; 

// comment out this line and InitializeVtables succeeds in default appdomain 
DomainDumper dumper; 

class CppClassUsingManagedRef { 
public: 
    // comment out this line and the dynamic vtable initializer doesn't get created 
    CppClassUsingManagedRef(){} 

    virtual void VirtualMethodWithNoArgs() {} 

    // comment out this line and the dynamic vtable initializer doesn't get created 
    virtual void VirtualMethodWithImportedTypeRef(ReferredToClassB^ bref) {} 

    void MethodWithImportedTypeRef(ReferredToClassB^ bref) {} 
}; 

回避策:

  • あなたのユニットテストは、NUnitの実行可能ファイルのサブディレクトリにある場合は(そう、私は推測する)、することができますmodify the <probing> portion of the app.config file

    は、ここに私のアプリの関連部分です。

  • ユニットテストディレクトリにnunitとその依存関係をコピーすることができます。
  • アンマネージドC++クラスの仮想メソッドを変更して、NUnitがロードできないタイプへの参照を除外できます。これは、自分自身をObject^に限定し、メソッドの実装で実際の型にキャストすることで行うことができます。これは非常に難解ですが動作します。
  • あなたは、問題の仮想メソッドを作ることができる非仮想1
  • あなたは
+0

非常に詳細な回答、Mullet氏。ありがとう! –

+0

@Oliver Melletは、 "nunitとそのユニットテストディレクトリへの依存関係をコピーする"ために、ユニットテストディレクトリからテストを強制的に実行することは可能ですか?私はtestディレクトリからnunit-x86.exeを実行するだけで成功しました。 – Amanduh

+0

[起こるように](http://stackoverflow.com/questions/32493614/different-dependency-resolution-behavior-loading-assembly-in-default-appdomain-v)、NUnitにデフォルトのAppDomainでテストを実行させる/ domain:Nonenit 2.x)は、適用可能な場合は別の回避策です。 – rationull

関連する問題