串行 I/O 与 Windows/Windows CE 重叠/非重叠
Posted
技术标签:
【中文标题】串行 I/O 与 Windows/Windows CE 重叠/非重叠【英文标题】:Serial I/O Overlapped/Non-Overlapped with Windows/Windows CE 【发布时间】:2013-02-04 01:52:47 【问题描述】:很抱歉,这不是什么大问题,但更多的是为了帮助那些在这些特定问题上遇到问题的人。我正在处理的问题需要使用串行 I/O,但主要在 Windows CE 6.0 下运行。但是,最近有人问我是否也可以使该应用程序也可以在 Windows 下运行,所以我开始着手解决这个问题。我确实花了很多时间环顾四周,看看是否有人有我正在寻找的答案,这一切都被认为是很多错误信息和在某些情况下基本上是错误的事情。所以在解决了这个问题之后,我想我会与大家分享我的发现,这样遇到这些困难的人都会有答案。
在 Windows CE 下,不支持 OVERLAPPED I/O。这意味着通过串口进行双向通信可能会很麻烦。主要问题是,当您等待来自串口的数据时,您无法发送数据,因为这样做会导致您的主线程阻塞,直到读取操作完成或超时(取决于您是否设置了超时)
像大多数做串行 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 包含打开的 comm 端口句柄等。现在,当你的线程执行时,问题出在 Windows 下并点击 WaitCommEvent(),您的阅读器线程将进入睡眠状态等待串口数据。好的,这很好,应该是这样,但是......你如何结束这个线程并让 MessageBox() 出现?好吧,事实证明,这实际上并没有那么容易,并且是 Windows CE 和 Windows 在其串行 I/O 方式上的根本区别。
在 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 时再次阻塞,并且此将与 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 上正常工作。
希望对我见过的每个遇到过这个问题的人有所帮助。
【问题讨论】:
为什么不尝试在运行时动态加载 CancelSynchronousIo() ,如果您在 CE 下只需调用一个空存根?如果库在桌面上使用是否重叠,但在 Windows CE 下不重叠?似乎有更顺畅的方法可以解决这个问题。 您不能动态加载 CancelSynchronousIo(),它是一个 Windows API 函数,不属于 Windows CE。这样做的目的是要有一个无需修改即可在 Windows CE 和 Windows 上运行的解决方案。有一个适用于 Windows 的 OVERLAPPED 版本和一个适用于 Windows CE 的单独的 Non OVERLAPPED 版本是没有意义的,您也可以只使用 #ifdef/#endif 并有条件地为目标平台编译您的代码。SetCommMask
一直为我工作让WaitCommEvent
提前完成。但是,在我的代码中,它总是触发正在进行的重叠WaitCommEvent
的完成。你是说重叠的WaitCommEvent
确实由SetCommMask
完成,但同步的却没有?
你为你的 commTimeouts 设置了什么?是MAXDWORD, 0,0,0,0
吗?
【参考方案1】:
由于我认为我在上面的评论中有误解,这里有更多关于不使用紧密循环的可能解决方案的详细信息。请注意,这些使用运行时确定,因此在两种操作系统下都很好(尽管无论如何您都必须为每个目标单独编译),并且由于两者都没有使用#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);
【讨论】:
好的,我同意你关于确定 CancelSynchronousIo() 存在并在可用时使用它的观点!做得很好,只是我从来没有真正想到过……我只是选择了两个平台都相同的代码。该死,我的老派字节保存方法!cancelFcn(hPort);
错误;该函数使用线程的句柄来取消同步 i/o。要么提供该句柄(例如由CreateThread
返回),要么使用确实采用端口句柄的CancelIoEx
。以上是关于串行 I/O 与 Windows/Windows CE 重叠/非重叠的主要内容,如果未能解决你的问题,请参考以下文章
ABC程序使用相同设备进行I/O操作,即程序以串行方式使用设备,试画出单道运行和多道运行的时间关系图(调度程序执行时间忽略不计)在两种情况下,完成这三道程序各要花多少时间