6

申し訳ありませんが、これはそれほど問題ではありませんが、これらの特定の問題を抱えている人々を支援するためのものです。私が取り組んでいる問題は、シリアルI / Oを使用する必要がありますが、主にWindowsCE6.0で実行されています。しかし、最近、アプリケーションをWindowsでも動作させることができるかどうか尋ねられたので、この問題の解決に着手しました。私は、誰かが私が探していた答えを持っているかどうかを確認するためにかなりの時間を費やしました、そしてそれはすべて、多くの誤った情報といくつかの例では基本的に間違っていたものとして出くわしました。それで、この問題を解決したので、私は自分の発見をみんなと共有して、これらの困難に遭遇した人が答えを得ることができるようにしたいと思いました。

Windows CEでは、OVERLAPPED I/Oはサポートされていません。これは、シリアルポートを介した双方向通信が非常に面倒になる可能性があることを意味します。主な問題は、シリアルポートからのデータを待機しているときに、データを送信できないことです。これを行うと、読み取り操作が完了するかタイムアウトになるまでメインスレッドがブロックされます(タイムアウトを設定したかどうかによって異なります)。

シリアルI/Oを行うほとんどの人と同様に、シリアルポートを読み取るためにリーダーシリアルスレッドを設定しました。これは、EV_RXCHARマスクを指定したWaitCommEvent()を使用してシリアルデータを待機していました。ここで、WindowsとWindowsCEで問題が発生します。

例として、このような単純なリーダースレッドがある場合:-

UINT SimpleReaderThread(LPVOID thParam)
{
    DWORD eMask;
    WaitCommEvent(thParam, &eMask, NULL);
    MessageBox(NULL, TEXT("Thread Exited"), TEXT("Hello"), MB_OK);
}

明らかに、上記の例では、シリアルポートなどからデータを読み取っていません。また、thParamには通信ポートなどへの開いたハンドルが含まれていると想定しています。現在、問題はWindowsでスレッドが実行され、 WaitCommEvent()、リーダースレッドはシリアルポートデータを待ってスリープ状態になります。さて、それは問題ありませんが、...このスレッドを終了してMessageBox()を表示するにはどうすればよいですか?実は、これは実際にはそれほど簡単ではなく、シリアルI/Oの実行方法におけるWindowsCEとWindowsの根本的な違いです。

Windows CEでは、SetCommMask(COMMPORT_HANDLE、0)やCloseHandle(COMMPORT_HANDLE)など、WaitCommEvent()を失敗させるためのいくつかの操作を実行できます。これにより、スレッドを適切に終了できるため、シリアルポートを解放して、データの送信を再開できます。ただし、これらはどちらもWindowsでは機能せず、どちらも呼び出し元のスレッドをスリープ状態にして、WaitCommEvent()の完了を待機します。では、WindowsでWaitCommEvent()をどのように終了しますか?通常、OVERLAPPED I / Oを使用し、スレッドブロッキングは問題になりませんが、ソリューションはWindows CEとも互換性がある必要があるため、OVERLAPPED I/Oはオプションではありません。WindowsでWaitCommEvent()を終了するためにできることが1つあります。それは、CancelSynchronousIo()関数を呼び出すことです。これにより、WaitCommEvent()が終了しますが、これはデバイスに依存する可能性があることに注意してください。CancelSynchronousIo()の主な問題は、Windows CEでもサポートされていないことです。そのため、この問題にそれを使用するのは運が悪いです。

それで、どうやってそれをしますか?実際、この問題を解決するには、Windows CEでサポートされているWindowsでこの関数を終了する方法がないため、WaitCommEvent()を使用することはできません。これにより、ReadFile()が残り、NON OVERLAPPED I / Oの読み取り中に再びブロックされ、CommTimeoutsで機能します。

ReadFile()とCOMMTIMEOUTS構造体を使用するということは、シリアルデータを待機するタイトなループが必要になることを意味しますが、大量のシリアルデータを受信して​​いない場合は、問題にはなりません。また、小さなタイムアウトでループを終了するイベントにより、リソースがシステムに戻され、100%の負荷でプロセッサに負荷がかからないようになります。以下は私が思いついた解決策であり、改善できると思われる場合はフィードバックをいただければ幸いです。

typedef struct
{
    UINT8 sync;
    UINT8 op
    UINT8 dev;
    UINT8 node;
    UINT8 data;
    UINT8 csum;
} COMMDAT;

COMSTAT cs = {0};
DWORD byte_count;
COMMDAT cd;

ZeroMemory(&cd, sizeof(COMMDAT));
bool recv = false;
do
{
    ClearCommError(comm_handle, 0, &cs);
    if (cs.cbInQue == sizeof(COMMDAT))
    {
        ReadFile(comm_handle, &cd, sizeof(COMMDAT), &byte_count, NULL);
        recv = true;
    }
} while ((WaitForSingleObject(event_handle, 2) != WAIT_OBJECT_0) && !recv);
ThreadExit(recv ? cd.data : 0xFF);

したがって、スレッドを終了するには、event_handleでイベントを通知するだけで、スレッドを適切に終了し、リソースをクリーンアップして、WindowsおよびWindowsCEで正しく機能するようになります。

私が見たすべての人がこの問題に苦しんでいるのを助けることを願っています。

4

1 に答える 1

6

上記のコメントには誤解があったと思うので、タイトループを使用しない2つの可能な解決策について詳しく説明します。これらはランタイム決定を使用するため、両方のOSで問題ないことに注意してください(ただし、いずれにせよターゲットごとに個別にコンパイルする必要があります)。どちらも使用しないため、#ifdefすぐに気付かないうちに一方または他方でコンパイラが破損する可能性は低くなります。

まず、CancelSynchonousIoを動的にロードし、OSに存在する場合に使用できます。オプションで、CEのキャンセルの代わりに何かを実行することもできます(ハンドルを閉じるなど)。

typedef BOOL (WINAPI *CancelIo)(HANDLE hThread);

HANDLE hPort;

BOOL CancelStub(HANDLE h)
{
    // stub for WinCE
    CloseHandle(hPort);
}

void IoWithCancel()
{
    CancelIo cancelFcn;

    cancelFcn = (CancelIo)GetProcAddress(
        GetModuleHandle(_T("kernel32.dll")), 
        _T("CancelSynchronousIo"));

    // if for some reason you want something to happen in CE
    if(cancelFcn == NULL)
    {
        cancelFcn = (CancelIo)CancelStub;
    }

    hPort = CreateFile( /* blah, blah */);

    // do my I/O

    if(cancelFcn != NULL)
    {
        cancelFcn(hPort);
    }
}

異なるスレッドモデルを使用する可能性があるため、もう少し作業が必要なもう1つのオプション(ただし、C ++を使用している場合は、プラットフォームに基づく個別のクラスの場合に最適です)は、プラットフォームを決定することです。デスクトップでオーバーラップして使用します。

HANDLE hPort;

void IoWithOverlapped()
{
    DWORD overlapped = 0;
    OSVERSIONINFO version;

    GetVersionEx(&version);
    version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if((version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
        || (version.dwPlatformId == VER_PLATFORM_WIN32_NT))
    {
        overlapped = FILE_FLAG_OVERLAPPED;
    }
    else
    {
        // create a receive thread
    }

    hPort = CreateFile(
        _T("COM1:"), 
        GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, 
        OPEN_EXISTING, 
        overlapped,
        NULL);
}
于 2013-02-19T14:56:31.893 に答える