8

たとえば、winsock ライブラリは、ビジュアル スタジオのすべてのバージョンでうまく機能します。しかし、すべてのバージョンで一貫したバイナリを提供するのに本当に苦労しています。VS 2005 でコンパイルされた dll は、2008 年に作成されたアプリケーションにリンクすると機能しません。2k5 と 2k8 の両方を SP1 にアップグレードしましたが、結果はあまり変わりませんでした。それはうまくいきます。しかし、これを C# アプリに含めると、C# アプリはアクセス違反エラーを受け取りますが、従来の C++ アプリケーションでは問題なく動作します。

dll を提供するときに知っておくべき戦略はありますか?

4

3 に答える 3

12

まず、DLL の境界を越えてプレーンな古いデータ以外のものを渡さないでください。つまり、構造体は問題ありません。クラスはそうではありません。次に、所有権が譲渡されていないことを確認します。つまり、dll の境界を越えて渡された構造体は、dll の外部で決して割り当て解除されません。したがって、dll が X* GetX() 関数をエクスポートする場合、対応する FreeX(X*) タイプの関数があり、割り当てられた同じランタイムが割り当て解除を担当することが保証されます。

次へ: DLL を取得して、静的ランタイムにリンクします。複数のサード パーティからの DL を含むプロジェクトをまとめて、それぞれがリンクされ、異なるランタイムを想定しており、アプリが期待するランタイムとは異なる可能性があるため、インストーラー ソフトウェアに 7.0、7.1、8.0、および 9.0 のランタイムをインストールするよう強制する可能性があります。そのうちのいくつかは、問題が発生する場合と発生しない場合がある別のサービス パックに存在します。親切に - dll プロジェクトを静的にリンクしてください。

-- 編集: このアプローチでは、C++ クラスを直接エクスポートすることはできません。モジュール間でクラス定義を共有するということは、コンパイラまたはコンパイラのバージョンが異なれば、装飾された名前が異なる方法で生成されるため、同種のラン​​タイム環境が必要になることを意味します。

代わりに COM スタイル インターフェイスとしてクラスをエクスポートすることで、この制限を回避できます。つまり、ランタイムに依存しない方法でクラスをエクスポートすることはできませんが、宣言することで簡単に作成できる「インターフェイス」をエクスポートできます。純粋仮想関数のみを含むクラス...

  struct IExportedMethods {
    virtual long __stdcall AMethod(void)=0;
  };
  // with the win32 macros:
  interface IExportedMethods {
    STDMETHOD_(long,AMethod)(THIS)PURE;
  };

クラス定義では、次のインターフェイスから継承します。

  class CMyObject: public IExportedMethods { ...

C ファクトリ メソッドを作成することで、次のようなインターフェイスをエクスポートできます。

  extern "C" __declspec(dllexport) IExportedClass* WINAPI CreateMyExportedObject(){
    return new CMyObject; 
  }

これは、コンパイラ バージョンとランタイムに依存しないクラス バージョンをエクスポートする非常に軽量な方法です。これらのいずれかを削除することはできません。dll またはインターフェイスのメンバーとしてリリース関数を含める必要があります。インターフェイスのメンバーとして、次のようになります。

  interface IExportedMethods {
    STDMETHOD_(void) Release(THIS) PURE; };
  class CMyObject : public IExportedMethods {
    STDMETHODIMP_(void) Release(){
      delete this;
    }
  };

このアイデアを採用して、さらに実行することができます。IUnknown からインターフェイスを継承し、ref カウントの AddRef メソッドと Release メソッドを実装し、v2 インターフェイスまたはその他の機能の QueryInterface を実行できます。最後に、DllCreateClassObject を使用してオブジェクトを作成し、必要な COM 登録を行います。これはすべてオプションですが、C 関数を介してアクセスする単純なインターフェイス定義で簡単に解決できます。

于 2008-10-24T09:56:30.270 に答える
6

私は Chris Becke の見解には同意しませんが、彼のアプローチの利点を理解しています。

欠点は、ライブラリ間での共有が禁止されているため、ユーティリティ オブジェクトのライブラリを作成できないことです。

Chris のソリューションの拡張

VS バージョン間で一貫した dll バイナリを作成する方法は?

どちらを選択するかは、コンパイラがどれだけ異なるかによって異なります。一方では、同じコンパイラの異なるバージョンが同じ方法でデータの配置を処理できるため、DLL 間で構造体とクラスを公開できます。反対に、他のライブラリ コンパイラやコンパイル オプションを信用できない可能性があります。

Windows Win32 API では、「ハンドラ」を介して問題を処理しました。次の方法で同じことを行います。

1 - 構造体を公開しません。ポインターのみを公開する(つまり、void * ポインター)
2 - この構造体データのアクセスは、ポインターを最初のパラメーターとして受け取る関数を介して行われます
3 - この構造体のポインターの割り当て/割り当て解除データは、関数を介して行われます

このようにして、構造体が変更されたときにすべてを再コンパイルすることを避けることができます。

これを行う C++ の方法は PImpl です。http://en.wikipedia.org/wiki/Opaque_pointerを参照してください

上記の void * 概念と同じ動作をしますが、PImpl を使用すると、RAII とカプセル化の両方を使用でき、強い型の安全性から利益を得ることができます。これには互換性のある装飾 (同じコンパイラー) が必要ですが、同じランタイムまたはバージョン (バージョン間で装飾が同じである場合) は必要ありません。

別の解決策は?

異なるコンパイラ/コンパイラ バージョンの DLL を混ぜ合わせることを期待することは、(質問で説明したように) 災害のレシピであるか、基本的な C コーディングにフォールバックするためにコードのほとんど (すべてではないにしても) の C++ ソリューションを手放したため退屈です。 、 または両方。

私の解決策は次のとおりです。

1 - すべてのモジュールが同じコンパイラ/バージョンでコンパイルされていることを確認してください。限目。
2 - すべてのモジュールが同じランタイムと動的にリンクするようにコンパイルされている
ことを確認してくださいVS バージョン間で一貫性のある dll バイナリを作成する方法の Chris Becke による当然のことです。.

アプリケーションのすべてのモジュールを同じコンパイラと同じバージョンのコンパイラに対してコンパイルすることを義務付けるのは、驚くべきことでも法外なことでもないことに注意してください。

コンパイラを混在させることが良いことだと誰にも言わせないでください。そうではない。コンパイラを混在させる自由は、ほとんどの人にとって、建物の上から飛び降りることで享受できるのと同じ種類の自由です。自由にそうすることができますが、通常はそれを望まないだけです。

私のソリューションにより、次のことが可能になります。

1 - クラスをエクスポートし、実際のキャストされていないC++ ライブラリを作成します (たとえば、Visual C++ の __declspec(dllexport) で行う必要があるように)
2 -割り当ての所有権を譲渡します (割り当てと/またはインラインコードまたは STL の割り当て解除)
3 -各モジュールが独自のバージョンのランタイムを持っているという事実に関連する問題に悩まされないでください(つまり、メモリ割り当て、および C または C++ API によって使用される一部のグローバルデータ)。

これは、モジュールのデバッグ バージョンを他のモジュールのリリース バージョンと混在させてはならないことを意味することに注意してください。アプリは完全にデバッグ中か、完全にリリース中です。

于 2008-10-24T10:40:31.443 に答える
2

構造を渡す問題に関しては、次のように構造を調整する限り、これは安全です。


#pragma pack(push,4)
typedef myStruct {
  int a;
  char b;
  float c;
}myStruct;
#pragma pack(pop)

この宣言をヘッダー ファイルに入れ、両方のプロジェクトに含めることができます。このようにして、構造を渡す際に問題が発生することはありません。

また、ランタイム ライブラリと静的にリンクしていることを確認し、モジュールでメモリを割り当て (ptr=malloc(1024))、別のモジュールでそのメモリを解放する (free(ptr)) などを試みないでください。

于 2008-10-24T11:48:31.327 に答える