Any CPU -> x64移行でハマった:InstallUtil.exeは同じアーキテクチャで実行する必要がある
C# と .NET Framework で作成していた Windows サービスのターゲットアーキテクチャを Any CPU から x64 に移行したときにハマったエピソードです。
なぜ x64 に移行したのか
今回 x64 に移行した理由は主に以下の通りです。
- Windows 11 では x86 版 OS が提供されていない
- パフォーマンス向上を期待した
- 物理 CPU のアーキテクチャと一致する
- x86 のメモリ制限を回避できる
- x86 固有のライブラリに依存していなかった
発生した問題
x64 ビルドしたアプリケーションを InstallUtil.exe で登録しようとしたところ、サービス登録に失敗しました。
実際に出力されたエラーメッセージは以下の通りです。
PS C:\Users\xxx> C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /ServiceName="MyService" /DispName="MyService" "C:\path\to\BackgroundService\bin\x64\Release\BackgroundService.exe"
Microsoft(R) .NET Framework Installation utility Version 4.8.9221.0
Copyright (C) Microsoft Corporation. All rights reserved.
インストールを初期化中に例外が発生しました:
System.BadImageFormatException: ファイルまたはアセンブリ 'file:///C:\path\to\BackgroundService\bin\x64\Release\BackgroundService.exe'、またはその依存関係の 1 つが読み込めませんでした。間違ったフォーマットのプログラムを読み込もうとしました。
エラーメッセージの読み方
このエラーメッセージのポイントはここです。
そして、例外がSystem.BadImageFormatExceptionであることです。
一見、「ファイルが見つからないエラー」に見えますが、実際は「アーキテクチャ(x86 / x64)の不一致」が原因です。
原因:アーキテクチャ不一致
System.BadImageFormatException は、.NET(.NET Framework / .NET いずれでも)において、アセンブリのアーキテクチャ(x86 / x64)が一致していない場合に発生する代表的な例外です。
- x86プロセスで x64アセンブリを読み込もうとした
- x64プロセスで x86専用アセンブリを読み込もうとした
- ネイティブDLLのビット数が一致しない
今回のケースは1番目です。
- InstallUtil.exe (x86)
- 対象アプリケーション(x64)
だったため、アセンブリのロードに失敗し、System.BadImageFormatExceptionが発生していました。
InstallUtil.exe の動作と制約
InstallUtil.exe は内部でアセンブリをロードして処理します。
今回の例では、アセンブリは BackgroundService.exe です。
Microsoft の公式ドキュメントでは次のように説明されています。
Installutil.exe uses reflection to inspect the specified assemblies and to find all Installer types.
https://learn.microsoft.com/en-us/dotnet/framework/tools/installutil-exe-installer-tool
つまり、リフレクションを使って Installer クラスを検出するために、アセンブリをロードします。今回の例では、ここで失敗しました。
解決策
今回の問題の解決策は単純で、InstallUtil.exe を対象アプリケーションと同じアーキテクチャのものに切り替えることです。
公式ドキュメントでも以下のようにも明記されています。
Use the 32-bit Installer tool to install 32-bit assemblies, and the 64-bit Installer tool to install 64-bit assemblies.
https://learn.microsoft.com/en-us/dotnet/framework/tools/installutil-exe-installer-tool
今回、対象アプリケーションを x64 でビルドしたため、x86 版ではなく x64 版の InstallUtil.exe を使う必要がありました。
- C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /ServiceName="MyService" /DispName="MyService" "C:\path\to\BackgroundService\bin\x64\Release\BackgroundService.exe"
+ C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /ServiceName="MyService" /DispName="MyService" "C:\path\to\BackgroundService\bin\x64\Release\BackgroundService.exe"
代替手段:sc.exe を使ったサービス登録
今回の問題は InstallUtil.exe を使っていたために発生しました。
サービス登録は別の方法でも行えます。
例えば、sc.exe を使います。
sc.exe は Service Control Manager(SCM)を直接操作するコマンドです。
sc create MyService binPath= "C:\path\to\BackgroundService.exe" DisplayName= "MyService"
sc.exe は Windows のサービス管理機能を直接操作するコマンドであり、InstallUtil.exe のようにアセンブリをロードして処理するわけではありません。そのため、「アーキテクチャ(x86 / x64)の影響を受けない」メリットがあります。一方、InstallUtil.exe が提供する「インストール/アンインストール時のカスタムフック処理」は使えません。
そのため、
- 単純なサービス登録 →
sc.exe - インストール処理をコードで制御 →
InstallUtil.exe
で、使い分けるとよさそうです。
株式会社ラグザイア(luxiar.com)の技術広報ブログです。 ラグザイアはRuby on RailsとC#に特化した町田の受託開発企業です。フルリモートでの開発を積極的に推進しており、全国からの参加を可能にしています。柔軟な働き方で最新のソフトウェアソリューションを提供します。
Discussion