アプリ版:「スタンプのみでお礼する」機能のリリースについて

お世話になります

無線機とGPSセンサーをCOMPORTでつないで操作やモニターするソフトを作成してまして
煮詰まってしまったので質問させていただきます。

無線機に設定をした際や読みこみリクエストを送った際に無線機からの返事を
取りこぼしてしまうことがあり受信を別スレッドにすれば解決するのかと思って
改良途中です。

まず手始めにGPSセンサーをCOMPORTをつないでいてGPSセンサーの受信を
別スレッドで動かしてます。
なんとか別スレッドで動くところまで来たのですがスレッドの終了処理が
わからないでいます。

ダイアログに
”GPS_ENABLE”

というボタンがありそれを押すと受信および表示→受信停止表示クリア→受信および表示
を繰り返すようにしてます。

色々いじってみたのですがGPS読み込み時に”GPS_ENABLE”を
押して終了しようとすると例外がスローされてしまいます。

ハンドルされない例外が 0x7682B5B2 で発生しました (FT991REMOTE.exe 内):
Microsoft C++ の例外: CMemoryException (メモリの場所 0x0587ECA4)。

例外がスローされました:読み取りアクセス違反。
pOb-> が 0xDDDDDDDD でした。

だいたい上記の2つのどちらかが出る感じです

ソースを提示します
よろしくお願いします

// FT991REMOTEDlg.h : ヘッダー ファイル
static UINT AFX_CDECL ThreadProc_GPS(LPVOID pParam);
~~~~~

// GetGPS.h : ヘッダー ファイル
static UINT __cdecl CALL_GPS_READ_Thread(LPVOID pData);
int READ_ROOP;//READのループ可否
~~~~~
// FT991REMOTEDlg.cpp : 実装ファイル

GetGPS* GetGPS1 = new GetGPS();
CWinThread* GPS_pThread;


BOOL CFT991REMOTEDlg::OnInitDialog()
{
~~~~~
GetGPS1->GPS_Flag = 0; //GPS読みだし有効無効
////////////////////////////////////////////////////////////
//タイマー設定//////////////////////////////////////////////
////////////////////////////////////////////////////////////
UINT timerID = 1;
UINT interval = 10;
m_timerID = SetTimer(timerID, interval, NULL);
// タイマーを設定できない場合
if (m_timerID == 0)
{
AfxMessageBox(_T("タイマーを設定できませんでした。"));
}
return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
~~~~~
}
void CFT991REMOTEDlg::OnTimer(UINT_PTR nIDEvent)
{
~~~~~
if ( (m_timerID == 1) && (NowCOMMAND_Set == 0 ) )
{
if (GetGPS1->GPS_Flag == 1)
{
CFT991REMOTEDlg::GPS_CALL_Read();//GPS内容表示
}
}
~~~~~
}
//GPS読み込み開始終了ボタン
void CFT991REMOTEDlg::OnBnClicked_GPS_Enable()
{
int Ret = 0;
if (GetGPS1->GPS_Flag == 0)//開始処理
{
Ret = GetGPS1->GetComport_CFG(_T("GPS.ini"));//GPSrs23cのコンフィグゲット及び初期化
if (Ret == TRUE)
{
GetGPS1->READ_ROOP = TRUE;//READのループ可否
GetGPS1->GPS_Flag = 1;
GetGPS1->OLD_GL_DATA = _T("");
GetGPS1->GL_DATA = _T("");

GPS_pThread = AfxBeginThread(GetGPS::CALL_GPS_READ_Thread, GetGPS1); // 重い処理をワーカースレッドで処理
}
}
else if (GetGPS1->GPS_Flag != 0)//終了処理
{
GetGPS1->READ_ROOP = FALSE;//READのループ可否
GetGPS1->GPS_Flag = 0;
GetGPS1->OLD_GL_DATA = _T("");
GetGPS1->GL_DATA = _T("");
GPS_pThread->Delete();//スレッドの終わらせ方このへんがあわからない
AfxEndThread(0, 1);//スレッドの終わらせ方このへんがあわからない;
CloseHandle(GetGPS1->ComPort5); // シリアルポートを閉じる
}
}

/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
GetGPS.cpp
int GetGPS::GetComport_CFG(CString CFG_FILE)
{
省略
ComPort5 = CreateFile(_T("\\\\.\\COM8"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); // シリアルポートを開く
);
Ret = PurgeComm(//消去
ComPort5,//通信デバイスのハンドル:CreateFile()で取得したハンドルを指定
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR
);
if (Ret == FALSE)//失敗した場合
{
printf("PurgeComm failed.\n");
CloseHandle(ComPort5);
message = _T("COMOPORTが開けません");
AfxMessageBox(message);
//exit(0);
return FALSE;
}
return TRUE;
}


//COMPORTから読み込み
int GetGPS::Readrs232c(CString* RS_SD, CString* RS_RD)//RS232C受信
{
int Ret;
RwRs232TimeOutSet();//タイムアウトの設定
Sleep(600); //
DWORD errors; // エラーが起きた場合、エラーコードが入る
COMSTAT comStat; // 通信状態バッファ

ClearCommError(ComPort5, &errors, &comStat); // 入出力バッファの情報を通信状態バッファへ取り込む
CString SSD, RRD;//引数のローカル変数利用
char* recievedData = new char[comStat.cbInQue];
DWORD NumberOfBytesRead = 0;

Ret = ReadFile(ComPort5, recievedData, comStat.cbInQue, &NumberOfBytesRead, NULL); // 受信バッファからデータバッファへ取り込む

A 回答 (3件)

作業スレッドの終了待ちは


WaitForSingleObject(GPS_pThread, INFINITE);
のようですね。ただし作業スレッドが終了しないと帰ってこない。

あと作業スレッドは終了すると自オブジェクトを削除しているかもしれない。そうするとGPS_pThread->Delete();は要らず、単にGPS_pThread=NULL;で良い。

下記によるとスレッド終了後に終了コードを取得する場合は
> m_bAutoDeleteデータメンバーを FALSE に設定します。これにより、CWinThread オブジェクトは、スレッドが終了した後も残ります。
とある。逆に言うとm_bAutoDelete=FALSEをしていない場合はスレッド終了時にオブジェクトが削除されるということ。
# https://docs.microsoft.com/ja-jp/cpp/parallel/mu …

あと参考までに MFCでスレッド処理を行う サンプル
# https://www.paveway.info/entry/2018/11/27/mfc_th …
このコードではスレッドは終了時に自分で終了メッセージを主スレッドに送っているので、主スレッドでスレッド終了イベントが捕まえられる。
    • good
    • 0
この回答へのお礼

お世話になります
rinkun様 ご回答ありがとうございます
すみません返信が逆になってしまいました。
こちらさ先です
色々とアドバイスをいただき感謝します

確かに省略しているところではRS232Cの読み込みを行ってます。
RS232Cの読み込みではデータがたまるのを待つためSleep(700)で
待機してました
上のRS232Cの読み込み処理が終わってから

forの下部の方でbreakさせているので再びRS232Cを読みに行くことはないと思って
大丈夫かと思ってましたがそれがうまくいかない原因だったのかもしれません。

取り急ぎかなり適当にいじりましたがなんとか例外が出なくなったようです

GPS_Read関数内
for(;;)
{
~~~~~~~~~

if (READ_ROOP == FALSE)
{
int ret;
ret = PurgeComm(//消去
ComPort5,//通信デバイスのハンドル:CreateFile()で取得したハンドルを指定
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR
// 実行する操作: 上記は未処理の読書きの中止及び送受信のバッファーのクリアを指定
);

GPS_Flag = 0;//このフラッグが0だとRS232Cを読みに行きません
CloseHandle(ComPort5); // シリアルポートを閉じるをここにもってきました
Sleep(1000);//ここで待たせるようにしました。
AfxEndThread(0, 1);
break;
}

お礼日時:2021/10/17 20:24

うーん、GPS_Readのforループ中の省略されたコードでCOMポートを読んでいるのですよね。

これの待ち時間によってはフラグREAD_LOOPをチェックする周期が長すぎて想定外になります。
あとフラグREAD_LOOPを立てた主スレッドの方は、その後に作業スレッド終了を待機しないとオブジェクトの削除時点で作業スレッドが終了していることを保証できません。質問のコードはこれが問題ですね。フラグを立てた数行後にオブジェクト削除していて、その間にシステムコールを呼びそうなコードが含まれていないので、作業スレッドが動作する隙もありません。スレッドは多くの場合、システムコールが呼ばれたタイミングで切り替わります。
スレッドの終了を待機するメソッドが用意されているはずなのでフラグを立てたらそれを呼んでスレッド終了を待って下さい。なお作業スレッドがフラグをチェックする周期が長い場合、このコードを入れると主スレッドの応答がなくなる場合がありますので注意下さい。
GUIの主スレッドなら作業スレッド終了でイベントが起きるようにしてフラグを立てて帰り、別途、作業スレッド終了イベントでオブジェクトを削除する方が安全です。

フラグREAD_LOOPは単なる変数でなくスレッド間通信機能のオブジェクトの方が安心ですが、このコードの例だと単なる変数でも動作はするでしょう。
    • good
    • 0
この回答へのお礼

void CFT991REMOTEDlg::OnBnClicked_GPS_Enable()
{

~~~~~~~~~
else if (GetGPS1->GPS_Flag != 0)
{

~~~~~~~~~

GetGPS1->READ_ROOP = FALSE;//READのループ可否

GetGPS1->GPS_Flag = 0;
GetGPS1->OLD_GL_DATA = _T("");
GetGPS1->GL_DATA = _T("");

//GPS_pThread->Delete();//これを残しておくと例外発生
GPS_pThread = NULL;
//AfxEndThread(0, 1);//これも←実際にループしてる場所で書く

// CloseHandle(GetGPS1->ComPort5); // シリアルポートを閉じる←GPS_Read関数へ移動

}
}

確かに消したものをまた消す行為が悪かったと思います
下記のように変更でよくなりました
//GPS_pThread->Delete();//例外でる
GPS_pThread = NULL;

更にこちらも変更して効いたようです
GetGPS1->GPS_Flag = 0;
CloseHandle(GetGPS1->ComPort5); // シリアルポートを閉じる←GPS_Read関数へ移動
あと今更ですがずーと繰り返すfor内に宣言してるところがこれもまずかったかと思い外で宣言するようにしました。

for(;;)
{
int a;//これだといけないのかと思い外に出しました
CString b;

}
イベントについてはなかなか理解が追い付かずこれから徐々に対策いようかと思います。

お礼日時:2021/10/17 20:20

長々としたソースをちゃんと読んではいませんが、おそらく作業スレッドが動いたままスレッドが使うオブジェクトを削除して破棄済みメモリにアクセスしているのでしょう。


まず一般論として(プロセスもそうですが)スレッドを外部から強制的に安全に止めることは出来ません。なので安全に止めたければ作業スレッドの処理中に定期的にフラグチェックを入れて、止めたい時には主スレッドがフラグを立てて作業スレッドはフラグチェック時点で停止指示があれば自分で終了します。厳密にはフラグもただの変数でなくイベントなどのスレッド間通信機能を使います。スレッドが使うオブジェクトは、スレッドが停止したことを確認して主スレッド側で削除します。
それと主スレッドと作業スレッドのデータのやりとりはどうしていますか? この辺も下手をするとメモリリークの原因になるので、よぼど多数のスレッドを作るとか効率優先でなければ作業スレッドでなく作業プロセスにしてデータもプロセス間通信でやりとりする方が安全です。
    • good
    • 0
この回答へのお礼

お世話になります
rinkun様 ご回答ありがとうございます

読み込んだデータの受け渡しは

クラスGetGPSのグローバル変数を読んでそちらを
FT991REMOTEDlgで表示しています。

GetGPS1->Ido_Keido
などのデータを表示してます

GPS_Read()がぐるぐる回ってまして
FT991REMOTEDlgで READ_ROOP を FALSE
にして GPS_Read()を抜けるようにしたつもりでした
安直にfor文の最後の方で抜ければうまくいくかと思いましたが
甘かったようです。

おっしゃってるように
破棄済みメモリにアクセスしているみたいなのですが
この辺の後処理がよくわかりません。
イベントなどのスレッド間通信機能もよく理解してませんので
GetGPS1のグローバル変数をいじって止めようとしてました




//////////////////////////////
//GPS読み込み成形処理////////////////////////////
//////////////////////////////
int GetGPS::GPS_Read()
{

for(;;)
{
COMPORT読み込み処理
読み込んだデータを計算整形し
グローバル変数に記録

~~~~~~~~~~~

if (READ_ROOP == FALSE)
{
int ret;
ret = PurgeComm(//消去
ComPort5,//通信デバイスのハンドル:CreateFile()で取得したハンドルを指定
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR
// 実行する操作: 上記は未処理の読書きの中止及び送受信のバッファーのクリアを指定
);

AfxEndThread(0, 1);
break;


}
}

お礼日時:2021/10/15 21:29

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています