現在、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
文字セット:マルチバイト文字
いい線まで行ってるようですが、
!CSerialPortProcessor::ReceiveEventProcess(CSerialPortProcessor *
processor=0x00000000)
をよく見るとprocessorにヌルポが渡っているようです。この関数は
CSerialPortProcessor::ReceiveThreadFunc(LPVOID lpParameter)
から呼ばれているので、すなわちlpParameterに既に0が渡っているなど、
掲載されたコード「外」に問題がある疑いがあります。
スレッドの起動部分などを疑ってみてはどうでしょう。
さて、原因とは関係ありませんが、ReceiveThreadFunc(LPVOID lpParameter)
関数で
tmp->ReceiveEventProcess(tmp);
としてますが、C++的には、thisのメンバ関数に
thisのパラメータは不要ですよね(^^)/。
やぁ、失敗 orz.
× さて、原因とは関係ありませんが
○ 原因と関係あるかもしれません。
仲澤@失業者 さん
ご回答ありがとうございます。
>> としてますが、C++的には、thisのメンバ関数に
>> thisのパラメータは不要ですよね(^^)/。
そうなのですね。スレッドなので this参照を渡して操作しないと、メンバ変数との整合性
が取れないと思ってました。
ご指摘通りtmp->ReceiveEventProcess();とし、使わないように修正しました。
その上で、例外発生時に「this」をウォッチで確認すると「0x00000000」となっていて、ヌ
ルポになってました。(型はCSerialPortProcessorでした)
デバッグすると、CSerialPortProcessorクラスをDeleteしている箇所に止まらないのでとて
も不思議ですが、そこを中心に探して見ます。
お知恵を頂き、ありがとうございました。
いや、明示的に、個別のインスタンスを渡したい場合は、話は別ですが。
あまり例を見たことがありません(普通やりますかね、どうなんでしょう)。
一般に、スレッド関数からのコールバックを個別のインスタンスで受け取る場合、
つまり、staticでないコールバックを使用する場合、
「1スレッドに付き1インスタンス」
にすると思ったもので、そう指摘しました。
残念ながら、提示のコードからは類推することしかできませんでした。
さて、この様なコードの場合、ReceiveEventProcess()関数は
引数が必要ないので、
DWORD dEvent = 0;
while( m_ReceiveThreadFlag)
{
boolean result = WaitCommEvent( m_HComPort, &dEvent, &m_ReceiveOverlap);
:
の様に、スッキリ書けてうれしい訳ですね。
ついでに、自分が想定してしゃべっているクラスは、
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;)。
仲澤@失業者 さん
確かに「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)
ん?
> unsigned char *tmp = new unsigned char[Comstat.cbInQue + 1];
unsigned char *tmpをクラスメンバー、もしくはグローバル変数にして
外で宣言する。
[Comstat.cbInQue + 1]は任意の変数にして0にならないようにする。
できれば10以上かな?
これでどうでしょうか。
*tmpはほかで宣言して使うと例外が発生する可能性があります。
お返事遅くなりました。
仲澤@失業者 さん
よくよく考えたら、私のコードは「1スレッド1インスタンス」ではなく、「複数スレッド
1インスタンス」でした…
1インスタンス内で「スレッドの開始 → スレッドの停止 → スレッドの開始 → スレッ
ドの停止 …」 を繰り返しているだけです。
この流れは「1スレッド1インスタンス」では無いですよね…
その辺から見直してみます。
ITO さん
ご回答ありがとうございます。
ご指摘頂いた、
unsigned char *tmpをクラスメンバー変数にし、サイズを10以上にするようにして見たの
ですが、現象は変わりませんでした。
上記に記載しましたが前提が違ったので、全体を見直します。
取りあえず解決したのでご報告いたします。
「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引数:読み取ったバイト数」をきちんと判定してるサンプルって見なかったなぁ…
(見落としているだけの可能性が高いですが…)
勘違い等していたら突っ込み頂けると助かります。
お騒がせし、申し訳有りませんでした。
また、スレッドの勉強になりました。ありがとうございます。
しかし、何でWindowsXPでは例外にならなかったんだろう…orz
やっぱりメモリ管理がより厳密になったのかなぁ…