雖然說大多數情況下,我們可以非常便利的通過打印機驅動來控制打印機,但還是有一些特殊情況,導致無法通過打印機驅動來完成我們預想的任務,比如,打印機只是一個系統設備中的一部分,需要協調其它設備一起工作時,如果只是通過打印機驅動來完成打印任務,就很難與系統中的其它設備完美協調。
那么,我們應該如何解決這種問題呢?一、開發特定的打印機驅動來配合;二、定制專用的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;
}
本來想上傳完整源代碼的,但不知道為什么,一直顯示上傳中斷,無法上傳,有需要的朋友可以聯系我。