web-dev-qa-db-ja.com

このプログラムがクラッシュする理由:DLL間でstd :: stringを渡す

次のクラッシュ(MSVC9)の理由を理解するのに問題があります。

_//// the following compiles to A.dll with release runtime linked dynamically
//A.h
class A {
  __declspec(dllexport) std::string getString();
};
//A.cpp
#include "A.h"
std::string A::getString() {
   return "I am a string.";
}

//// the following compiles to main.exe with debug runtime linked dynamically
#include "A.h"
int main() {
   A a;
   std::string s = a.getString();
   return 0;
} // crash on exit
_

明らかに(?)これは、実行可能ファイルとDLLのメモリモデルが異なるためです。 A::getString()が返す文字列がA.dllで割り当てられ、main.exeで解放されている可能性がありますか?

もしそうなら、なぜ-そしてDLL(または実行可能ファイル)間で文字列を渡すための安全な方法は何でしょうか?カスタム削除機能でshared_ptrのようなラッパーを使用しない。

26
msi

これは実際にはヒープ実装の違いが原因ではありません-MSVC std :: string実装は、小さい文字列に動的に割り当てられたメモリを使用しません(小さい文字列の最適化を使用します)。 CRTは一致する必要がありますが、今回はそれが問題ではありません。

何が起こっているのかというと、1つの定義規則に違反して未定義の動作を呼び出しているということです。

リリースビルドとデバッグビルドでは、異なるプリプロセッサフ​​ラグが設定され、_std::string_の定義はそれぞれ異なることがわかります。コンパイラにsizeof(std::string)とは何かを尋ねます-MSVC10は、デバッグビルドでは32、リリースビルドでは28であると教えてくれます(これはパディングではありません-28と32は両方とも4バイトの境界です)。

では、何が起こっているのでしょうか。変数sは、コピーコンストラクターのデバッグバージョンを使用して初期化され、_std::string_のリリースバージョンをコピーします。メンバー変数のオフセットはバージョン間で異なるため、ガベージをコピーします。 MSVC実装は、開始ポインタと終了ポインタを効果的に格納します。ガベージをそれらにコピーしました。それらはもはやnullではないため、デストラクタはそれらを解放しようとし、アクセス違反が発生します。

ヒープの実装が同じであっても、最初から割り当てられなかったメモリへのガベージポインタを解放するため、クラッシュします。


要約すると、CRTのバージョンは一致する必要がありますが、定義も一致します-標準ライブラリの定義を含みます

50
JoeG

A :: getString()が返す文字列がA.dllで割り当てられ、main.exeで解放されている可能性がありますか?

はい。

もしそうなら、なぜ-そしてDLL(または実行可能ファイル)間で文字列を渡すための安全な方法は何でしょうか?カスタム削除機能でshared_ptrのようなラッパーを使用しない。

を使って shared_ptr私には賢明なことのように聞こえます。経験則として、このようなグリッチを回避するために、割り当てと割り当て解除は同じモジュールで実行する必要があることを忘れないでください。

Dll間でSTLオブジェクトをエクスポートすることは、せいぜいトリッキーなポニーです。最初に this MSDN KB記事をチェックし、 this postをチェックすることをお勧めします。

3
dirkgently

上記の内容に加えて、プラットフォームツールセット([プロパティ]-> [一般]の下)が両方のプロジェクトで同一であることを確認してください。そうしないと、到着側の文字列の内容が偽物になる可能性があります。

これは、v100ツールセットバージョンのコンソールアプリケーションプロジェクトがv90に設定されたライブラリを消費したときに発生しました。

2
Stefan

アプリ内のDLLごとに、デバッグまたはリリースの同じランタイムライブラリ(DLL 1)にリンクする必要があります。この場合、メモリが1つに割り当てられ、別のライブラリで解放されます。 (動的にリンクされたランタイムライブラリを使用する理由は、静的なものにリンクするdll/exeごとに1つではなく、プロセス全体に1つのヒープがあるためです。)

これには、std :: stringとstl-containersを値で返すことも含まれます。

理由は2つあります(更新されたセクション)

  • クラスのレイアウト/サイズが異なるため、コンパイルされたコードが異なると、データが異なる場所にあると想定されます。最初に作成した人は誰でも正しくなりますが、もう一方は遅かれ早かれクラッシュを引き起こします。
  • msvc heap-implementsはruntime-libごとに異なります。つまり、割り当てられていないヒープ内のポインターを解放しようとすると、問題が発生します。 (これは、レイアウトが類似している場合、つまり最初のケースよりも長生きする場合に発生します。)

したがって、ランタイムライブラリをまっすぐにするか、別のdllでの解放/割り当てを停止します(つまり、値によるものの受け渡しを停止します)。

2
Macke

両方のプロジェクト(AppとDLL)が静的バージョンではなく "マルチスレッドDLL"ランタイムライブラリのいずれかを使用していることを確認してください。

プロパティ->C/C++->コード生成->(/ MDまたは/ MDd

注:アプリでサードパーティのライブラリを使用している場合は、それらを再コンパイルする必要がある場合があります。リンカーは、不一致/重複するランタイムエラーでこれを一般的に通知します。

1
Hotpotato

これは、DLLとEXEが異なるCRT設定でコンパイルされているためである可能性があります。したがって、文字列を渡すと、リソースの競合が発生します。両方のDLLおよび実行可能ファイル。

1
Kerido