DLLとの間でオブジェクト、特にSTLオブジェクトを安全に渡すにはどうすればよいですか?


106

C ++ DLLとの間でクラスオブジェクト、特にSTLオブジェクトを渡すにはどうすればよいですか?

私のアプリケーションはDLLファイルの形式でサードパーティのプラグインとやり取りする必要があり、これらのプラグインがどのコンパイラでビルドされるかを制御できません。STLオブジェクトの保証されたABIがないことを承知しており、アプリケーションを不安定にすることを心配しています。


4
C ++標準ライブラリについて話している場合は、おそらくそれを呼び出す必要があります。STLは、コンテキストに応じて異なる意味を持つ可能性があります。(stackoverflow.com/questions/5205491/…も参照)
Micha Wiedenmann、2018年

回答:


156

この質問に対する簡単な答えは「しない」です。標準のC ++ ABI(アプリケーションバイナリインターフェイス、呼び出し規約の標準、データのパッキング/アラインメント、型のサイズなど)がないため、クラスを処理する標準的な方法を試行して適用するには、多くのフープを通過する必要があります。プログラム内のオブジェクト。これらのすべてのフープをジャンプした後で動作するという保証すらありません。また、1つのコンパイラリリースで機能するソリューションが次のリリースで機能するという保証もありません。

extern "C"C ABI 明確に定義され、安定しているため、を使用してプレーンなCインターフェースを作成するだけです。


本当に、本当に C ++オブジェクトをDLLの境界を越えて渡したい場合は、技術的には可能です。以下に、考慮しなければならないいくつかの要因を示します。

データのパッキング/調整

特定のクラス内では、個々のデータメンバーは通常メモリに特別に配置されるため、それらのアドレスは型のサイズの倍数に対応します。たとえば、intaは4バイト境界に揃えられます。

DLLがEXEとは異なるコンパイラーでコンパイルされている場合、特定のクラスのDLLのバージョンは、EXEのバージョンとは異なるパッキングを持っている可能性があるため、EXEがクラスオブジェクトをDLLに渡すと、DLLは適切にアクセスできない場合があります。そのクラス内の特定のデータメンバー。DLLは、EXEの定義ではなく、クラスの独自の定義で指定されたアドレスから読み取ろうとします。必要なデータメンバーが実際にそこに格納されていないため、ガベージ値が発生します。

#pragma packこれは、コンパイラーに特定のパッキングを適用するように強制するプリプロセッサーディレクティブを使用して回避できます。コンパイラーが選択したよりも大きなパック値を選択した場合でも、コンパイラーはデフォルトのパッキングを適用します。そのため、大きなパッキング値を選択した場合でも、クラス間でコンパイラー間で異なるパッキングが可能です。これを解決するには、を使用します#pragma pack(1)。これにより、コンパイラーはデータメンバーを1バイト境界に整列させます(基本的に、パッキングは適用されません)。パフォーマンスの問題や特定のシステムでのクラッシュを引き起こす可能性があるため、これは良いアイデアではありません。ただし、これにより、クラスのデータメンバーがメモリ内で整列される方法の一貫性保証されます。

メンバーの並べ替え

クラスがstandard-layoutではない場合、コンパイラはメモリ内のデータメンバーを再配置できます。これがどのように行われるかについての標準はないので、データの再配置はコンパイラ間の非互換性を引き起こす可能性があります。したがって、DLLとの間でデータをやり取りするには、標準レイアウトクラスが必要になります。

呼び出し規約

特定の関数が持つことができる複数の呼び出し規約があります。これらの呼び出し規約は、データが関数に渡される方法を指定します。パラメーターはレジスターまたはスタックに格納されますか?引数はスタックにプッシュされますか?関数が終了した後にスタックに残っている引数をクリーンアップするのは誰ですか?

標準の呼び出し規約を維持することが重要です。あなたがとしての機能を宣言した場合_cdeclC ++のデフォルト、および使用してそれを呼び出そうと_stdcall 悪いことが起こるのだろう_cdeclただし、C ++関数のデフォルトの呼び出し規約です。そのため_stdcall、ある場所でを指定し、_cdecl別の場所でを指定して意図的に解除しない限り、これは解除されません。

データ型サイズ

このドキュメントによると、Windowsでは、アプリが32ビットか64ビットかに関係なく、ほとんどの基本的なデータ型は同じサイズです。ただし、特定のデータ型のサイズは標準ではなくコンパイラーによって強制されるため(標準ではすべてが保証されます1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long))、固定サイズのデータ​​型を使用して、可能な場合はデータ型のサイズの互換性を確保することをお勧めします。

ヒープの問題

DLLがEXEと異なるバージョンのCランタイムにリンクしている場合、2つのモジュールは異なるヒープを使用します。これは、モジュールが異なるコンパイラーでコンパイルされている場合に特に起こりやすい問題です。

これを軽減するには、すべてのメモリを共有ヒープに割り当て、同じヒープから割り当てを解除する必要があります。幸い、Windowsはこれで助けにAPIを提供:GetProcessHeapはあなたがホストEXEのヒープにアクセスできるようになる、とます。HeapAlloc / HeapFreeは、あなたがこのヒープ内の割り当てと空きメモリようになります。あなたが通常使用しないことが重要であるmalloc/をfree、彼らはあなたが期待するように動作するという保証はありませんよう。

STLの問題

C ++標準ライブラリには、独自のABI問題があります。特定のSTLタイプがメモリ内で同じように配置される保証なく、特定のSTLクラスが実装間で同じサイズであることも保証されません(特に、デバッグビルドは追加のデバッグ情報を指定されたSTLタイプ)。したがって、STLコンテナーは、DLL境界を越えて渡され、反対側で再パックされる前に、基本タイプにアンパックされる必要があります。

名前マングリング

DLLはおそらく、EXEが呼び出す関数をエクスポートします。ただし、C ++コンパイラには、関数名をマングルする標準的な方法がありません。これは、名前GetCCDLLが付けられた関数_Z8GetCCDLLvがGCCおよび?GetCCDLL@@YAPAUCCDLL_v1@@XZMSVC で破損する可能性があることを意味します。

GCCで生成されたDLLは.libファイルを生成せず、MSVCでDLLを静的にリンクするにはDLLが必要であるため、DLLへの静的リンクを保証することはできません。動的リンクははるかにクリーンなオプションのように見えますが、名前のマングリングが邪魔になります。GetProcAddress間違ったマングル名にしようとすると、呼び出しが失敗し、DLLを使用できなくなります。これは回避するために少しハッカーを必要とし、DLLの境界を越えてC ++クラスを渡すことが悪い考えであるかなり主要な理由です。

DLLをビルドしてから、生成された.defファイルを確認する(ファイルが生成された場合、これはプロジェクトオプションによって異なります)か、Dependency Walkerなどのツールを使用して、破損した名前を見つけます。次に、独自の .defファイルを作成して、マングルされた関数へのマングルされていないエイリアスを定義する必要があります。例として、先ほどGetCCDLL触れた関数を使用してみましょう。私のシステムでは、次の.defファイルがそれぞれGCCおよびMSVCで機能します。

GCC:

EXPORTS
    GetCCDLL=_Z8GetCCDLLv @1

MSVC:

EXPORTS
    GetCCDLL=?GetCCDLL@@YAPAUCCDLL_v1@@XZ @1

DLLを再構築し、それがエクスポートする関数を再確認します。符号化されていない関数名がその中に含まれている必要があります。この方法ではオーバーロードされた関数を使用できないことに注意してください。マングルされていない関数名は、マングルされた名前で定義されている特定の関数オーバーロードのエイリアスです。また、関数の宣言を変更するたびに、DLLの新しい.defファイルを作成する必要があることに注意してください。マングルされた名前が変更されるためです。最も重要なのは、名前のマングリングをバイパスすることにより、非互換性の問題に関してリンカーが提供しようとしている保護を上書きすることです。

DLLのすべての関数のエイリアスを作成する必要がなく、エイリアスを定義する関数が1つしかないため、DLLのインターフェイス作成する場合、このプロセス全体がより簡単になります。ただし、同じ警告が引き続き適用されます。

クラスオブジェクトを関数に渡す

これはおそらく、クロスコンパイラーのデータ受け渡しを悩ます問題の中で最も微妙で最も危険なものです。他のすべてを処理する場合でも、引数が関数に渡される方法に標準はありません。これにより、明らかな理由もなく、簡単にデバッグできない方法で、微妙なクラッシュが発生する可能性があります。戻り値のバッファーを含め、すべての引数をポインター経由で渡す必要があります。これは不器用で不便であり、機能する場合と機能しない場合がある別のハックな回避策です。


これらのすべての回避策をまとめて、テンプレートと演算子を使用した創造的な作業に基づいて、DLLの境界を越えてオブジェクトを安全に渡そうとすることができます。#pragma packとそのバリアントのサポートと同様に、C ++ 11のサポートは必須です。MSVC 2013は、GCCおよびclangの最新バージョンと同様に、このサポートを提供します。

//POD_base.h: defines a template base class that wraps and unwraps data types for safe passing across compiler boundaries

//define malloc/free replacements to make use of Windows heap APIs
namespace pod_helpers
{
  void* pod_malloc(size_t size)
  {
    HANDLE heapHandle = GetProcessHeap();
    HANDLE storageHandle = nullptr;

    if (heapHandle == nullptr)
    {
      return nullptr;
    }

    storageHandle = HeapAlloc(heapHandle, 0, size);

    return storageHandle;
  }

  void pod_free(void* ptr)
  {
    HANDLE heapHandle = GetProcessHeap();
    if (heapHandle == nullptr)
    {
      return;
    }

    if (ptr == nullptr)
    {
      return;
    }

    HeapFree(heapHandle, 0, ptr);
  }
}

//define a template base class. We'll specialize this class for each datatype we want to pass across compiler boundaries.
#pragma pack(push, 1)
// All members are protected, because the class *must* be specialized
// for each type
template<typename T>
class pod
{
protected:
  pod();
  pod(const T& value);
  pod(const pod& copy);
  ~pod();

  pod<T>& operator=(pod<T> value);
  operator T() const;

  T get() const;
  void swap(pod<T>& first, pod<T>& second);
};
#pragma pack(pop)

//POD_basic_types.h: holds pod specializations for basic datatypes.
#pragma pack(push, 1)
template<>
class pod<unsigned int>
{
  //these are a couple of convenience typedefs that make the class easier to specialize and understand, since the behind-the-scenes logic is almost entirely the same except for the underlying datatypes in each specialization.
  typedef int original_type;
  typedef std::int32_t safe_type;

public:
  pod() : data(nullptr) {}

  pod(const original_type& value)
  {
    set_from(value);
  }

  pod(const pod<original_type>& copyVal)
  {
    original_type copyData = copyVal.get();
    set_from(copyData);
  }

  ~pod()
  {
    release();
  }

  pod<original_type>& operator=(pod<original_type> value)
  {
    swap(*this, value);

    return *this;
  }

  operator original_type() const
  {
    return get();
  }

protected:
  safe_type* data;

  original_type get() const
  {
    original_type result;

    result = static_cast<original_type>(*data);

    return result;
  }

  void set_from(const original_type& value)
  {
    data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type))); //note the pod_malloc call here - we want our memory buffer to go in the process heap, not the possibly-isolated DLL heap.

    if (data == nullptr)
    {
      return;
    }

    new(data) safe_type (value);
  }

  void release()
  {
    if (data)
    {
      pod_helpers::pod_free(data); //pod_free to go with the pod_malloc.
      data = nullptr;
    }
  }

  void swap(pod<original_type>& first, pod<original_type>& second)
  {
    using std::swap;

    swap(first.data, second.data);
  }
};
#pragma pack(pop)

podクラスは、すべての基本的なデータ型に特化されているので、それはint自動的に折り返されますint32_tuintに包まれるuint32_tなど、これはすべて、舞台裏で過負荷状態のおかげで起こる=()演算子を。基礎となるデータ型を除いてほぼ完全に同じであるため、基本的な型の特殊化の残りは省略しました(bool特殊化には少し余分なロジックがあり、aに変換され、int8_t次にint8_t0と比較されて、bool、しかしこれはかなり簡単です)。

この方法でSTLタイプをラップすることもできますが、少し余分な作業が必要です。

#pragma pack(push, 1)
template<typename charT>
class pod<std::basic_string<charT>> //double template ftw. We're specializing pod for std::basic_string, but we're making this specialization able to be specialized for different types; this way we can support all the basic_string types without needing to create four specializations of pod.
{
  //more comfort typedefs
  typedef std::basic_string<charT> original_type;
  typedef charT safe_type;

public:
  pod() : data(nullptr) {}

  pod(const original_type& value)
  {
    set_from(value);
  }

  pod(const charT* charValue)
  {
    original_type temp(charValue);
    set_from(temp);
  }

  pod(const pod<original_type>& copyVal)
  {
    original_type copyData = copyVal.get();
    set_from(copyData);
  }

  ~pod()
  {
    release();
  }

  pod<original_type>& operator=(pod<original_type> value)
  {
    swap(*this, value);

    return *this;
  }

  operator original_type() const
  {
    return get();
  }

protected:
  //this is almost the same as a basic type specialization, but we have to keep track of the number of elements being stored within the basic_string as well as the elements themselves.
  safe_type* data;
  typename original_type::size_type dataSize;

  original_type get() const
  {
    original_type result;
    result.reserve(dataSize);

    std::copy(data, data + dataSize, std::back_inserter(result));

    return result;
  }

  void set_from(const original_type& value)
  {
    dataSize = value.size();

    data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type) * dataSize));

    if (data == nullptr)
    {
      return;
    }

    //figure out where the data to copy starts and stops, then loop through the basic_string and copy each element to our buffer.
    safe_type* dataIterPtr = data;
    safe_type* dataEndPtr = data + dataSize;
    typename original_type::const_iterator iter = value.begin();

    for (; dataIterPtr != dataEndPtr;)
    {
      new(dataIterPtr++) safe_type(*iter++);
    }
  }

  void release()
  {
    if (data)
    {
      pod_helpers::pod_free(data);
      data = nullptr;
      dataSize = 0;
    }
  }

  void swap(pod<original_type>& first, pod<original_type>& second)
  {
    using std::swap;

    swap(first.data, second.data);
    swap(first.dataSize, second.dataSize);
  }
};
#pragma pack(pop)

これで、これらのポッドタイプを利用するDLLを作成できます。最初にインターフェースが必要なので、マングリングを理解する方法は1つだけです。

//CCDLL.h: defines a DLL interface for a pod-based DLL
struct CCDLL_v1
{
  virtual void ShowMessage(const pod<std::wstring>* message) = 0;
};

CCDLL_v1* GetCCDLL();

これは、DLLと呼び出し元の両方が使用できる基本的なインターフェースを作成するだけです。ポインタをそれ自体podではなくに渡していることに注意してくださいpod。これをDLL側に実装する必要があります。

struct CCDLL_v1_implementation: CCDLL_v1
{
  virtual void ShowMessage(const pod<std::wstring>* message) override;
};

CCDLL_v1* GetCCDLL()
{
  static CCDLL_v1_implementation* CCDLL = nullptr;

  if (!CCDLL)
  {
    CCDLL = new CCDLL_v1_implementation;
  }

  return CCDLL;
}

そして今、ShowMessage関数を実装しましょう:

#include "CCDLL_implementation.h"
void CCDLL_v1_implementation::ShowMessage(const pod<std::wstring>* message)
{
  std::wstring workingMessage = *message;

  MessageBox(NULL, workingMessage.c_str(), TEXT("This is a cross-compiler message"), MB_OK);
}

それほど凝ったものは何もありません。これは単に渡さpodれたものをノーマルにコピーwstringしてメッセージボックスに表示するだけです。結局、これは単なるPOCであり、完全なユーティリティライブラリではありません。

これでDLLをビルドできます。リンカの名前のマングリングを回避するための特別な.defファイルを忘れないでください。(注:実際にビルドして実行したCCDLL構造体には、ここに示すものよりも多くの関数がありました。.defファイルは期待どおりに機能しない可能性があります。)

次に、EXEがDLLを呼び出すようにします。

//main.cpp
#include "../CCDLL/CCDLL.h"

typedef CCDLL_v1*(__cdecl* fnGetCCDLL)();
static fnGetCCDLL Ptr_GetCCDLL = NULL;

int main()
{
  HMODULE ccdll = LoadLibrary(TEXT("D:\\Programming\\C++\\CCDLL\\Debug_VS\\CCDLL.dll")); //I built the DLL with Visual Studio and the EXE with GCC. Your paths may vary.

  Ptr_GetCCDLL = (fnGetCCDLL)GetProcAddress(ccdll, (LPCSTR)"GetCCDLL");
  CCDLL_v1* CCDLL_lib;

  CCDLL_lib = Ptr_GetCCDLL(); //This calls the DLL's GetCCDLL method, which is an alias to the mangled function. By dynamically loading the DLL like this, we're completely bypassing the name mangling, exactly as expected.

  pod<std::wstring> message = TEXT("Hello world!");

  CCDLL_lib->ShowMessage(&message);

  FreeLibrary(ccdll); //unload the library when we're done with it

  return 0;
}

そして、これが結果です。私たちのDLLは動作します。過去のSTL ABIの問題、過去のC ++ ABIの問題、過去のマングリングの問題に成功し、MSVC DLLはGCC EXEで動作しています。

あとで結果を表示している画像。


結論として、DLLの境界を越えてC ++オブジェクトを渡さなければならない場合、これがその方法です。ただし、これは、ご使用のセットアップまたは他の誰かとの動作が保証されているものではありません。これはいつでも壊れる可能性があり、ソフトウェアがメジャーリリースされる予定の前日に壊れる可能性があります。この道はハッキング、リスク、そして私がおそらく狙われるべき一般的な馬鹿さでいっぱいです。このルートを使用する場合は、十分に注意してテストしてください。そして、本当に...これをまったく行わないでください。


1
うーん、悪くない!標準のC ++型を使用してWindows DLLとやり取りすることに対してかなり良い引数のコレクションを引き出し、それに応じてタグ付けしました。これらの特定のABI制限は、MSVC以外のツールチェーンには適用されません。これはさえ...言及されるべきである
πάνταῥεῖ

12
@DavidHeffernanそうですね。しかし、これは私にとって数週間の研究の結果なので、私が学んだことを文書化して、他の人が同じ研究を行ったり、実用的なソリューションをハッキングしようとする同じ試みをしたりする必要がないようにすることは価値があると思いました。これは、この辺りでは半ばよくある質問のようです。
cfはモニカの

@πάνταῥεῖ これらの特定のABI制限は、MSVC以外の他のツールチェーンには適用されませんこれについても言及する必要があります...これが正しく理解されているかどうかはわかりません。これらのABIの問題はMSVCに限定されていることを示していますか?たとえば、clangでビルドされたDLLはGCCでビルドされたEXEで正常に動作しますか?それは私のすべての研究に矛盾しているように見えるので、私は少し混乱しています...
cfは、モニカの

@computerfreakerありません私は... PEとELFが異なるABIフォーマットを使用していることを言っている
πάνταῥεῖ

3
@computerfreakerほとんどの主要なC ++コンパイラ(GCC、Clang、ICC、EDGなど)は、Itanium C ++ ABIに準拠しています。MSVCはサポートしていません。つまり、これらのABIの問題は、MSVCに固有のものですが、排他的ではありません。UnixプラットフォームのCコンパイラでさえ(そして同じコンパイラの異なるバージョンでさえ!)、相互運用性は完全ではありません。ただし、通常は十分に近いため、ClangでビルドされたDLLをGCCでビルドされた実行可能ファイルと正常にリンクできたとしても、驚くことはありません。
Stuart Olsen、

17

@computerfreakerは、型定義がユーザー制御下にあり、両方のプログラムでまったく同じトークンシーケンスが使用されている場合でも、ABIの欠如が一般的なケースでDLL境界を越えてC ++オブジェクトを渡せない理由を説明しています。(動作する2つのケースがあります:標準レイアウトクラスと純粋なインターフェイス)

C ++標準で定義されたオブジェクト型(標準テンプレートライブラリから変更されたものを含む)の場合、状況ははるかに悪化します。C ++標準は完全な型定義を提供せず、最小要件のみを提供するため、これらの型を定義するトークンは複数のコンパイラで同じではありません。さらに、これらの型定義に現れる識別子の名前ルックアップは、同じものを解決しません。 C ++ ABIが存在するシステムでも、モジュールの境界を越えてそのような型を共有しようとすると、One Definition Rule違反が原因で、未定義の大規模な動作が発生します。

これはLinuxプログラマーが慣れていないことです。g++のlibstdc ++は事実上の標準であり、事実上すべてのプログラムがそれを使用しており、ODRを満たしています。clangのlibc ++はその前提を破り、C ++ 11はほぼすべての標準ライブラリタイプに対する必須の変更を伴いました。

モジュール間で標準ライブラリタイプを共有しないでください。これは未定義の動作です。


16

ここでの回答の一部は、C ++クラスを渡すことを非常に恐ろしく聞こえるようにしますが、別の見方を共有したいと思います。他の応答のいくつかで言及されている純粋な仮想C ++メソッドは、実際には思ったよりもクリーンであることがわかります。私はこのコンセプトに基づいてプラグインシステム全体を構築しましたが、それは何年も非常にうまく機能しています。LoadLib()とGetProcAddress()を使用して指定されたディレクトリから動的にdllをロードする「PluginManager」クラスがあります(Linuxの同等のもので、実行可能ファイルをクロスプラットフォームにするため)。

信じられないかもしれませんが、この方法は、純粋な仮想インターフェイスの最後に新しい関数を追加し、その新しい関数なしでインターフェイスに対してコンパイルされたdllをロードするなど、風変わりなことを行っても許容されます-それらは正常にロードされます。もちろん...バージョン番号をチェックして、実行可能ファイルが関数を実装する新しいdllの新しい関数のみを呼び出すことを確認する必要があります。しかし、良いニュースは次のとおりです。つまり、ある意味で、時間の経過とともにインターフェースを進化させる大まかな方法​​があります。

純粋な仮想インターフェースのもう1つの優れた点は、必要な数のインターフェースを継承でき、ひし形の問題に遭遇することは決してないということです。

このアプローチの最大の欠点は、パラメーターとして渡す型に非常に注意する必要があることです。クラスまたはSTLオブジェクトは、最初にそれらを純粋な仮想インターフェースでラップしない限り、存在しません。構造体はありません(プラグマパックブードゥーを通過しない場合)。原始的な型と他のインターフェースへのポインタだけです。また、関数をオーバーロードすることはできません。これは不便ですが、表示ストッパーではありません。

良いニュースは、ほんの数行のコードで、再利用可能なジェネリッククラスとインターフェイスを作成して、STL文字列、ベクター、およびその他のコンテナークラスをラップできることです。または、GetCount()やGetVal(n)などの関数をインターフェイスに追加して、リストをループさせることもできます。

私たちのためにプラグインを構築している人々は、それを非常に簡単に見つけます。彼らはABIの境界などの専門家である必要はありません。関心のあるインターフェイスを継承し、サポートしている関数をコード化し、そうでない場合はfalseを返します。

これらすべてを機能させるテクノロジーは、私の知る限り、どの標準にも基づいていません。私が収集したものから、Microsoftは仮想テーブルをそのようにしてCOMを作成できるようにし、他のコンパイラライターもそれに倣うことにしました。これには、GCC、Intel、Borland、およびその他のほとんどの主要なC ++コンパイラが含まれます。あいまいな組み込みコンパイラの使用を計画している場合、このアプローチはおそらく機能しません。理論的には、どのコンパイラ企業もいつでも仮想テーブルを変更して事態を打破する可能性がありますが、このテクノロジーに依存する何年にもわたって書かれた膨大な量のコードを考えると、主要企業のいずれかが順位を破ることを決定した場合、私は非常に驚きます。

ですから、話の教訓は...いくつかの極端な状況を除いて、ABI境界がプリミティブ型でクリーンな状態を保ち、過負荷を回避できることを確認できるインターフェースの担当者が1人必要です。その規定に問題がなければ、コンパイラ間でDLL / SO内のクラスへのインターフェイスを共有することを恐れません。クラスを直接共有する==問題がありますが、純粋な仮想インターフェースを共有することはそれほど悪くありません。


それは良い点です...私は「クラスへのインターフェースを共有することを恐れないでください」と言うべきでした。回答を編集します。
Ph0t0n 2017

2
ねえ、それは素晴らしい答えです、ありがとう!私の意見でそれをさらに良くするのは、あなたが言及していることのいくつかの例(またはいくつかのコード)を示す、さらに読むためのリンクです(たとえば、STLクラスをラップするためなど)。それ以外の場合は、この答えですが、これらのものが実際にどのように表示され、どのように検索するかについて少し迷っています。
Ela782

8

すべてのモジュール(.EXEと.DLL)が同じC ++コンパイラバージョンと同じ設定とCRTのフレーバーでビルドされていない限り、DLLの境界を越えてSTLオブジェクトを安全に渡すことはできません。

DLLからオブジェクト指向のインターフェイスを公開する場合は、C ++の純粋なインターフェイス(COMと同様)を公開する必要があります。CodeProjectに関するこの興味深い記事を読んでみてください。

ハウツー:DLLからC ++クラスをエクスポートする

DLL境界で純粋なCインターフェイスを公開し、呼び出し元のサイトでC ++ラッパーを構築することも検討してください。
これはWin32で発生するものに似ています。Win32実装コードはほとんどC ++ですが、多くのWin32 APIは純粋なCインターフェイスを公開しています(COMインターフェイスを公開するAPIもあります)。次に、ATL / WTLおよびMFCは、これらの純粋なCインターフェイスをC ++クラスおよびオブジェクトでラップします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.