虽然说大多数情况下,我们可以非常便利的通过打印机驱动来控制打印机,但还是有一些特殊情况,导致无法通过打印机驱动来完成我们预想的任务,比如,打印机只是一个系统设备中的一部分,需要协调其它设备一起工作时,如果只是通过打印机驱动来完成打印任务,就很难与系统中的其它设备完美协调。
那么,我们应该如何解决这种问题呢?一、开发特定的打印机驱动来配合;二、定制专用的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, // 指定打印机接口 GUID NULL, 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 1
BOOL 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);
#else
while(WaitForSingleObject(overlapped.hEvent,2000) != WAIT_OBJECT_0)
{
waitTimes++;
if(waitTimes > 6)
{
::CloseHandle(overlapped.hEvent);
break;
}
}
#endif
while (!GetOverlappedResult(m_hDevice, &overlapped, &readLen, FALSE))
;
// if (!bResult)
// break;
dataLen += readLen;
if(dataLen < len)
{
lpBuff += readLen;
len -= readLen;
continue;
}
else
break;
}
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;
}
else
return 0;
}
本来想上传完整源代码的,但不知道为什么,一直显示上传中断,无法上传,有需要的朋友可以联系我。