syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

Visual Studio 2015とD3DCompiler

Visual Studio 2015でビルドした、DirectX 11.1を使ったMFC/WPF混合アプリ (.NET 4.5.2) をWindows 7エクスプローラーから起動すると、起動直後に勝手に終了する現象が出ました*1
しかし、終了時にWindowsからクラッシュエラーのタスクダイアログも表示されないし、イベントログにも何も記録が残りません。
Windows 8.1では普通に起動します。また、Windows 7でも、Visual Studio 2015 IDEからデバッグ実行した場合は普通に起動します。

調査

まずPowerShell経由で起動して、終了コードを取得してみました。

$result = Start-Process -FilePath "<DX11AppName>.exe" -PassThru -Wait
Write-Host $result.ExitCode.ToString("X8")

結果は「C0000135」となりました。
調べてみると、どうもこのコードはWindows OSの場合「Unknown Hard Error」で、アプリケーションの場合「Unable To Locate Component」らしいです。

DLL依存関係が怪しいと感じたのでDependency Walkerで調べてみたところ、D3DCompiler_47.dll勝手にリンクされていました。

Windows 7の場合、Visual Studio 2012や2013/2015をインストールしても、System32やSysWOW64にはD3DCompiler_46.dllやD3DCompiler_47.dllはインストールされません。これらは「DirectXエンドユーザーランタイム」にも含まれていません。
Windows SDKDirectX SDKが統合されたWindows SDK 8.0以降では、D3DCompilerのランタイムは

%ProgramFiles(x86)%\Windows Kits\8.0\bin\x86
%ProgramFiles(x86)%\Windows Kits\8.0\bin\x64
%ProgramFiles(x86)%\Windows Kits\8.1\bin\x86
%ProgramFiles(x86)%\Windows Kits\8.1\bin\x64
%ProgramFiles(x86)%\Windows Kits\10\bin\x86
%ProgramFiles(x86)%\Windows Kits\10\bin\x64

といった場所にインストールされます。スタンドアロンのHLSLシェーダーコンパイラーであるfxc.exeと同じ場所です。ただし、ARM版やARM64版のDLLは存在しないようです。
Visual Studio IDEからの起動時には、この場所にパスを通すのでDLL解決できるんですが、エクスプローラーからの起動時にはDLL解決できなくて強制終了させられる模様。

Windows 8の場合、D3DCompiler_46.dllは標準コンポーネントとしてシステムフォルダーにインストールされていません。また、Windows 8のストアアプリでは、D3DCompilerの使用が認められていませんでした。
Windows 8.1の場合、Windows 8.1版のD3DCompiler_47.dllが標準インストールされています。
Windows 10の場合、Windows 10版のD3DCompiler_47.dllが標準インストールされています。
また、Windows 8.1ストアアプリおよびWindows 10ユニバーサルWindowsプラットフォーム (UWP) アプリでは、D3DCompilerの使用が認められるようになりました。
このため、Windows 8.1あるいはWindows 10でD3DCompiler_47.dllを利用する場合、デスクトップアプリでもストアアプリでも、同DLLをアプリに同梱する必要はありません。

ちなみに、Windows SDK 8.1に含まれるD3DCompiler_47.dllのバージョンは「6.3.9600.16384」です。
一方、Windows SDK 10 (10.0.10240.0) に含まれるD3DCompiler_47.dllのバージョンは「10.0.10240.16384」です。
ファイルサイズも異なるし、SDK 10版のほうはDirectX 12/11.3 API対応(シェーダーモデル5.1対応)が追加されているので、前方互換性はありません。さらに、システムフォルダーにインストールされているD3DCompiler_47.dllは、Windows Updateか何かで更新されているらしく、SDK同梱版とは末尾のパッチ番号(リビジョン番号)が微妙に異なるようです。ていうかこれまで通り新しいWindows 10バージョンではファイル名のナンバリングを更新しろよ……

結論

DirectX 11を使ったデスクトップアプリケーションをビルドし、Windows 7/Windows 8.x向けにも配布する場合、DLL依存関係には十分注意したほうがよさそうです。
D3DCompiler_47.dllをリンクした場合、Windows SDK 8.1/10に含まれる同DLLをアプリケーションに同梱する必要があります。D3DCompilerランタイムはレジストリ登録を必要とするCOMライブラリではなく、ただのC言語形式関数がエクスポートされているだけのDLLみたいです。つまりシステムに影響を与えることなく、アプリごとのプライベートアセンブリとして同梱できます。
もちろんDirectX 12/11.3はWindows 7/Windows 8.1にはバックポートされないので、当然それらの機能を使った場合はアウトですが、DirectX 11.1までの機能しか使わず、またD3DCompilerを明示的にリンクしない場合でも、VS2015ではD3DCompilerが勝手にリンクされることがある模様。かなり迷惑な仕様変更です。ちなみにx86/x64版のD3DCompiler (lib/dll) はSDKに同梱されていますが、ARM/ARM64版に関しては*.libのみで、*.dllは同梱されていません。

通例、アーリーバインドしたDLLの解決ができない場合は、アプリ起動時に「DLLが見つからない」という旨のエラーメッセージが出るはずなんですが、今回はメッセージが表示されることなく勝手に終了してしまい、イベントも記録されなかったこと、そしてWindows 8.1には同名のD3DCompiler_47.dllが存在し、エクスプローラーから実行してもDLL解決だけはできるようになっていたことが、エラー原因の発見の遅れにつながりました。

2018-02-07追記:
GitHubに登録する前の過去のソースコード履歴を調べてみたところ、開発環境をちょうどVS2013からVS2015に更新する少し前に、D3DCompilerを使ってシェーダーリフレクション機能をテストするコードを書いていたんですが、その際にd3dcompiler.libを明示的にリンクしており、テストが終わってもそのまま無効化するのを忘れていたことが原因だったようです。少なくともVS2015の仕様変更ではなかった模様。お騒がせしました。普段からD3DXやD3DCompilerを使わないように注意していたんですが……
なお、.NET 4.7ではWPFがD3DCompiler_47.dllに依存するようになったらしく、Windows 7の場合は.NET 4.7本体をインストールする前に、KB4019990をインストールしておくか、最新のWindows Updateを適用しておく必要があるようです。依存コンポーネントがあるならば.NETのインストーラーが自動でインストールするべきだと思うんですが。また、.NET 4.7のWPFには潜在的な不具合が多々あるようです。.NET 4.7.1で問題が解消されているかどうかは不明です。

*1:Windows 7 SP1はIE10/IE11などと同時にインストールされるPlatform UpdateプログラムKB2670838を適用することで、DirectX 11.1 APIを使用できるようになります。