虽然说大多数情况下,我们可以非常便利的通过打印机驱动来控制打印机,但还是有一些特殊情况,导致无法通过打印机驱动来完成我们预想的任务,比如,打印机只是一个系统设备中的一部分,需要协调其它设备一起工作时,如果只是通过打印机驱动来完成打印任务,就很难与系统中的其它设备完美协调。
那么,我们应该如何解决这种问题呢?一、开发特定的打印机驱动来配合;二、定制专用的Firmware,增加特殊的控制指令,通过USB端口来控制打印机的打印并实时获取打印机当前的工作状态,从而实现完美配合系统其它设备的功能。
那么,新的问题来了,我们应该选择方案一还是方案二呢?其实这个问题,不难选择,大多数情况下,有这种需求的打印机,就不是一款通用形的打印机,而是一款定制的或专用于某个领域的打印机,也就意味着,这种打印机本就是定制的,从硬件到Firmware,都是定制的,所以,显然选择方案二是最合适的。
新问题又有了,我们如何才能通过USB端口控制打印机呢?回答这个问题之前,我们先介绍一下window系统下usb设备的类型。
一、usb设备的类型
USB设备类型根据功能和应用场景可分为以下几大类:
一)、常用设备类
-
HID(人机接口设备)
- 用途:用于人与计算机交互的输入设备
- 示例:键盘、鼠标、游戏手柄
- 协议特征:支持低速/全速模式,兼容性强
-
MSC(大容量存储设备)
- 用途:数据传输与存储
- 示例:U盘、移动硬盘、SD卡读卡器
- 协议速度:USB 2.0最高支持480Mbps
-
CDC(通信设备类)
- 用途:串行通信与网络连接
- 示例:调制解调器、网络摄像头
- 应用场景:虚拟COM端口、数据透传
-
Audio Class(音频设备类)
- 用途:音频输入/输出
- 示例:USB麦克风、耳机、MIDI设备
- 应用特点:支持音频流传输与处理
-
Video Class(视频设备类,UVC)
- 用途:视频捕捉与传输
- 示例:网络摄像头、视频采集卡
- 协议优势:标准化视频传输协议
二)、其他设备类
- Printer Class(打印机类)
- 用途:打印机控制与数据传输
- PTP(图像传输协议)
- 用途:相机、扫描仪等图像设备的数据传输
- Hub Class(集线器类)
- 用途:扩展USB端口数量
三)、物理接口类型
虽然与功能分类无关,但物理接口类型影响设备兼容性:
- Type-A:最常见接口,用于U盘、键盘等
- Type-C:正反插设计,支持高速数据传输与供电
- Micro/Mini USB:主要用于旧款手机及小型设备
注:USB设备类与物理接口类型无直接绑定关系,同一接口(如Type-C)可能支持多种设备类功能
二、USB 设备 GUID 核心解析
一)、GUID 的定义与作用
GUID(全局唯一标识符) 是用于标识 USB 设备类别的 128 位唯一编码,确保不同设备接口或功能在系统中被精准识别。
- 设备接口类 GUID:标识设备的具体功能接口(如打印机、存储设备),
例如 USB 打印设备的接口 GUID 为
GUID_DEVINTERFACE_USBPRINT
({28d78fad-5a12-11d1-ae5b-0000f803a8c2}
)。 - 设备安装类 GUID:用于管理驱动安装分类(如鼠标、键盘),通过注册表路径
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
查看对应关系。
二)、GUID 与硬件标识符的关系
标识符 | 含义 | 用途 |
---|---|---|
VID | 供应商 ID(由 USB-IF 分配) | 标识设备制造商 |
PID | 产品 ID(由厂商自定义) | 区分同一厂商的不同产品型号 |
GUID | 全局唯一标识符(系统或驱动定义) | 系统层面管理设备接口或驱动分类(如 GUID_DEVINTERFACE_USBPRINT 标识打印接口) |
三)、系统级 GUID 应用场景
-
驱动匹配与加载
- Windows 系统通过设备接口类 GUID 自动加载对应驱动程序(如
usbprint.sys
驱动绑定GUID_DEVINTERFACE_USBPRINT
)。 - 若 GUID 与驱动注册不匹配,设备管理器会显示未知设备或错误代码(如
43
)。
- Windows 系统通过设备接口类 GUID 自动加载对应驱动程序(如
-
设备枚举与管理
- 使用 API
SetupDiGetClassDevs
时需指定 GUID 来筛选设备(示例:DIGCF_DEVICEINTERFACE | DIGCF_PRESENT
枚举已连接的 USB 打印机)。 - 设备路径(如
\\?\USB#VID_xxxx&PID_xxxx#...
)中隐含 GUID 信息,用于底层通信。
- 使用 API
四)、GUID 查看与调试方法
-
注册表查看
- 设备接口类 GUID:通过
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
分支查询。 - 设备安装类 GUID:在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
下按设备类别检索。
- 设备接口类 GUID:通过
-
设备管理器调试
- 右键设备 → 属性 → 详细信息 → 选择 设备类 GUID 字段,查看当前设备绑定的 GUID。
五)、Windows 11 的 GUID 管理优化
- 动态切换机制:针对多功能设备(如打印扫描一体机),系统会根据当前操作模式动态切换 GUID,优化资源分配。
- USB4 兼容性增强:新增 USB4 设备接口 GUID(如隧道协议支持),提升高速数据传输和 DisplayPort 视频流的稳定性
三、USB 打印设备 GUID
一)、USB 打印设备接口 GUID
Windows 系统通过 GUID(全局唯一标识符) 识别特定设备类型。对于 USB 打印机,其设备接口 GUID 定义为:
DEFINE_GUID(GUID_DEVINTERFACE_USBPRINT, 0x28d78fad, 0x5a12, 0x11d1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);
该 GUID 用于通过 Windows API 枚举和识别 USB 打印设备。
二)、获取 GUID 的编程方法
-
设备枚举核心代码
#include <SetupAPI.h> #include <initguid.h> #include <Usbiodef.h>HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USBPRINT, // 指定打印机接口 GUIDNULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE );
通过 SetupDiGetClassDevs 函数可获取所有已连接的 USB 打印机设备实例。
-
设备路径提取
SP_DEVICE_INTERFACE_DATA interfaceData = {0}; interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);// 遍历设备接口列表 SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USBPRINT, 0, &interfaceData);
结合 SetupDiGetDeviceInterfaceDetail 可进一步获取设备物理路径(如 \\?\USB#VID_04B8&PID_0202...)
四、实例说明通过USB端口控制打印机
一)、枚举USB打印机端口
DEFINE_GUID(GUID_DEVINTERFACE_USBPRINT,0x28d78fad, 0x5a12, 0x11d1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);#define FX_PRINTER_ID _T("\\\\?\\USB#VID_03EB&PID_6013#")
#define SL_PRINTER_ID _T("\\\\?\\USB#VID_03EB&PID_6006#")
#define ST_PRINTER_ID _T("\\\\?\\USB#VID_03EB&PID_5008#")
#define OEM_PRINTER_ID _T("\\\\?\\USB#VID_0009&PID_0005#")BYTE CUSB_Device::EnumDeviceInterface(CString * pszDevicePath, CString * pszDeviceID, int nports)
{int MemberIndex = 0;LONG Result = 0;DWORD Length = 0;HANDLE hDevInfo;ULONG Required;BYTE index = 0;PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL;SP_DEVICE_INTERFACE_DATA devInfoData;hDevInfo = SetupDiGetClassDevs((LPGUID)&(GUID_DEVINTERFACE_USBPRINT), NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);if (hDevInfo == INVALID_HANDLE_VALUE){
// MessageBox(NULL, _T("No hardware device"), NULL, MB_OK);return 0;}devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);//Step through the available devices looking for the one we want. do{Result = SetupDiEnumDeviceInterfaces(hDevInfo, 0, (LPGUID)&(GUID_DEVINTERFACE_USBPRINT), MemberIndex++, &devInfoData);if (Result != 0){SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInfoData, NULL, 0, &Length, NULL);//Allocate memory for the hDevInfo structure, using the returned Length.// detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)new BYTE[Length * 4];detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)GlobalAlloc(GPTR, Length);;if (detailData != NULL){//Set cbSize in the detailData structure. detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);//Call the function again, this time passing it the returned buffer size.if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInfoData, detailData, Length, &Required, NULL) == TRUE){CString szID(detailData->DevicePath);szID.MakeUpper();if ((szID.Find(SL_PRINTER_ID, 0) != -1) || (szID.Find(ST_PRINTER_ID, 0) != -1) || (szID.Find(FX_PRINTER_ID, 0) != -1) || (szID.Find(OEM_PRINTER_ID, 0) != -1)){if (nports != 0){if (index < nports){if (pszDevicePath != NULL)pszDevicePath[index] = szID;if (pszDeviceID != NULL){int iPID_POS = szID.Find(_T("PID_"));m_szSerial = szID.Right(szID.GetLength() - iPID_POS - 9);int iret = m_szSerial.Find(_T("#"));m_szSerial = m_szSerial.Left(iret);pszDeviceID[index] = m_szSerial;}index++;}}else{index++;}}}GlobalFree(detailData);}}} while (Result != 0 && MemberIndex < 127);SetupDiDestroyDeviceInfoList(hDevInfo);if (!Result && (MemberIndex >= 127)){
// MessageBox(NULL, _T("Can not Open USB port"), NULL, MB_OK);return 0;}return index ;
}
系统有可能连接多台打印机,所以我们只要枚举需要控制的打印机端口,下面代码就是用来判断是否是我们需要控制的打印机端口。
if ((szID.Find(SL_PRINTER_ID, 0) != -1) || (szID.Find(ST_PRINTER_ID, 0) != -1) || (szID.Find(FX_PRINTER_ID, 0) != -1) || (szID.Find(OEM_PRINTER_ID, 0) != -1))
下面代码用于打开USB打印端口:
m_hDevice = CreateFile(detailData->DevicePath,GENERIC_READ | GENERIC_WRITE,NULL,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
二)、写USB打印端口
DWORD CUSB_Device::writePort(BYTE *buff, DWORD len)
{DWORD writtedLen,idx,waitTimes,n,tempLen;BOOL bResult;ASSERT(buff);if( !(buff && len && this->openPort()))return 0;writtedLen = 0;idx = 0;waitTimes = 0;m_percent = 0;tempLen = len;OVERLAPPED overlapped;memset(&overlapped,0,sizeof(OVERLAPPED));overlapped.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);while(len){n = tempLen - idx;if( n > _SEND_BLOCK_SIZE)n = _SEND_BLOCK_SIZE;if (!::WriteFile(m_hDevice, &buff[idx], n, &writtedLen, &overlapped)){if (GetLastError() == ERROR_IO_PENDING)//GetLastError()函数返回ERROR_IO_PENDING,表明串口正在进行操作{//使用WaitForSingleObject函数等待,直到读操作完成或延时已达到1秒钟//当串口操作进行完毕后,overlapped的hEvent事件会变为有信号while(WaitForSingleObject(overlapped.hEvent,2000) != WAIT_OBJECT_0){waitTimes++;if(waitTimes > 6){BYTE lpstatus[_MAX_USB_RECEIVED];if (GetPrinterStatus(lpstatus) > 0){if ((lpstatus[0] & 0xff) == 0x98){MessageBox(NULL, NULL, _TEXT("读写USB口出错!"), MB_OK | MB_ICONINFORMATION );return idx;}}}}waitTimes = 0;bResult = GetOverlappedResult(m_hDevice, &overlapped, &writtedLen, FALSE);if (!bResult) break; }else{if(waitTimes > 1)break;waitTimes++;writtedLen = 0;m_hDevice = INVALID_HANDLE_VALUE;this->openPort();}}idx += writtedLen;len -= writtedLen;m_percent = (BYTE)(idx * 100 / tempLen);}return idx;
}
三)、读打印端口
DWORD CUSB_Device::readPort(BYTE *buff, DWORD len)
{DWORD readLen,dataLen,waitTimes = 0;
// BOOL bResult;BYTE *lpBuff;ASSERT(buff);if( !(buff && len && this->openPort()))return 0;OVERLAPPED overlapped;memset(&overlapped,0,sizeof(OVERLAPPED));overlapped.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);// bResult = TRUE;readLen = 0;dataLen = 0;lpBuff = buff;while(true){::ReadFile(m_hDevice, lpBuff, len, &readLen, &overlapped);if (GetLastError() == ERROR_IO_PENDING)//GetLastError()函数返回ERROR_IO_PENDING,表明串口正在进行操作{//使用WaitForSingleObject函数等待,直到读操作完成或延时已达到1秒钟//当串口操作进行完毕后,overlapped的hEvent事件会变为有信号
#if 1BOOL timewait=TRUE;while(timewait){switch(WaitForSingleObject(overlapped.hEvent,6000)){case WAIT_OBJECT_0:timewait = FALSE;break;case WAIT_TIMEOUT:case WAIT_ABANDONED:default:return 0;}}
// GetOverlappedResult(m_hDevice, &m_ReadOvlp, &curLen, TRUE);
#elsewhile(WaitForSingleObject(overlapped.hEvent,2000) != WAIT_OBJECT_0){waitTimes++;if(waitTimes > 6){::CloseHandle(overlapped.hEvent);break;}}
#endifwhile (!GetOverlappedResult(m_hDevice, &overlapped, &readLen, FALSE));
// if (!bResult)
// break;dataLen += readLen;if(dataLen < len){lpBuff += readLen;len -= readLen;continue;}elsebreak;}else{if(waitTimes > 1)break;waitTimes++;m_hDevice = INVALID_HANDLE_VALUE;this->openPort();}} if(dataLen != len)return 0;return dataLen;
}
四)、获取打印机状态
BOOL Control_Info(HANDLE hDevice,DWORD cntrlCode,LPTSTR buff,DWORD &len)
{BOOL retFlag;DWORD retLen;retFlag = DeviceIoControl(hDevice, //HANDLE hDevice,cntrlCode,//DWORD cntrlCode,NULL, //LPVOID lpInBuffer,0, //DWORD nInBufferSize,buff, //LPVOID lpOutBuffer,len, //DWORD nOutBufferSize,&retLen, //LPDWORD lpBytesReturned,NULL // LPOVERLAPPED lpOverlapped);len = retLen;return retFlag;
}BYTE CUSB_Device::GetPrinterStatus(BYTE * lpsts)
{
// return 1;DWORD len = _MAX_USB_RECEIVED;TCHAR lptemp[_MAX_USB_RECEIVED];if( !(this->openPort()))return 0;if (Control_Info(m_hDevice,IOCTL_USBPRINT_GET_LPT_STATUS,lptemp,len)){*lpsts = (BYTE)lptemp[0];return (BYTE)len;}elsereturn 0;
}
本来想上传完整源代码的,但不知道为什么,一直显示上传中断,无法上传,有需要的朋友可以联系我。