Windows7(64bit)で new すると例外が発生する

[戻る]
pianyi 2013/02/22(金) 15:03:25
現在、WindowsXP SP3(32/64bit) Windows7(64bit) で動作するシリアル通信を行うアプリ
を作っています。

WindowsXP SP3(32bit) で開発し正常にテストが完了したので、Windows7(64bit)でテスト
し始めた所、"unsigned char *tmp = new unsigned char[];"で例外が発生してしまいま
す。
また、debugでは発生せず、Releaseの時のみ発生します。

多分、new/deleteのやり方が良くないからだと思うのですが、助言など頂けないでしょう
か。


動作説明:
このスレッドは、COM1〜10まで接続確認しながらループさせてます。
その為、下記の動きをさせています。
COM1のスレッド開始 → COM1のスレッド停止 → COM2のスレッド開始 → COM2のスレッド
停止 …COM10のスレッド開始 → COM10のスレッド停止 


ここからコードの一部:
// スレッド開始メソッド
DWORD WINAPI CSerialPortProcessor::ReceiveThreadFunc(LPVOID lpParameter) { 
    CSerialPortProcessor *tmp = (CSerialPortProcessor*)lpParameter;
    return tmp->ReceiveEventProcess(tmp); 
} 
// イベント処理
DWORD WINAPI CSerialPortProcessor::ReceiveEventProcess(CSerialPortProcessor 
*processor) 
{ 
    DWORD dEvent = 0;
    while(processor->m_ReceiveThreadFlag)
    {
        boolean result = WaitCommEvent(processor->m_HComPort, &dEvent, 
&processor->m_ReceiveOverlap);
        if(GetLastError() == ERROR_IO_PENDING)
        {   // WaitCommEvent関数後はERROR_IO_PENDINGが多いので、イベント受信処理
が終わるまで待つ
            DWORD dtrByte = 0;
            GetOverlappedResult(processor->m_HComPort, &processor-
>m_ReceiveOverlap, &dtrByte, TRUE);
        }
        if(dEvent & EV_RXCHAR)
        {   // 受信イベント発生
            DWORD dError = 0;
            COMSTAT Comstat;
            // 受信バイトを取得する
            ClearCommError(processor->m_HComPort, &dError, &Comstat);
            TRACE(_T("受信バイト数:%ld"), Comstat.cbInQue);
            TRACE(CCommon::NEW_LINE);
            if(Comstat.cbInQue)
            {
                DWORD readBytes; 
// このnew unsigned char で例外が発生する
                unsigned char *tmp = new unsigned char[Comstat.cbInQue + 1];
                memset(tmp, '\0', Comstat.cbInQue + 1);
                ReadFile(processor->m_HComPort, tmp, Comstat.cbInQue, 
&readBytes, &processor->m_ReceiveOverlap);
                WaitForSingleObject(processor->m_Mutex, 0); 
                for(UINT i = 0; i < Comstat.cbInQue; i++)
                {
                    // 後退、LF改行、%が来た場合は、登録しない
                    if(tmp[i] != HEX_BS && tmp[i] != HEX_LF && tmp[i] != 
HEX_PERCENT)
                    {
                        // データに続けて保持しておく 
                        processor->m_ReceivedData.AppendChar(tmp[i]);
                    }
                }
                ReleaseMutex(processor->m_Mutex); 
                delete[] tmp;
                if(CCommon::GetNewLineIndex(processor->m_ReceivedData) != -1)
                {   // 改行が入ったら読み込み完了とし、イベント発生
                    PostMessage(processor->m_MainHWnd, 
WM_USER_MESSAGE_RECEIVE_DATA, 0, 0);
                }
            }
        }
        ResetEvent(processor->m_ReceiveOverlap.hEvent);
    }
    processor->m_ReceivedData = _T("");
    return S_OK;
}


呼び出し履歴:
!_CxxThrowException(void * pExceptionObject=0x00eff90c, const _s__ThrowInfo * 
pThrowInfo=0x0101de34)  行 161    C++
!AfxThrowMemoryException()  行 222 + 0x15 バイト    C++
!operator new(unsigned int nSize=31)  行 363 + 0xe バイト    C++
!CSerialPortProcessor::ReceiveEventProcess(CSerialPortProcessor * 
processor=0x00000000)
!CSerialPortProcessor::ReceiveThreadFunc(void * lpParameter=0x00bc7eb8)


情報不足などございましたらご指摘願います。
よろしくお願いします。

開発環境:
OS:Windows XP SP3
ツール:VS 2008 VC++ MFC
MFCの使用:スタティックライブラリのMFC
文字セット:マルチバイト文字

仲澤@失業者 2013/02/22(金) 16:35:10
いい線まで行ってるようですが、

!CSerialPortProcessor::ReceiveEventProcess(CSerialPortProcessor * 
processor=0x00000000)

をよく見るとprocessorにヌルポが渡っているようです。この関数は

CSerialPortProcessor::ReceiveThreadFunc(LPVOID lpParameter)

から呼ばれているので、すなわちlpParameterに既に0が渡っているなど、
掲載されたコード「外」に問題がある疑いがあります。
スレッドの起動部分などを疑ってみてはどうでしょう。

さて、原因とは関係ありませんが、ReceiveThreadFunc(LPVOID lpParameter)
関数で

tmp->ReceiveEventProcess(tmp); 

としてますが、C++的には、thisのメンバ関数に
thisのパラメータは不要ですよね(^^)/。

仲澤@失業者 2013/02/22(金) 16:39:50
やぁ、失敗 orz.
× さて、原因とは関係ありませんが
○ 原因と関係あるかもしれません。

pianyi 2013/02/22(金) 17:39:53
仲澤@失業者 さん

ご回答ありがとうございます。

>> としてますが、C++的には、thisのメンバ関数に
>> thisのパラメータは不要ですよね(^^)/。
そうなのですね。スレッドなので this参照を渡して操作しないと、メンバ変数との整合性
が取れないと思ってました。
ご指摘通り"tmp->ReceiveEventProcess();"とし、使わないように修正しました。


その上で、例外発生時に「this」をウォッチで確認すると「0x00000000」となっていて、ヌ
ルポになってました。(型はCSerialPortProcessorでした)

デバッグすると、CSerialPortProcessorクラスをDeleteしている箇所に止まらないのでとて
も不思議ですが、そこを中心に探して見ます。

お知恵を頂き、ありがとうございました。

仲澤@失業者 2013/02/22(金) 18:11:16
いや、明示的に、個別のインスタンスを渡したい場合は、話は別ですが。
あまり例を見たことがありません(普通やりますかね、どうなんでしょう)。

一般に、スレッド関数からのコールバックを個別のインスタンスで受け取る場合、
つまり、staticでないコールバックを使用する場合、

「1スレッドに付き1インスタンス」

にすると思ったもので、そう指摘しました。
残念ながら、提示のコードからは類推することしかできませんでした。

さて、この様なコードの場合、ReceiveEventProcess()関数は
引数が必要ないので、

DWORD dEvent = 0;
while( m_ReceiveThreadFlag)
{
    boolean result = WaitCommEvent( m_HComPort, &dEvent, &m_ReceiveOverlap);
    :

の様に、スッキリ書けてうれしい訳ですね。

仲澤@失業者 2013/02/22(金) 18:23:35
ついでに、自分が想定してしゃべっているクラスは、

class MyClass{
  friend UINT __cdecl StaticFunction_ThreadProc( void* ex_Inst);
  void StartThread(){
    AfxBeginThread( StaticFunction_ThreadProc, this);
  }
  void ThreadMain(){
    // スレッド処理メイン
  }
};
// 個々のインスタンスのスレッド処理メインをキックするだけ
UINT __cdecl StaticFunction_ThreadProc( void* ex_Inst){
  static BOOL run = FALSE;
  if( run == TRUE){ /*再入しました*/return;}
  run = TRUE;
  MyClass * me = ( MyClass *)ex_Inst;
  me->ThreadMain();
  return 0x11111111;
}

みたいなのを想定してます(vv;)。

pianyi 2013/02/22(金) 19:18:51
仲澤@失業者 さん

確かに「1スレッド1インスタンス」です。
すっきり書けるほうが嬉しいですね。

探してみてるのですが、原因が見えてこないです。
スレッド周りだと思われるので、スレッド開始と終了を提示させて頂きます。


スレッド開始処理:
// 指定ポートを開く 
m_HComPort = CreateFile(  
    comPortName, 
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING, 
    FILE_FLAG_OVERLAPPED,   // 非同期通信指定
    NULL); 
if(m_HComPort != INVALID_HANDLE_VALUE)
{
    // 受信用イベントハンドラ
    ZeroMemory( &m_ReceiveOverlap, sizeof(m_ReceiveOverlap) );
    m_ReceiveOverlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    m_ReceiveOverlap.Offset = 0;
    m_ReceiveOverlap.OffsetHigh = 0;
    // 受信用スレッドを作成する。
    m_HReceiveThread = CreateThread(NULL, 0, 
        ReceiveThreadFunc,
        (LPVOID)this, 
        CREATE_SUSPENDED,
        &m_ReceiveThreadId); 
    // スレッド処理を開始に設定する
    m_ReceiveThreadFlag = TRUE;
    // 受信を開始する。
    ResumeThread(m_HReceiveThread);
}


スレッド停止処理:
// スレッド停止要請
m_ReceiveThreadFlag = FALSE;
if (m_HReceiveThread != NULL)
{   // 受信スレッド停止
    SetCommMask(m_HReceiveThread, 0);

    if (WAIT_TIMEOUT == WaitForSingleObject(m_HReceiveThread, 2000))
    {
        TerminateThread(m_HReceiveThread, S_FALSE);
    }
    else 
    {
        CloseHandle(m_HReceiveThread);
    }
    m_HReceiveThread = NULL;
}
if(m_ReceiveOverlap.hEvent)
{
    ResetEvent(m_ReceiveOverlap.hEvent);
    CloseHandle(m_ReceiveOverlap.hEvent);
    m_ReceiveOverlap.hEvent = NULL;
}
if (m_HComPort != NULL){ 
    CloseHandle(m_HComPort); 
    m_HComPort = NULL;
}



処理の流れ:
1.   COM1で"スレッド開始処理"を実行。
2.   先に提示したスレッド処理が動く。
3.1. 5秒以内に指定の文字列が受信できない場合は、スレッド停止処理を行う。
3.2. 5秒以内に指定の文字列が受信できた場合は、接続完了に設定する。
4.   接続完了していない場合は、1.〜3.を繰り返す。(COM2、COM3…COM10に増やす)


土日でリフレッシュして、考え直したいと思います。
(投げ逃げっぽいな…orz)

ITO 2013/02/23(土) 13:32:10
ん?
> unsigned char *tmp = new unsigned char[Comstat.cbInQue + 1];
 unsigned char *tmpをクラスメンバー、もしくはグローバル変数にして
 外で宣言する。
 [Comstat.cbInQue + 1]は任意の変数にして0にならないようにする。
  できれば10以上かな?
 これでどうでしょうか。
 *tmpはほかで宣言して使うと例外が発生する可能性があります。
 
 

pianyi 2013/02/25(月) 11:16:37
お返事遅くなりました。

仲澤@失業者 さん

よくよく考えたら、私のコードは「1スレッド1インスタンス」ではなく、「複数スレッド
1インスタンス」でした…
1インスタンス内で「スレッドの開始 → スレッドの停止 → スレッドの開始 → スレッ
ドの停止 …」 を繰り返しているだけです。

この流れは「1スレッド1インスタンス」では無いですよね…
その辺から見直してみます。



ITO さん

ご回答ありがとうございます。
ご指摘頂いた、
unsigned char *tmpをクラスメンバー変数にし、サイズを10以上にするようにして見たの
ですが、現象は変わりませんでした。

上記に記載しましたが前提が違ったので、全体を見直します。

pianyi 2013/02/25(月) 15:15:41
[[解決]]
取りあえず解決したのでご報告いたします。

「1スレッド1インスタンス」にしてもエラーが発生しました。
その為、再度"CreateFile"・"ReadFile"・"WriteFile"をMSDNで確認した所、"ReadFile"で考慮
してない部分がありました。

http://msdn.microsoft.com/ja-jp/library/cc429679.aspx
************************ここから抜粋************************
lpNumberOfBytesRead
1 個の変数へのポインタを指定します。関数から制御が返ると、この変数に、実際に読み取った
バイト数が格納されます。何らかの作業やエラーのチェックを行う前に、ReadFile はこの値を 
0 に設定します。名前付きパイプに対してこの関数を実行し、戻り値が 0 以外(TRUE)である
場合に、この変数に値 0 が格納されたときは、メッセージモードのパイプのもう一方の側が、
nNumberOfBytesToWrite パラメータを 0 に設定して WriteFile 関数を呼び出した(つまり、何
も書き込まずに、ファイルのタイムスタンプを更新しただけ)ことを意味します。
************************ここまで抜粋************************

要するに、データは受け取ってるが「第3引数:読み取ったバイト数」は"0"になることがあ
る。って事だと思います。

で、実際に確認した所、Comstat.cbInQue は数値が入っているにもかかわらず、
"ReadFile"メソッドの「第3引数:読み取ったバイト数」は0が返ってきていました。
("ReadFile"メソッドの戻り値もTRUEでした)

その為、(戻り値がTRUE && 「第3引数:読み取ったバイト数」 > 0)と言うif文を追加した所、
エラーが発生しなくなりました。
悲しい限りです。

下記のように修正したしました。
************************ここから改修箇所一部************************
if(dEvent & EV_RXCHAR)
{   // 受信イベント発生
    DWORD dError = 0;
    COMSTAT Comstat;
    // 受信バイトを取得する
    ClearCommError(m_HComPort, &dError, &Comstat);
    if(Comstat.cbInQue)
    {
        BOOL successFlg = FALSE;
        DWORD readBytes; 
        // 受信データ用領域を確保
        unsigned char *tmp = new unsigned char[Comstat.cbInQue + 1];
        memset(tmp, '\0', Comstat.cbInQue + 1);
        // データの受信
        BOOL readFileResult = ReadFile(m_HComPort, tmp, Comstat.cbInQue, &readBytes, 
&m_ReceiveOverlap);

// ***************この判定文を追加***************
        if(readFileResult && readBytes > 0)
        {
            // 受信文字列データをスレッドセーフにする
            WaitForSingleObject(m_Mutex, 0); 
            for(DWORD i = 0; i < Comstat.cbInQue; i++)
            {
                // 後退、LF改行、%が来た場合は、登録しない
                if(tmp[i] != HEX_BS && tmp[i] != HEX_LF && tmp[i] != HEX_PERCENT)
                {
                    // 受信したデータは、m_ReceivedData に受信済みで取り出されていな
い 
                    // データに続けて保持しておく 
                    m_ReceivedData.AppendChar(tmp[i]);
                }
            }
            delete[] tmp;
            ReleaseMutex(m_Mutex); 
        }
        if(CCommon::GetNewLineIndex(m_ReceivedData) != -1 || m_ReceivedData.Find(_T
(">>")) != -1)
        {   // 改行か>>が入ったら読み込み完了とし、イベント発生
            PostMessage(m_MainHWnd, WM_USER_MESSAGE_RECEIVE_DATA, 0, 0);
        }
    }
}
************************ここまで改修箇所一部************************

ちなみに、今回問題になったドライバーは「F5521gw Moblie Broadband GPS Port」です。
Let's ノートに標準で入ってました。


しかし「第3引数:読み取ったバイト数」をきちんと判定してるサンプルって見なかったなぁ…
(見落としているだけの可能性が高いですが…)

勘違い等していたら突っ込み頂けると助かります。

お騒がせし、申し訳有りませんでした。
また、スレッドの勉強になりました。ありがとうございます。

pianyi 2013/02/25(月) 15:16:30
しかし、何でWindowsXPでは例外にならなかったんだろう…orz
やっぱりメモリ管理がより厳密になったのかなぁ…

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