DLL内部で動的に取得した変数を、実行モジュール側で開放する方法


ユリ  2009-06-09 02:26:19  No: 70307

今まで、プロパテの"MFCの使用"を"共有 DLL で MFC を使う" を指定して作成を行っていましたが、
"スタティック ライブラリで MFC を使用する"に変更したところ。(他のPCに配布するため)

DLL側のメソッドで戻り値が、動的に確保された変数、可変配列(vectorなど)を使用している箇所付近で、
"WindowsによってXXXXでブレークポイントが発生しました。ヒープが壊れたことが原因として考えられます。"
とエラーが発生します。

調べてみたところ、
http://madia.world.coocan.jp/cgi-bin/Vcbbs/wwwlng.cgi?print+200401/04010024.txt
http://msdn.microsoft.com/ja-jp/library/f22wcbea.aspx
で、基本的に不可能みたいに書かれていました。

何らかの方法で、実現することはできないでしょうか?
他のPCへの配布もあるので、"MFCの使用"は"スタティック ライブラリで MFC を使用する"を使用したいと思います
開発環境はVisual Studio2008 MFCアプリになります。

簡単に書くと以下のようになります。
--DLL側--
vector<int> GetArray() 

  vector<int> aRet;
  aRet.push_back( 1 );
  aRet.push_back( 2 );
  return aRet;
 }

--EXE側--
main(){
  vector<int> aTest = GetArray()

  return 0;    <==ここでエラーが発生する
}


ユリリ  2009-06-09 05:45:31  No: 70308

試してないけど、
const vector<int>& を返すようにすれば、
いけるんじゃね?


ユリリ  2009-06-09 05:48:25  No: 70309

よく読んでなかった。

>動的に確保された
これじゃ、
const vector<int>& もだめですな。


wclrp ( 'o')  2009-06-09 17:37:27  No: 70310

いまさらだけどプリミティブ型とかアライメントが規定されている構造体、ちゃんとバイナリ互換で規定されているインターフェースなどに限定したいな。

EXEがバージョンアップしたコンパイラ、コンパイルオプション、リンクするライブラリのバージョンとかデバッグ版・リリース版、バージョンアップしたSTLを使うとかが変化したら、DLLもそれに合わせないと構造体、MFC、STLの中身に食い違いが起きるかもしれないから気になって仕方ない。
とはいうものの大抵EXEとDLLをセットで公開するんだろうけど。


wclrp ( 'o')  2009-06-09 17:42:27  No: 70311

だれかこういうことに使えるSTL作ってくれねーかナ。
共有メモリに置けるSTLとかも。
いろいろ考えると難しいか。


ユリ  2009-06-09 18:36:59  No: 70312

返信ありがとうございます。
やはり、共有 DLL 使用時では上記のソースで問題ないですが、
MFCをスタティックリンクした場合は、DLL側からの戻り値でvectorは使用できないということでしょうか?  vector内部で動的にメモリを確保しているからかな?


tetrapod  2009-06-09 20:17:00  No: 70313

Windows 自体としては DLL と EXE で言語やコンパイラが違ってもおkなので
状況的には
g++ で作った DLL 側の vector の実装と
vc++ で作った EXE 側の vector の実装とが違う、とかと話のレベルが同じ。

なので答えは「そもそも動作保証されていないことをやりたがっている」のだから
動作保証は自分で行うべし、っつーことになるわけだ。

Windows というか Microsoft が保証している方法は既に指摘されているとおり
DLL/EXE 間でやり取りするデータは、バイナリ互換のある特定型のみ使え
ということになる。

で、そもそもの話として vector 自体をコピー渡し、コピー返却するわけ?
そのたびに(何百キロバイトにもなりうる)コピーが発生する事確定なのに?
そういう操作は根本的に無駄なので、仕様レベルで要再考察だと思う。
俺ならやらない。


ユリ  2009-06-09 20:58:26  No: 70314

>>tetrapodさん
実際の仕様では、プラグインとしてクラス(チェッカー)を作成して。
チェック結果(複数あり)を一括で取得する方法を考えていました。
結果配列は数個程度なので、一括管理を行ったほうが便利かと思いコピー返し
その後に、メンバ配列を削除します。

//チェッカー クラス
class Checker {
  //結果
  vector<Result>& m_aRet;
  
  //チェック
  Check() {
     aRet.push_back( 〜〜 )
  }  

  //結果取得
  GetResult( vector<Result>& aRet ) {
      aRet = m_aRet;

//参照渡しの aRetに1つづつ挿入しなおしてもダメ
//これなら大丈夫かと思ったのに・・・
//    for( int i = 0; i < m_aRet.size(); i++ ) {
//       aRet.push_back( m_aRet[ii] ); 
//    }
   aRet.clear()
  }

}

//結果格納 クラス
class Result{
    //いろいろ
}

//インスタンス作成
TestP* FuctoryChecker() {   return(new Checker); } 
void DeleteInstance(Checker* Obj){ delete(Obj); }


ユリ  2009-06-09 21:00:19  No: 70315

>>tetrapodさん
失礼しました。こっちのソースが正解です。
実際の仕様では、プラグインとしてクラス(チェッカー)を作成して。
チェック結果(複数あり)を一括で取得する方法を考えていました。
結果配列は数個程度なので、一括管理を行ったほうが便利かと思いコピー返し
その後に、メンバ配列を削除します。

//チェッカー クラス
class Checker {
  //結果
  vector<Result>   m_aRet;
  
  //チェック
  Check() {
     aRet.push_back( 〜〜 )
  }  

  //結果取得
  GetResult( vector<Result>& aRet ) {
      aRet = m_aRet;

//参照渡しの aRetに1つづつ挿入しなおしてもダメ
//これなら大丈夫かと思ったのに・・・
//    for( int i = 0; i < m_aRet.size(); i++ ) {
//       aRet.push_back( m_aRet[ii] ); 
//    }
   aRet.clear()
  }

}

//結果格納 クラス
class Result{
    //いろいろ
}

//インスタンス作成
TestP* FuctoryChecker() {   return(new Checker); } 
void DeleteInstance(Checker* Obj){ delete(Obj); }


  2009-06-09 22:05:54  No: 70316

プラグインならインターフェースで実装隠すべき
携帯なのでよく分からないけど


tetrapod  2009-06-09 22:29:45  No: 70317

今回問題になりうる事項は数点あって
・各種の型がバイナリ互換であるか否か (Result や vector)
・DLL/EXE が malloc(new) したメモリ領域を EXE/DLL で free(delete) できるか否か
・そもそも Result ってコピーしていいの?
・大量複写がおきうるコードを書いて性能が保証されうるか否か

んで、たとえばこんな感じで検索するとたくさんヒットするわけだが
http://www.google.co.jp/search?hl=ja&q=EXE+DLL+malloc&lr=&aq=f&oq=
CRT をスタティックリンクしたら malloc/free は DLL/EXE 境界を越えられないとわかる。

俺なら、配布を容易にするためにスタティックリンクするという選択がまず間違いだと判断する。
配布を容易にするなら正しくインストーラを作ればいい。
末端顧客にとっては、インストーラを使うだけってのが一番簡単だと思うわけだが。
(まさか EXE 1つを「コピーしてね」と配布するつもりでいるわけ?)

Windows XP の SxS はそれなりに正しく機能しているので、
DLL HELL の回避はそっちに任せてしまうほうが
開発者にとっても末端顧客にとっても楽だと思うのだけど、どうだろう


wclrp ( 'o')  2009-06-10 06:41:50  No: 70318

イメージだけどこんな感じかな。
そのまま動かせるプログラムじゃないよ。

<共通ヘッダー>

//結果格納 クラス
class Result{
    //いろいろ
}

class IChecker {
public:
virtual int GetResult( IVectorResult * pResult ) = 0;
virtual void Delete() = 0;
};

class IVectorResult {
public:
virtual void push_back(Result&) = 0;
};

<DLL実装>

IVectorResult は、
vector<Result>のアダプターで
実装がEXEにあるようにして
Checker::GetResultがm_aRetをコピーすればいいんじゃないの。

実装がEXEにあるからEXEでメモリ確保してEXEでメモリ解放することになる。
結局、DLL内の動的メモリ確保を、EXEで解放できているわけではなく、
どっちか一方に行わせているだけ。

m_aRetをコピーするので効率悪い感じもするが妥協。

class Checker : public IChecker 
{
vector<Result>   m_aRet;
public:
Checker();
~Checker();
virtual int GetResult( IVectorResult * pResult );
virtual void Delete();
};

Checker::Delete()
{
delete this;
}

int Checker::GetResult(IVectorResult * pResult)
{
for( int i = 0; i < m_aRet.size(); i++ ) {
pResult->push_back( m_aRet[i] ); 
}
}

<EXE実装>

clas CVectorResult : IVectorResult 
{
vector<Result>* m_p;
public:
virtual void push_back(Result&);
void Attach(vector<Result>*);
vector<Result>* Detach();
}
void CVectorResult::push_back(Result& x)
{
m_p->push_back(x);
}

使用例
CVectorResult a;
vector<Result>& b;
b.Attach(&a);

TestP* d = FuctoryChecker();
d.hoge();
d.GetResult(&b);
d.Delete();
d = NULL;

b.Detach();
aに結果が入っているし、
EXEにあるCVectorResult::push_backを実行したので
EXEで問題なく解放できる。

思いつきで書いたので動くかどうか知りません。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加