31

私は、オープン ソース ライブラリを利用していくつかの面倒な作業を行うコードをいくつか書きました。この作業は、単体テストと cmake を使用して Linux で行われ、Windows への移植に役立ちました。両方のプラットフォームで実行する必要があります。

私は Linux と cmake が好きで、ビジュアル スタジオ ファイルが自動的に生成されるのが好きです。今のところ、Windows ではすべてがコンパイルされ、リンクされ、テスト実行可能ファイルが生成されます。

ただし、この時点に到達するには、マニフェスト ファイルと再頒布可能パッケージについてすべてを学びながら、数日間 Windows と格闘しなければなりませんでした。

私の理解が進む限り:

VS 2005 では、Microsoft は Side By Side DLL を作成しました。これの動機は、以前は複数のアプリケーションが同じ dll の異なるバージョンをインストールし、以前にインストールされて動作していたアプリケーションがクラッシュする (つまり、「Dll Hell」) ことです。サイド バイ サイド DLL はこれを修正します。これは、どのバージョンを実行するかを指定する「マニフェスト ファイル」が各実行可能ファイル/DLL に追加されるようになったためです。

これはすべて順調です。アプリケーションが不可解にクラッシュすることはなくなりました。でも...

Microsoft は、Visual Studio のリリースごとに新しいシステム dll のセットをリリースしているようです。また、前述したように、私はサードパーティのライブラリにリンクしようとしている開発者です。多くの場合、これらは「プリコンパイル済み dll」として配布されます。では、あるバージョンの Visual Studio でコンパイルされたプリコンパイル済み DLL が、別のバージョンの Visual Studio を使用するアプリケーションにリンクされるとどうなるでしょうか。

私がインターネットで読んだことによると、悪いことが起こります。幸いなことに、私はそこまで到達することはありませんでした.実行可能ファイルを実行しているときに「MSVCR80.dllが見つかりません」という問題が発生し続けたため、このマニフェスト全体の問題への進出を開始しました.

最終的に、これを機能させる唯一の方法 (すべてを静的にリンクする以外に) は、すべてのサードパーティ ライブラリを同じバージョンの Visual Studio を使用してコンパイルする必要があるという結論に達しました。つまり、プリコンパイルされた dll を使用しないでください。新しいdllを構築し、代わりにそれを使用してください。

これは実際に本当ですか?私は何か見落としてますか?

さらに、これが事実であると思われる場合、Microsoft は悪意のある理由で意図的にこれを行ったと考えずにはいられません。

すべてのプリコンパイル済みバイナリが壊れて、プリコンパイル済みバイナリの使用が不必要に難しくなるだけでなく、サード パーティの独自ライブラリを使用するソフトウェア会社で働いている場合、彼らがビジュアル スタジオの最新バージョンにアップグレードするたびに、会社は必ず同じことをしないと、コードが実行されなくなります。

余談ですが、Linuxはこれをどのように回避しますか? 私はそれで開発することを好み、リンクの仕組みを理解していると言いましたが、この種の低レベルの共有ライブラリのバージョン管理の問題に遭遇するほど長い間アプリケーションを維持していません。

最後に、要約すると、この新しいマニフェスト スキームでプリコンパイル済みバイナリを使用することは可能ですか? もしそうなら、私の間違いは何ですか?そうでない場合、Microsoft は、これによりアプリケーション開発が容易になると正直に考えていますか?

更新 - より簡潔な質問: Linux はどのようにしてマニフェスト ファイルの使用を回避していますか?

4

4 に答える 4

27

アプリケーション内のすべてのコンポーネントは、同じランタイムを共有する必要があります。そうでない場合、delete ステートメントでアサートするなどの奇妙な問題に遭遇します。

これは、すべてのプラットフォームで同じです。これは Microsoft が発明したものではありません。

この「ランタイムが 1 つしかない」という問題は、ランタイムがどこに影響を与える可能性があるかを認識することで回避できます。これは主に、あるモジュールでメモリを割り当て、別のモジュールで解放する場合です。

a.dll
    dllexport void* createBla() { return malloc( 100 ); }

b.dll
    void consumeBla() { void* p = createBla(); free( p ); }

a.dll と b.dll が異なる rumtimes にリンクされている場合、ランタイム関数が独自のヒープを実装するため、クラッシュします。

この問題は、メモリを解放するために呼び出す必要がある destroyBla 関数を提供することで簡単に回避できます。

ランタイムで問題が発生する可能性があるポイントがいくつかありますが、ほとんどはこれらの構成をラップすることで回避できます。

参考のため :

  • モジュールの境界を越えてメモリ/オブジェクトを割り当て/解放しないでください
  • dll インターフェイスで複雑なオブジェクトを使用しないでください。(例: std::string, ...)
  • dll の境界を越えて精巧な C++ メカニズムを使用しないでください。(typeinfo、C++ 例外、...)
  • ...

しかし、これはマニフェストの問題ではありません。

マニフェストには、モジュールで使用されるランタイムのバージョン情報が含まれており、リンカーによってバイナリ (exe/dll) に埋め込まれます。アプリケーションが読み込まれ、その依存関係が解決されると、ローダーは exe ファイルに埋め込まれたマニフェスト情報を調べ、WinSxS フォルダーからランタイム dll の適切なバージョンを使用します。ランタイムまたはその他のモジュールを単に WinSxS フォルダーにコピーすることはできません。Microsoft が提供するランタイムをインストールする必要があります。Microsoft が提供する MSI パッケージがあり、テスト/エンド ユーザー マシンにソフトウェアをインストールするときに実行できます。

したがって、アプリケーションを使用する前にランタイムをインストールすると、「依存関係がありません」というエラーが発生しなくなります。


(「Linux はマニフェスト ファイルの使用をどのように回避しますか」という質問に更新されました)

マニフェスト ファイルとは

マニフェスト ファイルは、既存の実行可能ファイル/ダイナミック リンク ライブラリの隣に明確な情報を配置するか、このファイルに直接埋め込むために導入されました。

これは、アプリの起動/依存関係の読み込み時に読み込まれる dll の特定のバージョンを指定することによって行われます。

(マニフェスト ファイルでできることは他にもいくつかあります。たとえば、いくつかのメタデータをここに入れることができます)

なぜこれが行われるのですか?

歴史的な理由により、バージョンは dll 名の一部ではありません。したがって、「comctl32.dll」は、すべてのバージョンでこのように命名されています。(そのため、Win2k の comctl32 は、XP または Vista のものとは異なります)。本当に必要な (そしてテスト済みの) バージョンを指定するには、バージョン情報を "appname.exe.manifest" ファイルに配置します (またはこのファイル/情報を埋め込みます)。

なぜこのようにしたのですか?

多くのプログラムは、dll を systemrootdir の system32 ディレクトリにインストールしました。これは、共有ライブラリのバグ修正をすべての依存アプリケーションに簡単に展開できるようにするために行われました。また、メモリが限られていた時代には、複数のアプリケーションが同じライブラリを使用する場合、共有ライブラリによってメモリ フットプリントが削減されました。

この概念は、すべての dll をこのディレクトリにインストールしたときに、多くのプログラマによって悪用されました。共有ライブラリの新しいバージョンを古いバージョンで上書きすることがあります。ライブラリの動作が静かに変更され、依存するアプリケーションがクラッシュすることがありました。

これは、「アプリケーション ディレクトリ内のすべての dll を配布する」というアプローチにつながります。

なぜこれが悪かったのですか?

バグが発生すると、いくつかのディレクトリに散在するすべての dll を更新する必要がありました。(gdiplus.dll) 他のケースでは、これは不可能でした (Windows コンポーネント)

マニフェストアプローチ

このアプローチは、上記の問題をすべて解決します。DLL は、プログラマーが干渉しない中央の場所にインストールできます。ここで、dll を更新することができ (WinSxS フォルダー内の dll を更新することによって)、ローダーは「正しい」dll をロードします。(バージョンの一致は dll ローダーによって行われます)。

Linux にこのメカニズムがないのはなぜですか?

いくつかの推測があります。(これは本当に推測です...)

  • ほとんどのものはオープンソースであるため、バグ修正のために再コンパイルすることは対象ユーザーにとって問題ではありません
  • 「ランタイム」(gcc ランタイム) は 1 つしかないため、ランタイム共有/ライブラリ境界の問題はそれほど頻繁には発生しません。
  • 多くのコンポーネントはインターフェイス レベルで C を使用しており、適切に使用すればこれらの問題は発生しません。
  • ライブラリのバージョンは、ほとんどの場合、そのファイルの名前に埋め込まれています。
  • ほとんどのアプリケーションはライブラリに静的にバインドされているため、dll 地獄が発生することはありません。
  • これらの問題が発生しないように、GCC ランタイムは非常に安定した ABI に保たれています。
于 2009-02-26T02:00:47.893 に答える
3

サード パーティの DLL が解放する必要のあるメモリを割り当てている場合、その DLL はプリコンパイル済み DLL を出荷する際の主要なルールの 1 つに違反しています。まさにこの理由からです。

DLL がバイナリ形式でのみ出荷される場合は、DLL がリンクされているすべての再頒布可能コンポーネントも出荷する必要があり、そのエントリ ポイントは、異なるアロケータなどの潜在的なランタイム ライブラリ バージョンの問題から呼び出し元を分離する必要があります。彼らがそれらのルールに従っていれば、あなたが苦しむべきではありません。そうでない場合は、痛みや苦しみが生じるか、サードパーティの作成者に文句を言う必要があります。

于 2009-02-26T16:40:01.273 に答える
3

サード パーティの DLL がメモリを割り当て、それを解放する必要がある場合は、同じランタイム ライブラリが必要です。DLL に割り当て関数と割り当て解除関数がある場合は、問題ありません。

サードパーティの DLL がstdなどのコンテナを使用しvectorている場合、オブジェクトのレイアウトが完全に異なる可能性があるため、問題が発生する可能性があります。

物事を機能させることは可能ですが、いくつかの制限があります。上記の両方の問題に遭遇しました。

于 2009-02-26T15:09:08.357 に答える
1

最終的に、これを機能させる唯一の方法 (すべてを静的にリンクする以外に) は、すべてのサードパーティ ライブラリを同じバージョンの Visual Studio を使用してコンパイルする必要があるという結論に達しました。つまり、プリコンパイルされた dll を使用しないでください。新しいdllを構築し、代わりにそれを使用してください。

別の方法 (および私が働いている場所で使用しなければならない解決策) は、使用する必要があるすべてのサードパーティ ライブラリが同じコンパイラ バージョンでビルドされている (またはビルドされた状態で利用できる) 場合、そのバージョンを「そのまま」使用できるというものです。たとえば、VC6 を「使用しなければならない」というのは厄介なことですが、使用しなければならないライブラリがあり、そのソースが利用できない場合、それ以外の場合、残念ながら選択肢が制限されます。

……わかったから。:)

(私の仕事は Windows ではありませんが、ユーザーの観点から Windows で DLL と戦うこともありますが、特定のバージョンのコンパイラを使用し、すべてがビルドされたサードパーティ ソフトウェアのバージョンを取得する必要があります。ありがたいことに、すべてのベンダーはこの種のサポートを長年にわたって行ってきたため、かなり最新の状態を維持する傾向があります。)

于 2009-02-26T01:30:29.650 に答える