Windows下USB HID 设备枚举读写操作

时间:2025-02-23 08:42:27

一、USB HID 设备枚举操作

本文需求是通过USB HID的pid,vid,pvn(bcdDevice:用于标识USB当前产品的固件版本号)去寻找HID设备,然后进行数据传输操作。我也忘记当时数据是不是正常的了,反正下位机是有问题的后面改回来,客户上位机通信是ok,只是这个程序后续未验证了,但实现方式是这样的,区别也就是下面writefile的数据第一个字节的问题,给自己留个bug也行
在Win下面通过SetupDi系列函数进行枚举USB设备
BOOL Enum(USHORT m_Pid, USHORT m_Vid, USHORT m_Pvn, char* str, int* len)
	DWORD DeviceNum = 0;
	GUID hidGuid;
	//获取HID设备的GUID
	::HidD_GetHidGuid((LPGUID)&hidGuid);

	HDEVINFO hDevInfoList = SetupDiGetClassDevs(&hidGuid, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
	if (hDevInfoList != NULL)
	{
		SP_DEVICE_INTERFACE_DATA deviceInfoData;

		// Clear data structure
		ZeroMemory(&deviceInfoData, sizeof(deviceInfoData));
		deviceInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		SetLastError(NO_ERROR);
		while (1)
		{
			int ret = GetLastError();
			if (ret == ERROR_NO_MORE_ITEMS)
			{
				break;
			}
			ZeroMemory(&deviceInfoData, sizeof(deviceInfoData));
			deviceInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
			// retrieves a context structure for a device interface of a device information set.
			if (SetupDiEnumDeviceInterfaces(hDevInfoList, 0, &hidGuid, DeviceNum, &deviceInfoData))
			{
				// Must get the detailed information in two steps
				// First get the length of the detailed information and allocate the buffer
				// retrieves detailed information about a specified device interface.
				PSP_DEVICE_INTERFACE_DETAIL_DATA     functionClassDeviceData = NULL;
				ULONG  predictedLength, requiredLength;

				predictedLength = requiredLength = 0;
				SetupDiGetDeviceInterfaceDetail(hDevInfoList,
					&deviceInfoData,
					NULL,			// Not yet allocated
					0,				// Set output buffer length to zero 
					&requiredLength,// Find out memory requirement
					NULL);

				predictedLength = requiredLength;
				functionClassDeviceData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(predictedLength);
				functionClassDeviceData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

				SP_DEVINFO_DATA did = { sizeof(SP_DEVINFO_DATA) };

				// Second, get the detailed information
				if (SetupDiGetDeviceInterfaceDetail(hDevInfoList,
					&deviceInfoData,
					functionClassDeviceData,
					predictedLength,
					&requiredLength,
					&did))
				{
					TCHAR fname[256];

					// Try by friendly name first.
					if (!SetupDiGetDeviceRegistryProperty(hDevInfoList, &did, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname, sizeof(fname), NULL))
					{	// Try by device description if friendly name fails.
						if (!SetupDiGetDeviceRegistryProperty(hDevInfoList, &did, SPDRP_DEVICEDESC, NULL, (PBYTE)fname, sizeof(fname), NULL))
						{	// Use the raw path information for linkname and friendlyname
							strncpy_s(fname, 256, (char*)functionClassDeviceData->DevicePath, 256);
						}
					}


					HANDLE UdiskDevice = CreateFile(functionClassDeviceData->DevicePath,
						GENERIC_READ | GENERIC_WRITE,
						FILE_SHARE_READ | FILE_SHARE_WRITE,
						NULL,
						OPEN_EXISTING,
						0,
						NULL);



					//=============== Get Attribute ===============
					HIDD_ATTRIBUTES Attributes;
					ZeroMemory(&Attributes, sizeof(Attributes));
					Attributes.Size = sizeof(HIDD_ATTRIBUTES);
					if (!HidD_GetAttributes(UdiskDevice, &Attributes))
					{
						CloseHandle(UdiskDevice);
						DeviceNum++;
						continue;
					}
					if (Attributes.ProductID == m_Pid && Attributes.VendorID == m_Vid
						&& Attributes.VersionNumber == m_Pvn)
					{
						//Save Device Path
						CString m_linkname = functionClassDeviceData->DevicePath;
						memcpy(str, m_linkname.GetBuffer(), m_linkname.GetLength());
						*len = m_linkname.GetLength();
						free(functionClassDeviceData);
						SetupDiDestroyDeviceInfoList(hDevInfoList);
						return TRUE;
					}

					free(functionClassDeviceData);
					CloseHandle(UdiskDevice);
					UdiskDevice = INVALID_HANDLE_VALUE;
				}
				DeviceNum++;
			}
		}
	}

	// SetupDiDestroyDeviceInfoList() destroys a device information set
	// and frees all associated memory.
	SetupDiDestroyDeviceInfoList(hDevInfoList);
		return FALSE;
}

二、USB HID写操作

void CCoreUSBHIDCommToolDlg::OnBnClickedBtnSend()
{
	CString Cstr;
	int Len;
	UINT16 OutLength = 0;
	UINT8 OutData[4096] = { 0 };
	UINT8 InputData[1024] = { 0 };
	PHIDP_PREPARSED_DATA PreparsedData;
	HIDP_CAPS Capabilities;
	CString DevicePath = m_USBPath;

	UpdateData(TRUE);
	Cstr = m_SendInfo;
	Cstr.Remove(' ');

	Len = Cstr.GetLength();
	memcpy(InputData, Cstr.GetBuffer(), Len);
	if ((Len == 0) || (Len % 2 != 0))
	{
		AfxMessageBox("Input Data Error!");
		return;
	}
	if (AsciiToHex(InputData, Len, OutData, &OutLength) == FALSE)
	{
		AfxMessageBox("Input Data Error!");
		return;
	}
	unsigned long NumberOfBytesWriten = 0;
	if (hWriteCom == INVALID_HANDLE_VALUE)
	{
		AfxMessageBox("Comm Fail...");
		return;
	}
	OVERLAPPED    Overlapped;

	Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	Overlapped.Offset = 0;
	Overlapped.OffsetHigh = 0;

	SetLastError(NO_ERROR);
	int WriteTimeout = 100;
	int status = 1;
	BOOL bRet = WriteFile(hWriteCom, OutData, WriteLen, &NumberOfBytesWriten, &Overlapped);
	if (bRet)
	{
		status = 0;
	}
	if (status != 0)
	{
		DWORD CommLastErrCode = GetLastError();
		CloseHandle(hWriteCom);
		hWriteCom = INVALID_HANDLE_VALUE;
		CString strDate;
		SYSTEMTIME st;
		GetLocalTime(&st);
		strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
		((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("Cannot Write Data..." + strDate + "\r\n");
		return;
	}
	AfxBeginThread(UsbRunReadInfo, this);
}

三、USB HID读操作

UINT  CCoreUSBHIDCommToolDlg::UsbRunReadInfo(void* pParam)
{
	CCoreUSBHIDCommToolDlg* pData = (CCoreUSBHIDCommToolDlg*)pParam;
	CString DevicePath = pData->m_USBPath;

	BOOL result1;
	unsigned long numBytesReturned;
	


	int Imglen = 0;
	CString str = NULL;
	clock_t start, end, final = 0;
	pData->SetDlgItemText(IDC_EDIT_Recv, "");
	if (pData->hReadCom == INVALID_HANDLE_VALUE)
	{
		AfxMessageBox("Comm Fail...");
		return 1;
	}
	CFile cf;
	FILE* fp;
	if (!(fp = fopen("", "wb")))
	{
		return 0;
	}
	if (!cf.Open(_T(""), CFile::modeCreate | CFile::modeWrite))
	{
		AfxMessageBox(_T(""));
		return 0;
	}
	OVERLAPPED    Overlapped;
	DWORD  Flag;
	Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	Overlapped.Offset = 0;
	Overlapped.OffsetHigh = 0;
	int status = 1;
	int cusumlen = 0;
	int OffsetAddr = 0;//偏移地址
	byte inbuffer[4096] = { 0 };
	clock_t	dwStart = clock();
	clock_t	dwEnd;
	UINT8* Startptr = RecvBuffer;
	//第一位数据是报ID,无效参数
	while (ReadFile(pData->hReadCom, Startptr, pData->ReadLen, &numBytesReturned, &Overlapped))
	{
		memcpy(&RecvBufferNoID[OffsetAddr], &Startptr[1], (numBytesReturned - 1));
		*Startptr = *(Startptr + (numBytesReturned - 1));
		OffsetAddr += (numBytesReturned - 1);
		cusumlen += numBytesReturned;
		if (OffsetAddr >= sumlen)
		{
			break;
		}
		dwEnd = clock();
		if (dwEnd - dwStart > 5000)
		{
			break;
		}
	}
	dwEnd = clock();
	if (OffsetAddr != sumlen)
	{
		CString strDate;
		SYSTEMTIME st;
		GetLocalTime(&st);
		strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);

		((CEdit*)pData->GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("Read Data Timeout..." + strDate + "\r\n");
		CloseHandle(pData->hReadCom);
		pData->hReadCom = INVALID_HANDLE_VALUE;
		fclose(fp);
		cf.Close();
		return 0;
	}
	int length = 0;
	int i = 0;
	for (i = 0; i < sumlen; i++)
	{
		CString Tmp;
		Tmp.Format("%02X", RecvBufferNoID[i]);
		str += Tmp;
		if ((i - 1) % 53 == 0 && i != 0)
		{
			str += "\r\n";
		}
	}
	if ((i - 1) % 53 != 0)
	{
		str += "\r\n";
	}
	fwrite(RecvBufferNoID, sizeof(unsigned char), sumlen, fp);
	fclose(fp);
	//DWORD	dwEnd = GetTickCount();
	CString ss;
	ss.Format("时间为%d ms", dwEnd - dwStart);
	str += ss;
	((CEdit*)(pData->GetDlgItem(IDC_EDIT_Recv)))->SetWindowText(str);
	((CEdit*)(pData->GetDlgItem(IDC_EDIT_Recv)))->SetSel(str.GetLength(), str.GetLength());
	((CEdit*)(pData->GetDlgItem(IDC_EDIT_Recv)))->ReplaceSel("\r\n");
	int len = str.GetLength();
	cf.Write(str.GetBuffer(len), len);
	cf.Close();

	FILE* fpbmp; 
	fpbmp = fopen("", "wb");
	if (fpbmp == NULL)
	{
		fclose(fpbmp);
		return FALSE;
	}
	*((int*)&bmpDummy[18]) = width;
	*((int*)&bmpDummy[22]) = High;
	fwrite(bmpDummy, 1078, sizeof(char), fpbmp);
	fwrite(RecvBufferNoID, width * High, sizeof(unsigned char), fpbmp);
	fclose(fpbmp);
	return 0;
}

四、当时封装的其他函数

/******************************************************************************************************
** 函数名称:	AsciiToHex
** 功能描述:	ASCII 到 HEX 的转换函数
** 形    参:  	UINT8  *_pInData   ,传入的ASCII数据缓冲,该缓冲在内部会被该变
**              UINT16 _nInLength  ,*_pInData缓冲区的长度
**              UINT8  *_pOutData  ,存放HEX数据的缓冲
**              UINT16 *_nOutLength,存放HEX数据的长度
** 返	 回:    true 转换成功,false 转换失败
********************************************************************************************************/
BOOL AsciiToHex(UINT8* _pInData, UINT16 _nInLength, UINT8* _pOutData, UINT16* _nOutLength)
{
	UINT16 i, j, nLen;
	UINT8 TmpData;
	for (i = 0; i < _nInLength; i++)
	{
		if ((_pInData[i] >= '0') && (_pInData[i] <= '9'))
			TmpData = _pInData[i] - '0';
		else if ((_pInData[i] >= 'A') && (_pInData[i] <= 'F')) //A....F
			TmpData = _pInData[i] - 0x37;
		else if ((_pInData[i] >= 'a') && (_pInData[i] <= 'f'))  //a....f
			TmpData = _pInData[i] - 0x57;
		else
			return FALSE;
		_pInData[i] = TmpData;
	}
	for (nLen = 0, j = 0; j < i; j += 2)
		_pOutData[nLen++] = (_pInData[j] << 4) | _pInData[j + 1];

	*_nOutLength = nLen;
	return TRUE;
}

void CCoreUSBHIDCommToolDlg::OnBnClickedBtnOpen()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str, Tstr, strDate;
	USHORT m_Pid, m_Vid, m_Pvn;
	GetDlgItemText(IDC_EDIT_PID, str);
	if (str.IsEmpty())
	{
		AfxMessageBox("PID 输入为空!!!");
		return;
	}
	m_Pid = strtoull(str, NULL, 16);
	GetDlgItemText(IDC_EDIT_VID, str);
	if (str.IsEmpty())
	{
		AfxMessageBox("VID 输入为空!!!");
		return;
	}
	m_Vid = strtoull(str, NULL, 16);
	GetDlgItemText(IDC_EDIT_PVN, str);
	if (str.IsEmpty())
	{
		AfxMessageBox("PVN 输入为空!!!");
		return;
	}
	m_Pvn = strtoull(str, NULL, 16);
	//获得当前时间,年月日
	SYSTEMTIME st;
	GetLocalTime(&st);
	strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
	((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("正在打开设备,请等待" + strDate + "\r\n");
	char charstr[128] = { 0 };
	int len = 0;
	BOOL ret = Enum(m_Pid, m_Vid, m_Pvn, charstr, &len);
	if (ret == FALSE)
	{
		GetLocalTime(&st);
		memset(m_USBPath, 0, sizeof(char) * 128);
		strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
		((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("打开设备失败未识别到设备" + strDate + "\r\n");
		return;
	}

	memcpy(m_USBPath, charstr, len);
	PHIDP_PREPARSED_DATA PreparsedData;
	HIDP_CAPS Capabilities;

	//得到读句柄/写句柄,及读与写的长度,不然放到读写函数里去实现会导致通信超时
	hWriteCom = CreateFile(
		m_USBPath,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING, 0,
		NULL);

	if (hWriteCom == INVALID_HANDLE_VALUE)
	{
		AfxMessageBox("Comm Fail...");
		return ;
	}
	if (!HidD_GetPreparsedData(hWriteCom, &PreparsedData))
	{
		CloseHandle(hWriteCom);
		CString strDate;
		SYSTEMTIME st;
		GetLocalTime(&st);
		strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
		((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("Cannot get the Preparsed Data..." + strDate + "\r\n");
		return ;
	}

	if (!HidP_GetCaps(PreparsedData, &Capabilities))
	{
		CloseHandle(hWriteCom);
		CString strDate;
		SYSTEMTIME st;
		GetLocalTime(&st);
		strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
		((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("Cannot get the Cap Data..." + strDate + "\r\n");
		return ;
	}
	WriteLen = Capabilities.OutputReportByteLength;
	hReadCom = CreateFile(
		m_USBPath,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING, 0,
		NULL);

	if (hReadCom == INVALID_HANDLE_VALUE)
	{
		AfxMessageBox("Comm Fail...");		
		CloseHandle(hWriteCom);
		hWriteCom = INVALID_HANDLE_VALUE;
		return;
	}
	if (!HidD_GetPreparsedData(hReadCom, &PreparsedData))
	{
		CloseHandle(hWriteCom);
		CloseHandle(hReadCom);
		hWriteCom = INVALID_HANDLE_VALUE;
		hReadCom = INVALID_HANDLE_VALUE;
		CString strDate;
		SYSTEMTIME st;
		GetLocalTime(&st);
		strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
		((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("Cannot get the Preparsed Data..." + strDate + "\r\n");
		return;
	}

	if (!HidP_GetCaps(PreparsedData, &Capabilities))
	{
		CloseHandle(hWriteCom);
		CloseHandle(hReadCom);
		hWriteCom = INVALID_HANDLE_VALUE;
		hReadCom = INVALID_HANDLE_VALUE;
		CString strDate;
		SYSTEMTIME st;
		GetLocalTime(&st);
		strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
		((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("Cannot get the Cap Data..." + strDate + "\r\n");
		return;
	}
	
	ReadLen = Capabilities.OutputReportByteLength;
	GetLocalTime(&st);
	strDate.Format("%02d-%02d-%02d", st.wHour, st.wMinute, st.wSecond);
	((CEdit*)GetDlgItem(IDC_EDIT_Recv))->ReplaceSel("打开设备成功"+ strDate + "\r\n");
	SaveCfg();
	((CButton*)GetDlgItem(IDC_BTN_Close))->EnableWindow(TRUE);
	((CButton*)GetDlgItem(IDC_BTN_Open))->EnableWindow(FALSE);
	((CButton*)GetDlgItem(IDC_BTN_Send))->EnableWindow(TRUE);
}``