CRecordsetクラスのdelete時のヒープエラー

[戻る]
おみおみ 2006/07/24(月) 20:17:58
.net 2003 MFC使用。

CRecordsetを派生したクラスCRsHaseiをDLLで定義し、
Exe側ではそのクラスを以下のように使用

CRsHasei* pRs = new CRsHasei;
pRs ->Open();
pRs ->Close();

delete pRs;//ここでヒープエラー

DLLを使用しない場合はエラーになりません。
また、下記のようにオープン、クローズなしだとエラーになりません。

CRsHasei* pRs = new CRsHasei;
delete pRs;

原因や対応がわかりません。
よろしくお願い致します。

ITO 2006/07/25(火) 11:57:44
原因は、「delete pRs;」を行うタイミングで pRsを使っているので例外が 
    発生したんだと思います。
>CRsHasei* pRs = new CRsHasei;
DLL側で行う必要ありますか?
アプリケーション側なら、
「InitInstance()」でnewしてExitInstance()でdeleteすれば済みます。
DLLだとどこでどのタイミングで「CRsHasei」を使うか分りません。
  

おみおみ 2006/07/25(火) 17:35:32
すみません。ちょっと意味が理解できていないので、
ずれた返信かもしれませんが、
下記の処理は全てアプリケーション側で実行しています。

CRsHasei* pRs = new CRsHasei;
pRs ->Open();
pRs ->Close();
delete pRs;//ここでヒープエラー

>DLL側で行う必要ありますか?
これは上記の処理のことですか?CRsHaseiの定義のことですか?

>アプリケーション側なら、
>「InitInstance()」でnewしてExitInstance()でdeleteすれば済みます。

deleteはCRsHaseiを利用するクラスのデストラクタで実行したいと思っています。

よろしくお願いします。

ITO 2006/07/25(火) 17:57:42
>これは上記の処理のことですか?CRsHaseiの定義のことですか?
>CRsHasei* pRs = new CRsHasei;
>delete pRs;//ここでヒープエラー
この二つの文のことです。
>deleteはCRsHaseiを利用するクラスのデストラクタで実行したいと思っています。
今でもそうしていますか?
-----ココカラ------
pRs ->Open();
--処理---
pRs ->Close();
-----ココマデ---------
の処理にスレッドを使用していて、deleteする時に終わっていないとまずいですね。

PATIO 2006/07/25(火) 18:07:43
単純に派生したクラスの実装に問題があってメモリの状態がおかしくなっているとかあり
えないですか?
何らかの処理をするとおかしくなるのであれば、ありえないこともなさそうな気がします
けれど。
実際の実装がどうなっているのかわからないので単なる想像ですけれど。

おみおみ 2006/07/25(火) 21:11:04
>CRsHasei* pRs = new CRsHasei;
>delete pRs;

上記はExe側で実行しています。

>deleteはCRsHaseiを利用するクラスのデストラクタで実行したいと思っています。
今でもそうしていますか?

はい。それにテストとして、通常の関数内でも試しています。

>-----ココカラ------
>pRs ->Open();
>--処理---
>pRs ->Close();
>-----ココマデ---------
>の処理にスレッドを使用していて、deleteする時に終わっていないとまずいですね。

そうなんでしょうか。DLLを使用しない場合はスレッドにしなくてもエラーになりませ
ん。

>単純に派生したクラスの実装に問題があってメモリの状態がおかしくなっているとかあ
>りえないですか?
>何らかの処理をするとおかしくなるのであれば、ありえないこともなさそうな気がしま
>すけれど。

処理をおっていったところ、メモリのアドレス(pRsの値)は変わっていませんでした。
テスト時はオープンとクローズの間に何もしていません。

ちなみにCRsHaseiを定義したDLL内ではdelete時にエラーになりません。

ITO 2006/07/25(火) 21:28:00
>そうなんでしょうか。DLLを使用しない場合はスレッドにしなくてもエラーになりませ
>ん。
すみません、文章がおかしかったです。

誤
>の処理にスレッドを使用していて、deleteする時に終わっていないとまずいですね。

正
の処理にスレッドを使用していた場合、deleteする時に終わっていないと
まずいですね。       

おみおみ 2006/07/25(火) 21:59:22
>の処理にスレッドを使用していた場合、deleteする時に終わっていないと
まずいですね。 

ああ、そういう意味ならそうですね。

ITOさんの最初の書き込みの意味も分かりましたが、多分そういう制限はないと思いま
す。DLLにしなければ問題ないですので。

DLLの作り方が悪いのかもしれません。
明日ソース載せますので、よろしくお願いします。

おみおみ 2006/07/26(水) 20:09:55
>処理をおっていったところ、メモリのアドレス(pRsの値)は変わっていませんでした。

これは良く考えたら当たり前で、pRsの指す目盛りの中身を見ないとだめですね。

以下ソースです。

------------------------------------------------------
//RsWrapper.h
#pragma once
#include < afxdb.h >

class DLL_EXPORT_INEMS CRsWrapper : public CRecordset
//DLL_EXPORT_INEMSはマクロで、DLLでは__declspec(dllexport)、
//Exeでは__declspec(dllimport)になります。
{
public:
    CRsWrapper( CString p_cStrFolderPath, CString p_cStrFileName, CString 
p_cStrTableName, CDatabase* p_pdb = NULL );
    
    CRsWrapper( CString p_cStrFilePath, CString p_cStrTableName, 
Database* p_pdb = NULL );

    CRsWrapper( CDatabase* p_pdb = NULL );
    virtual ~CRsWrapper();
    DECLARE_DYNAMIC(CRsWrapper)

// オーバーライド
// ウィザードで生成された仮想関数のオーバーライド
public:
    virtual CString GetDefaultConnect();    // 既定の接続文字列
    virtual CString GetDefaultSQL();     // レコードセットの既定の SQL

    virtual void vSetFolderPath( CString p_cStrFolderPath );
    virtual void vSetFileName( CString p_cStrFileName );
    virtual void vSetTableName( CString p_cStrTableName );
// 実装
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

};

おみおみ 2006/07/26(水) 20:11:41
//RsWrapper.cpp
#include "stdafx.h"
#include "RsWrapper.h"

IMPLEMENT_DYNAMIC( CRsWrapper, CRecordset )
CRsWrapper::CRsWrapper( CString p_cStrFolderPath, CString p_cStrFileName, 
CString p_cStrTableName, CDatabase* p_pdb ): CRecordset( p_pdb )
{
    vSetFolderPath( p_cStrFolderPath );
    vSetFileName( p_cStrFileName );
    vSetTableName( p_cStrTableName );
}

CRsWrapper::CRsWrapper( CString p_cStrFilePath, CString p_cStrTableName, 
CDatabase* p_pdb ): CRecordset( p_pdb )
{
    vSetFolderPath( CDevCommon::cStrAbstFolderPath( p_cStrFilePath ));
    vSetFileName( CDevCommon::cStrAbstFileName( p_cStrFilePath ));
    vSetTableName( p_cStrTableName );
}

CRsWrapper::CRsWrapper( CDatabase* p_pdb ) : CRecordset( p_pdb )
{
}

CRsWrapper::~CRsWrapper()
{
}

CString CRsWrapper::GetDefaultConnect()
{
    return _T ("ODBC;DRIVER={Microsoft Access Driver (*.mdb)};FIL={MS 
Access};DBQ=") + m_cStrFolderPath + "\\" + m_cStrFileName;
}

CString CRsWrapper::GetDefaultSQL()
{
    return _T("[") + m_cStrTableName + _T("]");
}

void CRsWrapper::vSetFolderPath( CString p_cStrFolderPath )
{
    m_cStrFolderPath = p_cStrFolderPath;
}

void CRsWrapper::vSetFileName( CString p_cStrFileName )
{
    m_cStrFileName = p_cStrFileName;
}

void CRsWrapper::vSetTableName( CString p_cStrTableName )
{
    m_cStrTableName = p_cStrTableName;
}
/////////////////////////////////////////////////////////////////////////////
// CRsWrapper 診断

#ifdef _DEBUG
void CRsWrapper::AssertValid() const
{
    CRecordset::AssertValid();
}

void CRsWrapper::Dump(CDumpContext& dc) const
{
    CRecordset::Dump(dc);
}
#endif //_DEBUG

おみおみ 2006/07/26(水) 20:13:27
気づいたのです、今までの例は若干実際とは違っていました。
実際は以下のとおりです。

CRsWrapper・・・DLLで定義
CRsHasei・・・Exeで定義

↓Exeで使用
CRsHasei* pRs = new CRsHasei;
pRs ->Open();
pRs ->Close();
delete pRs;//ここでヒープエラー

ITO 2006/07/26(水) 21:21:40
実際は、「CRsHasei」のクラスが、DLLn関数を動かしているのですね。
ところで、
>CRsHasei* pRs = new CRsHasei;
何のために必要なんですか?
普通に
CRsHasei* pRs = new CRsHasei;
では駄目ですか?

ITO 2006/07/27(木) 12:01:46
CRsHasei がアプリケーション上で動いている以上
わざわざ、newする必要がないと思います。
ヒープに移すほどの容量のメモリーを使っているのでしょうか?

PATIO 2006/07/27(木) 13:22:39
普通にと言うのはこういうことですよね、多分。

CRsHasei Rs;

ITO 2006/07/27(木) 18:31:52
PATIOさん、フォロー有難う御座います。
>CRsHasei Rs;
すみません、間違っていました。

おみおみ 2006/07/27(木) 20:45:46
やっと原因が分かりました。

[DLL の境界を越えて CRT オブジェクトを渡す場合に発生する可能性のあるエラー]
http://msdn2.microsoft.com/ja-jp/library/ms235460.aspx

Exe側でnewもdeleteもしているのですが、継承しているため、どちらかがDLLで実行され
ているということなのでしょうか。

問題となっているDLL,ExeはMFC DLLをスタティックリンクしており、共有DLLにすれば発
生しないという情報があり、試しにDLL、Exeとも共有DLLを使用するようにしたところ、
症状は起きなくなりました。スタティックリンクするとCRTライブラリに相違が生じるの
でしょうか。

どちらにしろ私の能力では危険が多そうなので、DLLをスタティックリンクライブラリに
しました。というかもともとスタティックリンクライブラリをDLLにしようとしていたの
で、今回はそれをやめることにしました。

おつきあいどうもありがとうございました。

ちなみにnewしていたのはメモリ量の問題ではなく、
ポリモルフィズムしていたので、動的に生成する必要がありました。
まあnewしなくてもいい方法もあるのかもしれませんが、
そうであってもdeleteするとエラーになるというのはちょっと嫌でした。

おみおみ 2006/07/27(木) 20:45:49
[[解決]]
やっと原因が分かりました。

[DLL の境界を越えて CRT オブジェクトを渡す場合に発生する可能性のあるエラー]
http://msdn2.microsoft.com/ja-jp/library/ms235460.aspx

Exe側でnewもdeleteもしているのですが、継承しているため、どちらかがDLLで実行され
ているということなのでしょうか。

問題となっているDLL,ExeはMFC DLLをスタティックリンクしており、共有DLLにすれば発
生しないという情報があり、試しにDLL、Exeとも共有DLLを使用するようにしたところ、
症状は起きなくなりました。スタティックリンクするとCRTライブラリに相違が生じるの
でしょうか。

どちらにしろ私の能力では危険が多そうなので、DLLをスタティックリンクライブラリに
しました。というかもともとスタティックリンクライブラリをDLLにしようとしていたの
で、今回はそれをやめることにしました。

おつきあいどうもありがとうございました。

ちなみにnewしていたのはメモリ量の問題ではなく、
ポリモルフィズムしていたので、動的に生成する必要がありました。
まあnewしなくてもいい方法もあるのかもしれませんが、
そうであってもdeleteするとエラーになるというのはちょっと嫌でした。

PATIO 2006/07/28(金) 10:18:49
MFCライブラリをスタティックリンクしてしまうと
各々でライブラリの初期化や状態の保持が行われてしまう為に
おかしくなると思います。
DLLで構成するのであれば、MFCもDLLで使うべきだと思います。

ITO 2006/07/28(金) 11:44:41
今回、MFC拡張DLLですよね。
PATIOさんのご意見どおり、共有DLLじゃないと駄目みたいですね。
古いですが
http://www.microsoft.com/japan/developer/library/vccore/_core_extension_dlls.3a
_.overview.htm
新しい方
http://msdn2.microsoft.com/ja-JP/library/20ytt2wa.aspx

ぐぐってみるとたくさんありますね
MSDNのほうがいいかと思ったので参考にどうぞ
古いほうが詳しく書いてそうですね。

おみおみ 2006/07/29(土) 08:07:05
情報ありがとうございます。

>今回、MFC拡張DLLですよね。

いや、プロジェクト作成時に「MFCをスタティックにリンクした通常のDLL」というのを
選択しました。
おそらくこれ↓ですね。

http://msdn2.microsoft.com/ja-JP/library/f22wcbea.aspx

中盤のこの記述が関連してそうですね。
-------------------------------------------------------
レギュラー DLL 内のメモリ割り当てはすべて、DLL の範囲内に収める必要があります。
したがって、以下のポインタを呼び出し側の実行可能ファイルとやり取りすることはで
きません。

MFC オブジェクトへのポインタ

MFC によって割り当てられたメモリへのポインタ

呼び出し元の実行可能ファイルと DLL との間で上のポインタや MFC 派生オブジェクト
をやり取りする場合は、拡張 DLL を作成する必要があります。
-------------------------------------------------------

今回は勉強になりました。ありがとうございます。

古いスレッドには追加発言できません。