串行I / O重叠/非重叠Windows / Windows CE

时间:2013-02-19 11:37:48

标签: windows io serial-port windows-ce

对不起,这不是一个问题,而是帮助人们解决这些特殊问题。我正在处理的问题需要使用串行I / O,但主要是在Windows CE 6.0下运行。但是,最近我被问到应用程序是否也可以在Windows下运行,所以我开始着手解决这个问题。我确实花了很多时间环顾四周,看看是否有人得到了我正在寻找的答案,这些都是在很多错误信息和一些事情中基本上都是错误的事情。所以解决了这个问题后,我想我会和所有人分享我的发现,所以任何遇到这些困难的人都会得到答案。

在Windows CE下,OVERLAPPED I / O支持 NOT 。这意味着通过串口的双向通信可能非常麻烦。主要问题是,当您等待来自串行端口的数据时,您无法发送数据,因为这样做会导致主线程阻塞,直到读取操作完成或超时(取决于您是否设置了超时)

与大多数人进行串行I / O一样,我设置了一个读取器串行线程来读取串口,该串口使用带有EV_RXCHAR掩码的WaitCommEvent()来等待串行数据。现在,这就是Windows和Windows CE出现困难的地方。

如果我有这样一个简单的读者线程,作为一个例子: -

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方面是Windows CE和Windows之间的根本区别。

在Windows CE下,您可以做一些事情来使WaitCommEvent()失败,例如SetCommMask(COMMPORT_HANDLE,0)或甚至CloseHandle(COMMPORT_HANDLE)。这将允许您正确终止线程,因此释放串口以便您再次开始发送数据。但是这些东西都不能在Windows下运行,并且两者都会导致你调用它们的线程在WaitCommEvent()完成时等待。那么,你如何结束Windows下的WaitCommEvent()?好吧,通常你会使用OVERLAPPED I / O并且线程阻塞不会成为问题,但由于解决方案必须与Windows CE兼容,因此OVERLAPPED I / O不是一个选项。在Windows下你可以做一件事来结束WaitCommEvent(),那就是调用CancelSynchronousIo()函数,这将结束你的WaitCommEvent(),但要注意这可能是设备依赖的。 CancelSynchronousIo()的主要问题是Windows CE也不支持它,所以你运气不好用于解决这个问题!

那你怎么做的?事实是,要解决此问题,您根本无法使用WaitCommEvent(),因为无法在Windows CE支持的Windows上终止此功能。然后,您将使用ReadFile(),它会在读取NON OVERLAPPED I / O时再次阻塞,而 WILL 可以使用Comm Timeouts。

使用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和Windows CE上正常工作。

希望能帮助我见过的每个人都遇到这个问题。

1 个答案:

答案 0 :(得分:5)

由于我认为上面的评论中存在误解,所以这里有两个不使用紧密循环的可能解决方案的更多细节。请注意,这些都使用运行时确定,因此在两个操作系统下都是正常的(尽管你必须分别为每个目标编译),并且因为它们都不使用#ifdef,所以不太可能最终破坏编译器的一面或另一面你马上注意到了。

首先,您可以动态加载CancelSynchonousIo并在操作系统中使用它时使用它。甚至可选择做一些事情而不是取消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);
    }
}

另一个选项,需要更多的工作,因为你可能会有不同的线程模型(尽管如果你使用的是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);
}