.NET 5.0のC#プロジェクトからC++/CLIのDLLを使う際に起きたSystem.BadImageFormatException（間違ったフォーマットのプログラムを読み込もうとしました）の解決方法

# はじめに

手元にある太古のCで書かれたWin32なスタティック ライブラリを、Visual Studio 2019でASP.NET 5.0を使ってWeb API化しようと思い立ち挑戦したところ、スタティック ライブラリをラップした C++/CLI のアセンブリを使うところで

```
System.BadImageFormatException: Could not load file or assembly 'HogeWrapper, Version=1.0.7796.41445, Culture=neutral, PublicKeyToken=null'. 間違ったフォーマットのプログラムを読み込もうとしました。
```
のエラーでハマってしまったため、解決方法をメモしておきます。

# 背景

10年以上前のVisual StudioでビルドされたWin32（x86）のスタティック ライブラリ` hoge.lib`を.Net 5.0で使うために、まず C++/CLI でラッパーのクラスライブラリのDLL`HogeWrapper.dll`を作成しました。元のLIBが64bit対応していないので、このDLLも32bit縛りです。

次にDLL単独の動作確認のため、同じソリューション内に.Net 5.0 C#のコンソール アプリのプロジェクトを作って、ソリューション内のDLLのプロジェクトと依存関係を結んで実行したところ、うまく使うことができました。

そして最後に、別のソリューションでASP.NET 5.0 C#のWeb APIプロジェクトを作り、`HogeWrapper.dll`を参照設定しました。これで間接的に`hoge.lib`の機能が呼び出せるようになります。ビルドまでは順調でした。

しかし、実行時にハマりました。以下、Debug実行時の出力ウィンドウの途中部分なのですが、せっかくロードした`HogeWrapper.dll`がすぐにアンロードされてしまい、例外`System.BadImageFormatException`がスローされてしまいます。

```
'Hoge.exe' (Win32): 'C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\HogeWrapper.dll' が読み込まれました。シンボルが読み込まれました。
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\msvcp140d.dll' が読み込まれました。
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\ucrtbased.dll' が読み込まれました。
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\vcruntime140d.dll' が読み込まれました。
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\vcruntime140d.dll' がアンロードされました
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\ucrtbased.dll' がアンロードされました
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\msvcp140d.dll' がアンロードされました
'Hoge.exe' (Win32): 'C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\HogeWrapper.dll' がアンロードされました
'Hoge.exe' (Win32): 'C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\HogeWrapper.dll' が読み込まれました。シンボルが読み込まれました。
0x7519C602 で例外がスローされました (Hoge.exe 内): Microsoft C++ の例外: HRException (メモリの場所 0x065ACAD0)。
'Hoge.exe' (Win32): 'C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\HogeWrapper.dll' がアンロードされました
0x7519C602 で例外がスローされました (Hoge.exe 内): Microsoft C++ の例外: [rethrow] (メモリの場所 0x00000000)。
0x7519C602 で例外がスローされました (Hoge.exe 内): Microsoft C++ の例外: EEFileLoadException (メモリの場所 0x065AD048)。
0x7519C602 で例外がスローされました (Hoge.exe 内): Microsoft C++ の例外: [rethrow] (メモリの場所 0x00000000)。
0x7519C602 で例外がスローされました (Hoge.exe 内): Microsoft C++ の例外: [rethrow] (メモリの場所 0x00000000)。
0x7519C602 で例外がスローされました (Hoge.exe 内): Microsoft C++ の例外: [rethrow] (メモリの場所 0x00000000)。
'Hoge.exe' (Win32): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\Microsoft.DiaSymReader.Native.x86.dll' が読み込まれました。
'Hoge.exe' (Win32): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.Diagnostics.StackTrace.dll' が読み込まれました。
'Hoge.exe' (CoreCLR: clrhost): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.Diagnostics.StackTrace.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'Hoge.exe' (Win32): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.Reflection.Metadata.dll' が読み込まれました。
'Hoge.exe' (CoreCLR: clrhost): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.Reflection.Metadata.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'Hoge.exe' (Win32): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.Collections.Immutable.dll' が読み込まれました。
'Hoge.exe' (CoreCLR: clrhost): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.Collections.Immutable.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'Hoge.exe' (Win32): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.IO.MemoryMappedFiles.dll' が読み込まれました。
'Hoge.exe' (CoreCLR: clrhost): 'C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.IO.MemoryMappedFiles.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Error: An unhandled exception has occurred while executing the request.

System.BadImageFormatException: Could not load file or assembly 'HogeWrapper, Version=1.0.7796.41445, Culture=neutral, PublicKeyToken=null'. 間違ったフォーマットのプログラムを読み込もうとしました。
File name: 'HogeWrapper, Version=1.0.7796.41445, Culture=neutral, PublicKeyToken=null'
   at Hoge.Controllers.KeywordItemsController.Get(String data)
   at lambda_method2(Closure , Object , Object[] )
```

# 解決方法

この`System.BadImageFormatException`は、x86（Win32）とx64を混在させた時などに起こるそうなのですが、ASP.NET 5.0のWeb API側もきちんとx86に縛っていて混在はしていません。

ググってもうまく日本語の情報が見つからず、仕方がないので苦手な英語のサイトを追い始めたところ、Stack OverflowでそのものズバリのQAがありました。

[Bad image format when running managed C++/CLI assembly in .NET Core 3.1](https://stackoverflow.com/questions/58765242/bad-image-format-when-running-managed-c-cli-assembly-in-net-core-3-1)

これは .NET Core 3.1時代のものですが .NET 5.0も同様でした。C++/CLIのDLLを使う場合、DLLだけ持ってきてもダメで、同じ位置に出力される`Ijwhost.dll`もDLLと同じ位置に持っていかないとダメだそうです。今回 .NET Core系のC++/CLIを初めて使ったので、この辺りの常識を全く知りませんでした。

DLLは`C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\HogeWrapper.dll`にあるので、同じ位置に`Ijwhost.dll`を持っていったところ、無事にエラーが解消しました。以下、正常時の出力ウィンドウの抜粋です。DLLと同じ位置に置いた`Ijwhost.dll`が読み込まれているのがわかります。

```
'Hoge.exe' (Win32): 'C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\HogeWrapper.dll' が読み込まれました。シンボルが読み込まれました。
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\vcruntime140d.dll' が読み込まれました。
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\ucrtbased.dll' が読み込まれました。
'Hoge.exe' (Win32): 'C:\Windows\SysWOW64\msvcp140d.dll' が読み込まれました。
'Hoge.exe' (Win32): 'C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\Ijwhost.dll' が読み込まれました。
'Hoge.exe' (CoreCLR: clrhost): 'C:\Users\Segavvy\source\repos\Hoge\Hoge\bin\x86\Debug\net5.0\HogeWrapper.dll' が読み込まれました。シンボルが読み込まれました。
```

なお、最初に`Hoge.dll`の動作確認を簡単なC#のコンソール アプリで実施した時につまずかなかったのは、同一ソリューション内にDLLのプロジェクトも入れて、プロジェクトの依存関係を結んで使ったためです。これならDLLの位置に`Ijwhost.dll`もある形が維持されます。

別ソリューションからDLLの参照設定で使う場合は、今回のような注意が必要です。Debug/Releaseの切り替えなども考えると、手元にプロジェクト ファイルがある場合は同一ソリューションに入れてしまうのが正解みたいです。

# おわりに

.NET Core系を初めて使っているので、誤りがあるかも知れません。正しい参照設定の方法がありそうな気もします。何かわかったら更新します。識者の方のアドバイスもお待ちしております。

# （参考）

[.NetCore3.1向けC++/CLIを軽く試した感想](https://qiita.com/minion_cs_user/items/94b4a2e52e9239c36a42)