window环境下,如何通过USB接口控制打印机

发布于:2025-05-11 ⋅ 阅读:(25) ⋅ 点赞:(0)

虽然说大多数情况下,我们可以非常便利的通过打印机驱动来控制打印机,但还是有一些特殊情况,导致无法通过打印机驱动来完成我们预想的任务,比如,打印机只是一个系统设备中的一部分,需要协调其它设备一起工作时,如果只是通过打印机驱动来完成打印任务,就很难与系统中的其它设备完美协调。

那么,我们应该如何解决这种问题呢?一、开发特定的打印机驱动来配合;二、定制专用的Firmware,增加特殊的控制指令,通过USB端口来控制打印机的打印并实时获取打印机当前的工作状态,从而实现完美配合系统其它设备的功能。

那么,新的问题来了,我们应该选择方案一还是方案二呢?其实这个问题,不难选择,大多数情况下,有这种需求的打印机,就不是一款通用形的打印机,而是一款定制的或专用于某个领域的打印机,也就意味着,这种打印机本就是定制的,从硬件到Firmware,都是定制的,所以,显然选择方案二是最合适的。

新问题又有了,我们如何才能通过USB端口控制打印机呢?回答这个问题之前,我们先介绍一下window系统下usb设备的类型。

一、usb设备的类型

USB设备类型根据功能和应用场景可分为以下几大类:

一)、常用设备类

  1. HID(人机接口设备)

    • 用途:用于人与计算机交互的输入设备
    • 示例:键盘、鼠标、游戏手柄
    • 协议特征:支持低速/全速模式,兼容性强
  2. MSC(大容量存储设备)

    • 用途:数据传输与存储
    • 示例:U盘、移动硬盘、SD卡读卡器
    • 协议速度:USB 2.0最高支持480Mbps
  3. CDC(通信设备类)

    • 用途:串行通信与网络连接
    • 示例:调制解调器、网络摄像头
    • 应用场景:虚拟COM端口、数据透传
  4. Audio Class(音频设备类)

    • 用途:音频输入/输出
    • 示例:USB麦克风、耳机、MIDI设备
    • 应用特点:支持音频流传输与处理
  5. Video Class(视频设备类,UVC)

    • 用途:视频捕捉与传输
    • 示例:网络摄像头、视频采集卡
    • 协议优势:标准化视频传输协议

二)、其他设备类

  1. Printer Class(打印机类)
    • 用途:打印机控制与数据传输
  2. PTP(图像传输协议)
    • 用途:相机、扫描仪等图像设备的数据传输
  3. 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 应用场景

  1. 驱动匹配与加载

    • Windows 系统通过设备接口类 GUID 自动加载对应驱动程序(如 usbprint.sys 驱动绑定 GUID_DEVINTERFACE_USBPRINT)。
    • 若 GUID 与驱动注册不匹配,设备管理器会显示未知设备或错误代码(如 43)。
  2. 设备枚举与管理

    • 使用 API SetupDiGetClassDevs 时需指定 GUID 来筛选设备(示例:DIGCF_DEVICEINTERFACE | DIGCF_PRESENT 枚举已连接的 USB 打印机)。
    • 设备路径(如 \\?\USB#VID_xxxx&PID_xxxx#...)中隐含 GUID 信息,用于底层通信。

四)、GUID 查看与调试方法

  1. 注册表查看

    • 设备接口类 GUID‌:通过 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses 分支查询。
    • 设备安装类 GUID‌:在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class 下按设备类别检索。
  2. 设备管理器调试

    • 右键设备 → ‌属性‌ → ‌详细信息‌ → 选择 ‌设备类 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 的编程方法

  1. 设备枚举核心代码
     

    #include <SetupAPI.h>
    #include <initguid.h>
    #include <Usbiodef.h>
    
    HDEVINFO hDevInfo = SetupDiGetClassDevs(
        &GUID_DEVINTERFACE_USBPRINT,  // 指定打印机接口 GUID
        NULL,
        NULL,
        DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
    );
    

    通过 SetupDiGetClassDevs 函数可获取所有已连接的 USB 打印机设备实例。

  2. 设备路径提取

    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;
}

本来想上传完整源代码的,但不知道为什么,一直显示上传中断,无法上传,有需要的朋友可以联系我。


网站公告

今日签到

点亮在社区的每一天
去签到