【问题标题】:Microsoft Windows API Serial ReadFile Producing Unexpected OutputMicrosoft Windows API Serial ReadFile 产生意外输出
【发布时间】:2025-12-14 08:10:01
【问题描述】:

我目前正在尝试编写一个程序,该程序将从串行通信端口上的 Arduino HC-05 模块读取蓝牙输出。

http://cdn.makezine.com/uploads/2014/03/hc_hc-05-user-instructions-bluetooth.pdf

当我打开一个 Putty 终端并告诉它监听 COM4 时,我能够看到在 Arduino 上运行的程序正在打印的输出。

但是,当我运行以下程序以尝试以编程方式处理串行端口上的传入数据时,我得到了显示的输出。

#include <Windows.h>
#include <string>
#include <atltrace.h>
#include <iostream>

int main(int argc, char** argv[]) {

    HANDLE hComm = CreateFile(
        L"COM4",
        GENERIC_READ | GENERIC_WRITE,
        0,
        0,
        OPEN_EXISTING,
        NULL,
        0
    );

    if (hComm == INVALID_HANDLE_VALUE) {
        std::cout << "Error opening COM4" << std::endl;
        return 1;
    }

    DWORD dwRead;

    BOOL fWaitingOnRead = false;

    OVERLAPPED osReader = { 0 };

    char message[100];

    osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (osReader.hEvent == NULL) {
        std::cout << "Error creating overlapping event" << std::endl;
        return 2;
    }

    while (1) {

        if (!fWaitingOnRead) {
            if (!ReadFile(
                hComm,
                &message,
                sizeof(message),
                &dwRead,
                NULL
            )) {
                if (GetLastError() != ERROR_IO_PENDING) {
                    std::cout << "Communications error" << std::endl;
                    return 3;
                }
            }
            else {
                message[100] = '\0';
                std::cout << message << std::endl;
            }
        }
    }

    return 0;
}

我对句柄和 ReadFile 函数调用进行了更改,以便它将在无限循环中同步进行调用。但是,Visual Studio 会弹出一条警告说程序已停止工作,然后要求调试或关闭程序。我的假设是它一定是在某处停滞或未能在堆栈中某处执行某些 WindowsAPI 函数。

任何帮助,指点,不胜感激。

【问题讨论】:

  • 如果错误 ERROR_IO_PENDING,只打印缓冲区没有任何意义。您应该只在出现 no 错误时打印它,而当您这样做时,您应该只打印 dwRead 字节。如果错误不是 ERROR_IO_PENDING 你应该打印出它实际上是什么。
  • 打印缓冲区在 ReadFile 失败的 else 逻辑中
  • 不,不是; elseif (GetLastError... 不与 if (!ReadFile... 配对
  • 我很愚蠢。谢谢
  • 现在您已经删除了FILE_FLAG_OVERLAPPED,您不应再将osReader 传递给ReadFile

标签: c++ windows bluetooth arduino


【解决方案1】:

至少在 IMO 中,使用重叠 I/O 来完成这项工作是相当严重的过度杀伤力。你可以让它发挥作用,但你需要付出很多额外的努力,而且可能完成的很少。

在 Windows 下使用 comm 端口最重要的一点是将超时设置为至少一半有意义的值。当我第一次这样做时,我首先将所有值设置为1,期望这会起作用,但可能会消耗过多的 CPU 时间,所以我想尝试更高的值以保持足够快响应,同时减少 CPU 使用率。

所以,我写了some code,只是将COMMTIMEOUTS 结构中的所有值设置为1,并设置通信端口来发送/读取数据。

我从来没有尝试过使用更长的超时来尝试减少 CPU 使用率,因为即使在我第一次写这篇文章时使用的机器上(可能是 Pentium II 或类似的机器),它也可以正常工作,并且可以消耗太少的 CPU 时间需要关心——我真的看不出机器完全空闲和传输数据之间的区别。可能有一些情况可以证明更多的工作是合理的,但至少对于我的任何需要来说,它似乎已经足够了。

【讨论】:

    【解决方案2】:

    那是因为message 的类型错误。

    要包含一个字符串,它应该是一个字符数组,而不是一个字符指针数组。

    另外,要将其视为字符串,您需要将最后一个字符之后的数组元素设置为'\0'ReadFile 会将读取的字符数放入dwRead

    此外,您似乎没有正确使用重叠 I/O。这个简单的程序不需要重叠的 I/O - 删除它。 (正如@EJP 所指出的,您正在错误地检查 ERROR_IO_PENDING。也将其删除。)

    【讨论】:

    • 当我在运行时这样做时它会冻结
    • @JackFrye 当你做什么时会冻结什么?
    • 我已将数据更改为大小为 100 的字符数组。我想我在打印 char* 之前的内存地址。我确保蓝牙连接正常。似乎进入了 ReadFile 检查并冻结,然后 Visual Studio 询问我是否要停止或调试。我会更新代码。 @immibis 代码已更新
    • @JackFrye 你有使用 C++ 的经验吗?
    • 是的。我很惊讶我错过了不正确的逻辑。我不熟悉 windows api 中的很多数据类型。我对缓冲区也生疏了。在做微控制器课程时,在 C 语言中与他们进行了短暂的合作。我可以简单地使用内置的流类来做到这一点吗?
    【解决方案3】:

    在您的程序中查看下面的 cmets:

        if (!fWaitingOnRead) {
            if (!ReadFile(                // here you make a non-blocking read.
                hComm,
                message,
                sizeof(*message),
                &dwRead,
                &osReader
                )) {
                                         // Windows reports you should wait for  input.
                                         //
                    if (GetLastError() != ERROR_IO_PENDING) {
                     std::cout << "Communications error" << std::endl;
                    return 3;
                }
                else {                   // <-- remove this.
                                         // insert call to GetOverlappedcResult here.
                    std::cout << message << std::endl;
                }
            }
        }
    
        return 0;                       // instead of waiting for input, you exit.
    }
    

    在调用 ReadFile() 后,您必须插入对 GetOverlappedResult(hComm, &amp;osReader, &amp;dwBytesRceived, TRUE) 的调用,以等待读取操作完成并在缓冲区中有一些字节。

    如果您不想过早退出,您还需要在程序中有一个循环。

    如果您不想进行重叠 i/o(这是一个明智的决定),请不要将 OVERLAPPED 指针传递给 ReadFile。 ReadFile 将阻塞,直到它有一些数据可以给你。然后你显然不需要打电话给GetOverlappedresult()

    对于串口,还需要填写一个DCB结构。 https://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx

    你可以使用BuildCommDCB()来初始化它。 MS 文档中有一个指向它的链接,CallGetCommState(hComm, &amp;dcb) 用于初始化串行端口硬件。串行端口需要知道您的应用所需的波特率等。

    【讨论】:

    • 我取出 FILE_FLAG_OVERLAPPED 并将其替换为 NULL。程序再次停滞不前。如果我在句柄上将 FILE_FLAG_OVERLAPPED 设置为 NULL,我还需要循环和 GetOverlappedResult 吗?
    • 我的愿景是让我的 arduino 不断通过蓝牙将传感器数据发送到 com 端口,并让我们现在正在讨论的一些程序记录和处理这些数据并为 arduino 发回决策。我假设这将包括重叠,这可能会使事情变得更加困难。您能否在 GetOverlappedResult() 调用中包含一些伪代码以及您在之前的评论中提到的循环结构
    • 我的建议是:如果您不熟悉 Windows 编程,请不要使用重叠 I/O。特别是如果您有一个简单的应答/应答控制台应用程序。确保填写您的 DCB 并初始化端口。在无限循环中,调用 ReadFile(),处理数据并调用 WriteFile,保持简单。从头开始处理重叠 I/O 和多线程需要相当多的时间。我确实有一些处理这个问题的 c++ 类......我会寻找它们。
    • 如果您有任何可以帮助您的材料或您以前使用过的课程,我愿意接受学习它的挑战。我确实认为这可能会有所帮助,尤其是将这个程序与 arduino 重复循环的速率同步