こんにちは、Japan Developer Support Core チームの松井です。
今回は、古い Visual C++ ランタイムによるバイナリ互換性の問題について、msvcp140.dll!mtx_do_lock でアクセス違反が発生する事象を一例として、確認すべきランタイムのバージョンと読み込み元を説明します。
はじめに
Visual C++ 2015 以降の v14 系ランタイムにはバイナリ互換性があります。ただし、アプリケーションを構成するコンポーネントのうち、最も新しいビルド ツールと同じか、それより新しい Visual C++ ランタイムを使用する必要があります。
この条件を満たさない構成では、バイナリ互換性に関するさまざまな問題が発生する可能性があります。本記事で扱う、新しい MSVC ツールセットでビルドしたバイナリが古い msvcp140.dll を読み込み、std::mutex の処理でアクセス違反が観測される事象は、その一例です。システムに新しい Visual C++ Redistributable がインストールされていても、アプリケーションや別のソフトウェアがローカル配置した古い DLL をプロセスが読み込んでいる可能性があります。
本記事は、Visual C++ 2015 以降の v14 系ランタイムを動的リンクするデスクトップ アプリケーションを対象としています。内容は 2026 年 8 月時点の情報に基づきます。
std::mutex で観測される事象の例
バイナリ互換性の条件を満たさない構成で発生する事象は、std::mutex の処理におけるアクセス違反に限定されません。ここでは、Visual Studio 2022 version 17.10 で行われた std::mutex コンストラクター実装が constexpr になった変更 に関連し、同バージョン以降でビルドしたプログラムで観測される事象を一例として説明します。Microsoft STL の Visual Studio 2022 17.10 変更履歴 では、この変更後のプログラムが文書化されたバイナリ互換性の制限に従っていない場合、mutex の内部処理で null 参照が発生する可能性があると注意喚起しています。
この制限を満たさない構成で問題が発生した例では、最初に std::mutex::lock などを呼び出した際に、次のような場所でアクセス違反例外が発生してアプリケーションが異常終了します。
1 | 0:000> knL |
上記は、Visual Studio 2026 でビルドした以下のコードを、Visual C++ 2015-2022 Redistributable の古いバージョンをインストールしたコンピューターで実行した場合のコールスタックになります。
1 |
|
ただし、このコール スタックだけでは原因を確定できません。まず、プロセスが実際に読み込んだ msvcp140.dll のパスとバージョンを確認してください。
バイナリ互換性の条件
Visual Studio のバージョン間の C++ バイナリ互換性 では、Visual Studio 2015 以降の v14 系でビルドしたバイナリについて、次の条件が示されています。
- 最終的なバイナリのリンクには、入力として使用するバイナリのうち最も新しいビルド ツールと同じか、それより新しいリンカーを使用する
- 実行時には、アプリケーションを構成するコンポーネントのうち最も新しいビルド ツールと同じか、それより新しい Visual C++ Redistributable を使用する
/GLでコンパイルした静的ライブラリまたはオブジェクト ファイルを/LTCGで最終リンクする場合は、コンパイルと最終リンクに同じバージョンのビルド ツールを使用する
つまり、新しいランタイムで古いツールセットのバイナリを動かす後方互換性はありますが、その逆方向の互換性は保証されません。Visual Studio 2022 version 17.10 以降でビルドしたコンポーネントに、それより古い msvcp140.dll を組み合わせる構成は、この条件を満たしません。
Redistributable を更新しても古い DLL が読み込まれる理由
Visual C++ ランタイムには、主に次の配置方法があります。
- Visual C++ Redistributable を使用して、
System32やSysWOW64などへ中央配置する - ランタイム DLL をアプリケーションと同じフォルダーなどへローカル配置する
Microsoft は、サービス性の観点から Visual C++ Redistributable による中央配置を推奨 しています。ローカル配置も可能ですが、アプリケーションの提供元がランタイム DLL を更新する必要があるため、ローカル配置は推奨されていません。
非パッケージ デスクトップ アプリケーションの標準的な DLL 検索順序 では、実行ファイルのフォルダーがシステム フォルダーより先に検索されます。また、同名の DLL がすでにプロセスへ読み込まれている場合、その DLL が使用されることがあります。マニフェスト、Known DLL、明示的なパス、DLL 検索 API などを使用している場合、実際の検索動作は異なります。
このため、次のような構成では、システムにインストールした Redistributable の DLL とは異なる msvcp140.dll が使われる可能性があります。
- アプリケーションのフォルダーに古いランタイム DLL がある
- プロセスへ読み込まれるライブラリやセキュリティ ソフトウェアが、明示的なパスや変更した検索順序を使用して古いランタイム DLL を先に読み込んでいる
実際に読み込まれた DLL を確認する
インストール済みの Visual C++ Redistributable の一覧だけではなく、問題が発生するプロセスが読み込んだ DLL を確認します。調査前に、クラッシュが発生した時刻、対象プロセス、例外コード、コール スタックなどの情報も記録してください。
イベント ビューアーで確認する
Windows のイベント ビューアー で [Windows ログ] > [Application] を開き、Application Error、イベント ID 1000 を確認します。バイナリ互換性が保証されないバージョンの組み合わせによって生じる問題は多岐にわたりますが、上記で取り上げたケースでは、下記例のように障害モジュール名、障害モジュールのバージョン、障害モジュールのパス、例外コードが確認できます。
1 | 障害が発生しているアプリケーション名: MyApp1.exe、バージョン: 0.0.0.0、タイム スタンプ: 0x6a9517cc |
イベント ログはプロセスの終了後にも参照できるため、クラッシュ後に実際に読み込まれていた障害モジュールのパスとバージョンを事後確認できます。実行中のプロセスを確認する場合や詳細に調査する場合は、次のツールも利用できます。
Process Explorer で確認する
Process Explorer の DLL モードでは、選択したプロセスが読み込んでいる DLL を下部ペインに表示できます。
- 問題が発生するプロセスを選択します。
- 下部ペインを DLL モードに切り替えます。
msvcp140.dllを探し、完全なパスとバージョンを記録します。msvcp140_*.dllおよびvcruntime140*.dllなど他の Visual C++ ランタイム ファイルについても、異なる場所や古いバージョンから読み込まれていないか確認します。
ListDLLs で確認する
ListDLLs を使用すると、プロセス名またはプロセス ID を指定して、読み込まれた DLL の完全なパスとバージョン情報を確認できます。ListDLLs をダウンロードして展開し、展開先で実行してください。管理者としての実行が必要になる場合があります。たとえば、MyApp.exe について確認するコマンドは次のとおりです。
1 | .\Listdlls.exe -v MyApp.exe |
出力から Visual C++ ランタイム ファイルの完全なパスとバージョンを記録します。
プロセスが短時間でクラッシュして確認できない場合は、Process Monitor で対象プロセスの Load Image イベントを記録し、msvcp140.dll の読み込み元を確認する方法もあります。
確認結果に応じた対処
システム フォルダーの DLL が古い場合
対象 OS がシステム要件を満たすことを確認し、アプリケーションが必要とするアーキテクチャの 最新のサポート対象 Visual C++ Redistributable を、アプリケーションの提供元が示す要件に従ってインストールまたは修復します。対象 OS がシステム要件を満たさない場合は、アプリケーションの提供元へ対応方法を確認してください。
DLL ファイルだけを別の環境からコピーして置き換える方法は避けてください。Redistributable の正式なインストーラーを使用し、再起動を求められた場合は再起動後に確認します。
なお、v14 Redistributable は複数のアプリケーションが使用する共有コンポーネントであり、新しいバージョンが古いバージョンを置き換え、古い v14 ツールセットでビルドされたバイナリとの後方互換性を提供します。IT 管理者のお客様から「Visual C++ Redistributable が管理基準より新しいバージョンへ意図せず更新されたため古いバージョンへ戻したい」といったお問い合わせをいただくことがありますが、これを実施すると新しいツールセットでビルドされた別のアプリケーションの必要条件を満たさなくなる場合がありますので、安易にロールバックしないでください。
また、最新の Redistributable へ更新しても、他のソフトウェアやドライバーの更新またはインストールを契機に、予期せず古いバージョンへ置き換わるケースが報告されています。問題が再発する場合は、直前に行われた更新やインストールを確認し、該当する製品の提供元へ問い合わせてください。
アプリケーションのフォルダーから読み込まれている場合
アプリケーションの提供元へ、同梱されている Visual C++ ランタイムがビルドに使用したツールセット以上のバージョンか確認を依頼してください。利用者や IT 管理者の判断で DLL を削除、改名、または個別に差し替えると、別の依存関係を壊す可能性があります。提供元の更新プログラムまたは正式な修復手順を使用します。
ライブラリやセキュリティ ソフトウェアのフォルダーから読み込まれている場合
該当ソフトウェアを最新の状態に更新したうえで、開発元へ調査を依頼してください。問い合わせ時には、対象プロセス、クラッシュ時刻、読み込まれた DLL の完全なパスとバージョン、コール スタックを伝えます。
セキュリティ ソフトウェアの影響を切り分ける必要がある場合は、開発元へ手順を確認して一時的な無効化をお試しください。無効化だけでは完全に影響を取り除けない場合があるため、必要に応じて一時的にアンインストールすることもご検討ください。
開発者が配置時に確認すること
アプリケーション、ライブラリ、プロセスへ介入するソフトウェアの開発者は、原則として Visual C++ Redistributable による中央配置を検討してください。要件によってローカル配置を選択する場合は、次の点を確認します。
- ビルドに使用した最も新しいツールセット以上のランタイムを配置する
- 対象アーキテクチャと構成に必要な再頒布可能ファイル一式を、Visual Studio の再頒布可能ファイル ディレクトリから取得する
- アプリケーションやモジュールの更新時に、ローカル配置したランタイムも更新する
- 他のプロセスへモジュールを読み込ませる製品では、そのプロセスが使用するランタイムへ与える影響も検証する
- 単一の DLL だけを場当たり的に差し替えず、正式な配布セットを使用する
必要な DLL の判断については、再頒布する DLL の決定方法 を参照してください。
まとめ
- v14 系のバイナリ互換性を保つには、最も新しいビルド ツールと同じか、それより新しい Visual C++ ランタイムが必要です
- 古い
msvcp140.dllが読み込まれた構成におけるバイナリ互換性の問題の一例として、msvcp140.dll!mtx_do_lockのアクセス違反が観測される場合があります - イベント ID 1000 に障害モジュールとして記録されている場合はイベント ビューアーで、実行中または詳細調査では Process Explorer、ListDLLs、または Process Monitor で DLL のパスとバージョンを確認します
- ローカル配置した DLL が原因の場合、利用者が DLL を個別に差し替えず、該当ソフトウェアの開発元へ更新または修復方法を確認します
- 開発者は中央配置を優先し、ローカル配置する場合はランタイムを製品とともに継続して更新します
本記事が Visual C++ アプリケーションの問題調査に役立てば幸いです。
本ブログの内容は弊社の公式見解として保証されるものではなく、開発・運用時の参考情報としてご活用いただくことを目的としています。もし公式な見解が必要な場合は、弊社ドキュメント (https://learn.microsoft.com や https://support.microsoft.com) をご参照いただくか、もしくは私共サポートまでお問い合わせください。