You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用USB路径打开通信设备时如何为CreateFile()句柄的ReadFile()设置超时

需求背景

我正在扩展热敏小票打印机的串口(COM)打印接口,使其无需虚拟串口即可支持USB接口。目前我已完成可用原型,可枚举已连接的USB设备,定位指定供应商ID(VID)和产品ID(PID)设备的USB路径,并通过CreateFile()打开设备连接。

现有串口代码使用封装了Windows API的函数库实现,我的方案是在同一套函数框架下新增适配USB连接的代码,无需修改原有核心逻辑。此前我已通过相同方案成功实现后厨打印机同时支持串口和WiFi/LAN连接的需求。

但现有函数库依赖ReadFile()的超时机制:若热敏打印机在合理时间内未响应状态请求,应用可标记设备故障,继续执行业务逻辑或切换至备用打印机。

核心问题

通过USB路径调用CreateFile()打开通信设备获取的文件句柄,如何为其ReadFile()操作指定超时时间?

额外说明

相关代码为多线程架构,可同时对接多种串口通信设备(小票打印机、后厨打印机、称重设备等),且每个线程独占对应设备的访问权限(后厨打印线程仅访问后厨打印机串口、称重线程仅访问称重设备串口等)。

现有串口代码中,针对CreateFile()打开的串口连接,使用SetCommTimeouts()设置超时,但该函数对通过CreateFile()打开的USB连接无效,因此需要其他机制实现USB设备路径下的I/O超时判断。

现有代码片段

串口打开代码

// 参考微软官方串口开发规范:如何指定大于COM9的串口
// CreateFile() 可用于获取串口句柄,Win32 API说明中明确要求:
// 共享模式必须为0,创建参数必须为OPEN_EXISTING,模板参数必须为NULL
//
// 文件名使用"COM1"到"COM9"时CreateFile()可正常执行
// 但使用"COM10"及以上编号时会返回INVALID_HANDLE_VALUE
//
// 如果端口名称为\\.\COM10,调用CreateFile()时正确的写法是"\\\\.\\COM10"
//
// 注意:该语法同样适用于COM1到COM9,部分硬件支持自定义端口名,该语法也可兼容这类自定义名称
wsprintf(wszPortName, TEXT("\\\\.\\COM%d"), usPortId);

/* 打开串口 */
/* 避免CreateFile偶发调用失败 */
for (i = 0; i < 10; i++) {
    hHandle = CreateFile (wszPortName, /* 端口名称指针,对应PifOpenCom()入参 */
                      GENERIC_READ | GENERIC_WRITE,  /* 读写访问权限 */
                      0,            /* 共享模式 */
                      NULL,         /* 安全属性指针 */
                      OPEN_EXISTING,/* 串口打开方式 */
                      0,            /* 端口属性 */
                      NULL);        /* 待复制属性的端口句柄 */

    /* 打开端口失败则返回对应错误 */
    if ( hHandle == INVALID_HANDLE_VALUE )   {    /* 无法打开端口 */
        dwError = GetLastError ();
        if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_INVALID_NAME || dwError == ERROR_ACCESS_DENIED) {
            LeaveCriticalSection(&g_SioCriticalSection);
            // COM端口不存在,大概率是USB设备对应的虚拟串口已拔出、断电,或串口/虚拟串口被其他应用占用
            return PIF_ERROR_COM_ACCESS_DENIED;
        }
        PifLog (MODULE_PIF_OPENCOM, LOG_ERROR_PIFSIO_CODE_01);
        PifLog (MODULE_ERROR_NO(MODULE_PIF_OPENCOM), (USHORT)dwError);
        PifLog(MODULE_DATA_VALUE(FAULT_AT_PIFOPENCOM), usPortId);
        PifSleep(500);
    } else {
        break;
    }
}
if ( hHandle == INVALID_HANDLE_VALUE )   {    /* 无法打开端口 */
    wsprintf(wszDisplay, TEXT("CreateFile, COM%d, Last Error =%d\n"), usPortId, dwError);
    OutputDebugString(wszDisplay);
    LeaveCriticalSection(&g_SioCriticalSection);
    return PIF_ERROR_COM_ERRORS;
}

/* 清除错误并清空接收缓冲区 */
dwError = (DWORD)(~0);                  // 置位所有错误码位
ClearCommError(hHandle, &dwError, NULL);
PurgeComm( hHandle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

ReadFile封装实现代码

fResult = ReadFile(hHandle, pBuffer, (DWORD)usBytes, &dwBytesRead, NULL);

if (PifSioCheckPowerDown(usPort, aPifSioTable) == TRUE) {
    return PIF_ERROR_COM_POWER_FAILURE;
}

if (fResult) {
    if (!dwBytesRead) return PIF_ERROR_COM_TIMEOUT;
    return (SHORT)dwBytesRead;
} else {
    SHORT  sErrorCode = 0;     // PifSubGetErrorCode()返回的错误码,必须在GetLastError()之后调用
    dwError = GetLastError();
    PifLog (MODULE_PIF_READCOM, LOG_ERROR_PIFSIO_CODE_06);
    PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)dwError);
    sErrorCode = PifSubGetErrorCode(hHandle);
    PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)abs(sErrorCode));
    PifLog (MODULE_DATA_VALUE(MODULE_PIF_READCOM), usPort);
    return (sErrorCode);
}
解决方案

由于USB设备句柄不支持串口专用的SetCommTimeouts()接口,可通过以下两种方案实现超时逻辑,完全兼容现有框架,无需修改上层业务代码:

方案1:重叠IO + 等待超时(推荐,侵入性最低)

仅需要修改USB设备打开逻辑和ReadFile封装逻辑:

  1. 调用CreateFile()打开USB设备时,新增FILE_FLAG_OVERLAPPED属性标记
  2. 读取USB设备时传入OVERLAPPED结构体并绑定手动重置事件,用WaitForSingleObject()设置等待超时时间
  3. 超时触发时调用CancelIo()终止未完成的IO操作,直接返回现有框架定义的超时错误码即可

适配后的ReadFile封装示例

// 提前在设备上下文新增bIsUsbDevice标记,USB设备打开时赋值为TRUE,串口设备为FALSE
if (bIsUsbDevice) {
    OVERLAPPED ov = {0};
    ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    BOOL readRet = ReadFile(hHandle, pBuffer, (DWORD)usBytes, &dwBytesRead, &ov);
    
    if (!readRet && GetLastError() == ERROR_IO_PENDING) {
        // 超时时间可替换为你现有框架的超时配置值,单位为毫秒
        DWORD waitRet = WaitForSingleObject(ov.hEvent, 500);
        if (waitRet == WAIT_TIMEOUT) {
            CancelIo(hHandle);
            CloseHandle(ov.hEvent);
            return PIF_ERROR_COM_TIMEOUT;
        } else if (waitRet == WAIT_OBJECT_0) {
            GetOverlappedResult(hHandle, &ov, &dwBytesRead, FALSE);
        } else {
            CloseHandle(ov.hEvent);
            return PIF_ERROR_COM_ERRORS;
        }
    }
    CloseHandle(ov.hEvent);
} else {
    // 原有串口读取逻辑保持不变
    fResult = ReadFile(hHandle, pBuffer, (DWORD)usBytes, &dwBytesRead, NULL);
}

// 后续原有逻辑保持不变
if (PifSioCheckPowerDown(usPort, aPifSioTable) == TRUE) {
    return PIF_ERROR_COM_POWER_FAILURE;
}
// ...其余原有判断逻辑完全复用

方案2:超时检测线程(适配不支持重叠IO的场景)

如果不想修改IO模式,可在调用ReadFile()前启动计时线程,超时未完成读取就调用CancelIoEx()终止IO操作,此时ReadFile()会返回失败,错误码为ERROR_OPERATION_ABORTED,将该错误码映射为现有框架的PIF_ERROR_COM_TIMEOUT即可。

内容的提问来源于stack exchange,提问作者Richard Chambers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 20:06:04