「生の」Win API呼び出しを介してシリアルポートの非同期I/Oを行うマルチスレッドWindowsプログラムがあります。Windows 7/64 を除くすべての Windows バージョンで問題なく動作します。
問題は、プログラムが COM ポートを見つけてセットアップすることはできますが、データを送受信できないことです。Win XP または 7 でバイナリをコンパイルしても、Win 7/64 では送受信できません。互換モード、管理者として実行などは役に立ちません。
問題を FileIOCompletionRoutine コールバックに絞り込むことができました。呼び出されるたびに、dwErrorCode は常に 0、dwNumberOfBytesTransfered は常に 0 です。関数内からの GetOverlappedResult() は常に TRUE を返します (すべて問題ありません)。lpNumberOfBytesTransferred を正しく設定しているようです。しかし、lpOverlapped パラメーターは破損しています。これは、ガベージ値を指すガベージ ポインターです。
正しい OVERLAPPED 構造体が割り当てられているアドレスをデバッガーでチェックするか、temp を設定することで、破損していることがわかります。それを指すグローバル変数。
私の質問は、なぜこれが起こるのか、なぜ Windows 7/64 でのみ起こるのかということです。私が認識していない呼び出し規約に関する問題はありますか? または、オーバーラップした構造体はどういうわけか別の方法で処理されますか?
以下のコードの関連部分を投稿します。
class ThreadedComport : public Comport
{
private:
typedef struct
{
OVERLAPPED overlapped;
ThreadedComport* caller; /* add user data to struct */
} OVERLAPPED_overlap;
OVERLAPPED_overlap _send_overlapped;
OVERLAPPED_overlap _rec_overlapped;
...
static void WINAPI _send_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped);
static void WINAPI _receive_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped);
...
};
オープン/クローズは、マルチスレッドも非同期 I/O も実装されていない基本クラスで行われます。
void Comport::open (void)
{
char port[20];
DCB dcbCommPort;
COMMTIMEOUTS ctmo_new = {0};
if(_is_open)
{
close();
}
sprintf(port, "\\\\.\\COM%d", TEXT(_port_number));
_hcom = CreateFile(port,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0);
if(_hcom == INVALID_HANDLE_VALUE)
{
// error handling
}
GetCommTimeouts(_hcom, &_ctmo_old);
ctmo_new.ReadTotalTimeoutConstant = 10;
ctmo_new.ReadTotalTimeoutMultiplier = 0;
ctmo_new.WriteTotalTimeoutMultiplier = 0;
ctmo_new.WriteTotalTimeoutConstant = 0;
if(SetCommTimeouts(_hcom, &ctmo_new) == FALSE)
{
// error handling
}
dcbCommPort.DCBlength = sizeof(DCB);
if(GetCommState(_hcom, &(DCB)dcbCommPort) == FALSE)
{
// error handling
}
// setup DCB, this seems to work fine
dcbCommPort.DCBlength = sizeof(DCB);
dcbCommPort.BaudRate = baudrate_int;
if(_parity == PAR_NONE)
{
dcbCommPort.fParity = 0; /* disable parity */
}
else
{
dcbCommPort.fParity = 1; /* enable parity */
}
dcbCommPort.Parity = (uint8)_parity;
dcbCommPort.ByteSize = _databits;
dcbCommPort.StopBits = _stopbits;
SetCommState(_hcom, &(DCB)dcbCommPort);
}
void Comport::close (void)
{
if(_hcom != NULL)
{
SetCommTimeouts(_hcom, &_ctmo_old);
CloseHandle(_hcom);
_hcom = NULL;
}
_is_open = false;
}
マルチスレッドとイベント処理のメカニズム全体はかなり複雑で、関連する部分は次のとおりです。
送信
result = WriteFileEx (_hcom, // handle to output file
(void*)_write_data, // pointer to input buffer
send_buf_size, // number of bytes to write
(LPOVERLAPPED)&_send_overlapped, // pointer to async. i/o data
(LPOVERLAPPED_COMPLETION_ROUTINE )&_send_callback);
受け取る
result = ReadFileEx (_hcom, // handle to output file
(void*)_read_data, // pointer to input buffer
_MAX_MESSAGE_LENGTH, // number of bytes to read
(OVERLAPPED*)&_rec_overlapped, // pointer to async. i/o data
(LPOVERLAPPED_COMPLETION_ROUTINE )&_receive_callback);
コールバック関数
void WINAPI ThreadedComport::_send_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
ThreadedComport* _this = ((OVERLAPPED_overlap*)lpOverlapped)->caller;
if(dwErrorCode == 0) // no errors
{
if(dwNumberOfBytesTransfered > 0)
{
_this->_data_sent = dwNumberOfBytesTransfered;
}
}
SetEvent(lpOverlapped->hEvent);
}
void WINAPI ThreadedComport::_receive_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
if(dwErrorCode == 0) // no errors
{
if(dwNumberOfBytesTransfered > 0)
{
ThreadedComport* _this = ((OVERLAPPED_overlap*)lpOverlapped)->caller;
_this->_bytes_read = dwNumberOfBytesTransfered;
}
}
SetEvent(lpOverlapped->hEvent);
}
編集
更新: 私は、コールバックが実行される前に OVERLAPPED 変数が範囲外になったという理論に一日のほとんどを費やしました。これが決して起こらないことを確認し、OVERLAPPED 構造体を静的として宣言しようとしましたが、同じ問題が残ります。OVERLAPPED 構造体が範囲外になった場合、コールバックが構造体が以前に割り当てられたメモリ位置を指すことを期待しますが、そうではなく、まったくなじみのない別のメモリ位置を指します。なぜそれができるのか、私にはわかりません。
おそらく、Windows 7/64 は OVERLAPPED 構造体の内部ハードコピーを作成しますか? 構造体の最後に忍び込んだ追加のパラメーターに依存しているため、この動作がどのように発生するかがわかります(これはハックのように見えますが、公式の MSDN の例から「ハック」を得たようです)。
呼び出し規約も変更しようとしましたが、これはまったく機能しません。変更すると、プログラムがクラッシュします。(標準の呼び出し規約ではクラッシュが発生します。標準が何であれ、cdecl? __fastcall もクラッシュを引き起こします。) 機能する呼び出し規約は、__stdcall、WINAPI、および CALLBACK です。これらはすべて__stdcallの同じ名前だと思います.Win 64はとにかくその呼び出し規約を無視することをどこかで読みました.
コールバックが実行されるのは、Win 7/64 で何らかの「疑似障害」が発生し、破損したパラメータまたは無関係なパラメータで誤ったコールバック コールが生成されるためと思われます。
マルチスレッドの競合状態は別の理論ですが、バグを再現するために実行しているシナリオでは、スレッドは 1 つしかなく、ReadFileEx を呼び出しているスレッドがコールバックを実行しているスレッドと同じであることを確認できます。