HID、SCSI、CCID设备的通信

个人较少接触HID、SCSI设备相关方向的通信,近期接触到几个这类项目,完成后写点心得体会,个人观点,如果有误,敬请指正:

1、HID设备通信

     代码开始都是从列举HID设备开始的,中间应用函数FilterDeviceHID(hKey)来过滤掉不符合条件的HID设备,过滤条件是通过HID设备的PIDVID值比较,废话不多说,代码贴上,但只是部分代码,我的项目是MFC工程,条件有限:

	GUID    HID_Guid;
	HidD_GetHidGuid( &HID_Guid );
	hDevInfo = SetupDiGetClassDevs( &HID_Guid, NULL, NULL, DIGCF_PRESENT|DIGCF_INTERFACEDEVICE);
	if(hDevInfo != INVALID_HANDLE_VALUE)
	{
		SP_INTERFACE_DEVICE_DATA DevData;
		PSP_INTERFACE_DEVICE_DETAIL_DATA_A pDetail;
		DWORD dwIndex, dwLength;
		HANDLE hKey;
		CHAR szCardId[MAX_CARDID_STRING_LEN+1];
		for(dwIndex = 0;;)
		{
			DevData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
			if(!SetupDiEnumDeviceInterfaces(hDevInfo,NULL,&HID_Guid,dwIndex,&DevData))
				break;
			dwIndex++;
			// Get detail length
			if(SetupDiGetDeviceInterfaceDetail(hDevInfo,&DevData,NULL,0,&dwLength,NULL))
				break;

			// Must be error code:ERROR_INSUFFICIENT_BUFFER
			if(ERROR_INSUFFICIENT_BUFFER!=GetLastError())
				break;

			pDetail=(PSP_INTERFACE_DEVICE_DETAIL_DATA_A) new BYTE[dwLength];
			if(NULL==pDetail) {
				dwResult=RESULT_NO_MEMORY;
				break;
			}

			pDetail->cbSize=sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA_A);
			// Get detail
			if(!SetupDiGetDeviceInterfaceDetail(hDevInfo,&DevData,pDetail,dwLength,NULL,NULL)) {
				delete [] pDetail;
				break;
			}

			// Open key
			hKey=CreateFile(pDetail->DevicePath,GENERIC_READ|GENERIC_WRITE,
				FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
			if(INVALID_HANDLE_VALUE==hKey) {
				delete [] pDetail;
				continue;
			}

			// Filter device
			if(!FilterDeviceHID(hKey)) {
//				ReleaseMutex(hMutex);
//				CloseHandle(hMutex);
				CloseHandle(hKey);
				continue;
			}
#if 0
			// Get CardId
			if(SAR_OK!=GetCardId(hKey,szCardId)) {
//				ReleaseMutex(hMutex);
//				CloseHandle(hMutex);
				CloseHandle(hKey);
				continue;
			}
#endif
//			ReleaseMutex(hMutex);
//			CloseHandle(hMutex);

			MoveMemory(pDevPath, pDetail->DevicePath, strlen(pDetail->DevicePath)+1);
			CloseHandle(hKey);

2、在上方代码的末尾,我把设备路径拷贝出来作为返回值,因为只针对一个HID设备操作,如果对多个HID设备操作,那么定义一个全局的数组,数组类型为结构体变量,结构体中的成员为数组,数组用来保存设备路径,这样就OK了。过滤函数实现之前也说了,根据PIDVID值,这里就不详述了,都懂的。

3、找到对应的HID设备的路径,也就获得了对应的设备句柄。将指令传到指定的缓冲区,调用函数ExcuteHIDAPDUInterface(IN hKey, IN (BYTE*)&tCmdBuf, (IN) 40,  OUT pCmdOut, OUT &pCmdOutLen),完成指令响应的操作,后两个参数为响应内容及其响应内容的长度,40即为指令的长度。该函数内部,包括两个函数,发送指令函数和接收响应函数,下面贴出的是发送指令函数的具体实现方法:

#define HID_LEN_FOR_COMM 0x850
#define HID_EFFECITIVE_DATA_LEN 0x850-2
BOOL HidUsbSendData(
				 IN HANDLE hKey,
				 IN BYTE *pbData,
				 IN ULONG dwDataLen)
{
	BOOL bRet;
	BYTE bOutputReport[HID_LEN_FOR_COMM];
	memset(bOutputReport, 0, HID_LEN_FOR_COMM);
	WORD wOffset;

	//ULONG nLen = 32;
	if(dwDataLen < HID_EFFECITIVE_DATA_LEN)
	{
		bOutputReport[0] = 0x06;
		bOutputReport[1] = 0;
		MoveMemory(bOutputReport+2, pbData, dwDataLen);
		
		bRet = HidD_SetFeature(hKey, bOutputReport, dwDataLen+8);
		if(!bRet)
			return FALSE;
		return TRUE;
	}
	return FALSE;
}

因为只针对一个HID设备的通信,所以也就没有关于ReportId数组的定义,直接指定,同时,代码也只针对一段代码传输,没有写出分段数据的传输,最好在原有的代码上,在HidD_SetFeature后面加个for循环,如果第一次执行HidD_SetFeature函数失败,在做几次判断,即为for循环调用HidD_SetFeature函数,如果失败就失败了,调用GetLastError自己去检查吧。

下面是获取响应,及其响应后判断是否响应成功的判断,项目有关,有些是领导如此定义的,就啊按照这种规则定义,且看且领悟吧:

	bOutputReport[0] = 0x2f;
	
	bRet = HidD_GetFeature(hKey, bOutputReport, HID_LEN_FOR_COMM);
	if(!bRet)
	{
		int i = 0;
		for(i = 0; i < 40; i++)
		{
			bRet = HidD_GetFeature(hKey, bOutputReport, HID_LEN_FOR_COMM);
			if(bRet == TRUE)
				break;
		}
		if( i == 40)
			return FALSE;
	}
	//	if(bOutputReport[0] != 0x01)
	//	return FALSE;
	if(bOutputReport[4] != 0 || bOutputReport[5] != 0)
		return FALSE;
	wRecvLen = MAKEWORD(bOutputReport[3], bOutputReport[2]);
	if(pdwDataLen)
		*pdwDataLen = wRecvLen;
	if(pbData)
		MoveMemory(pbData, bOutputReport+6, wRecvLen-2);

	return TRUE;


以上是HID设备的通信,因为只贴出部分代码,如果不理解,留言解答吧,敬请谅解。

4、SCSI设备通信

       当然也是从列举SCSI设备开始了,注意和HID设备列举不同的右两点:F:GUID 。S:过滤。至于GUID,HID设备是通过HidD_GetHidGuid函数来获取GUID的值的,但是SCSI设备指定GUID的值为GUID_DEVINTERFACE_CDROM。过滤的不同是HID是通过比较PIDVID值进行比较,而SCSI是通过传输SCSI指令进行判断的。具体代码贴上,框架贴出,部分函数看不见的:

	hDevInfo=SetupDiGetClassDevs(&GUID_DEVINTERFACE_CDROM,NULL,NULL,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
	if(INVALID_HANDLE_VALUE!=hDevInfo) {
		SP_INTERFACE_DEVICE_DATA DevData;
		PSP_INTERFACE_DEVICE_DETAIL_DATA_A pDetail;
		DWORD dwIndex,dwLength;
		HANDLE hKey;
		//HANDLE hMutex;
		CHAR szCardId[MAX_CARDID_STRING_LEN+1];

		for(dwIndex=0;;) {
			// Enum device interface
			DevData.cbSize=sizeof(SP_INTERFACE_DEVICE_DATA);
			if(!SetupDiEnumDeviceInterfaces(hDevInfo,NULL,&GUID_DEVINTERFACE_CDROM,dwIndex,&DevData))
				break;

			++dwIndex;
			// Get detail length
			if(SetupDiGetDeviceInterfaceDetail(hDevInfo,&DevData,NULL,0,&dwLength,NULL))
				break;

			// Must be error code:ERROR_INSUFFICIENT_BUFFER
			if(ERROR_INSUFFICIENT_BUFFER!=GetLastError())
				break;

			pDetail=(PSP_INTERFACE_DEVICE_DETAIL_DATA_A) new BYTE[dwLength];
			if(NULL==pDetail) {
				dwResult=RESULT_NO_MEMORY;
				break;
			}

			pDetail->cbSize=sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA_A);
			// Get detail
			if(!SetupDiGetDeviceInterfaceDetail(hDevInfo,&DevData,pDetail,dwLength,NULL,NULL)) {
				delete [] pDetail;
				break;
			}

			// Open key
			hKey=CreateFile(pDetail->DevicePath,GENERIC_READ|GENERIC_WRITE,
				FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
			if(INVALID_HANDLE_VALUE==hKey) {
				delete [] pDetail;
				continue;
			}
#if 0
			// Create io mutex
			hMutex=CreateIoMutex(pDetail->DevicePath);
			delete [] pDetail;
			if(NULL==hMutex) {
				CloseHandle(hKey);
				continue;
			}

			WaitForSingleObject(hMutex,INFINITE);
#endif
			// Filter device
			if(!FilterDevice(hKey)) {
//				ReleaseMutex(hMutex);
//				CloseHandle(hMutex);
				CloseHandle(hKey);
				continue;
			}

#ifdef __SYNO_DEV_DEBUG_
			BYTE bSetSerialNumIns[1024],bRespond[1024];
			DWORD dwLen,dwResLen,dwRet;
			ZeroMemory(bSetSerialNumIns,1024);
			ZeroMemory(bRespond,1024);
			//800201 00 1000 0004 4c696e67756f20435350204170703131
			bSetSerialNumIns[0]=0x80;
			bSetSerialNumIns[1]=0x02;
			bSetSerialNumIns[2]=0x01;
			bSetSerialNumIns[4]=0x10;
			bSetSerialNumIns[7]=0x04;
			MoveMemory(bSetSerialNumIns+8,"0000000000000001",16);
			dwLen=16+8;
			dwRet=ExcuteAPDUInterface(hKey,bSetSerialNumIns,dwLen,bRespond,&dwResLen);
#endif
			//获取设备信息即获得卡ID可以不加,加上也不影响
#if 1
			// Get CardId
			if(SAR_OK!=GetCardId(hKey,szCardId)) {
//				ReleaseMutex(hMutex);
//				CloseHandle(hMutex);
				CloseHandle(hKey);
				continue;
			}

//			ReleaseMutex(hMutex);
//			CloseHandle(hMutex);
#endif
			MoveMemory(pDevPath, pDetail->DevicePath, strlen(pDetail->DevicePath)+1);
			CloseHandle(hKey);

5、同样在末尾,我把SCSI设备路径导出作为返回值,并关闭相应的有效的句柄,过滤函数的具体实现省略,具体实现通过介绍,大家都懂的,也该知道具体实现方式了。获得设备句柄之后就是指令的发送和响应的接收。同样是发送指令和接收响应的两个函数。先贴发送指令函数的具体实现,函数声明为BOOL UsbSendData(IN HANDLE hKey, IN BYTE *pbData, IN ULONG dwDataLen)代码如下:

	if(dwDataLen < 8)
		return FALSE;
	dwDataLen -= 8;
	//LC = ((UINT16)pbData[4] << 8) + pbData[5];
	//LE = ((UINT16)pbData[6] << 8) + pbData[7];
	sendBuf = &pbData[8];

	memset(&sptdwb,0,sizeof(sptdwb));
	sptdwb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);        
	sptdwb.sptd.PathId = 0;        
	sptdwb.sptd.TargetId = 1;        
	sptdwb.sptd.Lun = 0;        
	sptdwb.sptd.CdbLength = 10;        				//SCSI命令长度 
	sptdwb.sptd.DataIn = SCSI_IOCTL_DATA_OUT;        //表示SCSI命令是要写数据
	sptdwb.sptd.SenseInfoLength = 24;        
	sptdwb.sptd.DataTransferLength = dwDataLen;        //传输的数据长度
	sptdwb.sptd.TimeOutValue = 90;        			// 200秒的超时
	sptdwb.sptd.DataBuffer = sendBuf;				//发送或接收的数据缓冲区
	sptdwb.sptd.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER,ucSenseBuf);
	sptdwb.sptd.Cdb[0] = 0xef;						//SCSI
	sptdwb.sptd.Cdb[1] = 0x01;						//WRITE
	sptdwb.sptd.Cdb[2] = pbData[0];					//CLA
	sptdwb.sptd.Cdb[3] = pbData[1];					//INS
	sptdwb.sptd.Cdb[4] = pbData[2];					//P1
	sptdwb.sptd.Cdb[5] = pbData[3];					//P2

	//lc
	sptdwb.sptd.Cdb[6]=pbData[4];
	sptdwb.sptd.Cdb[7]=pbData[5];

	//le
	sptdwb.sptd.Cdb[8]=pbData[6];
	sptdwb.sptd.Cdb[9]=pbData[7];//(BYTE)(LE);
	//U16_TO_BIG(LC,&sptdwb.sptd.Cdb[6]);				//LC
	//U16_TO_BIG(LE,&sptdwb.sptd.Cdb[8]);				//LE
	//memcpy(&sptdwb.sptd.Cdb[6],&LC,2);
	//memcpy(&sptdwb.sptd.Cdb[8],&LE,2);
	length = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER);
	iRet = DeviceIoControl(hKey,IOCTL_SCSI_PASS_THROUGH_DIRECT,&sptdwb,length,&sptdwb,length,&bytesReturn,NULL);

具体各个参数的值,是与底层系统协商好的。

现在贴出接收响应函数的代码,函数原型为BOOL UsbReceiveData(IN HANDLE hKey, OUT BYTE *pbData, OUT ULONG *pdwDataLen),代码如下:

	memset(&sptdwb,0,sizeof(sptdwb));
	sptdwb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);        
	sptdwb.sptd.PathId = 0;        
	sptdwb.sptd.TargetId = 1;        
	sptdwb.sptd.Lun = 0;        
	sptdwb.sptd.CdbLength = 10;        				//SCSI命令长度 
	sptdwb.sptd.DataIn = SCSI_IOCTL_DATA_IN;        //表示SCSI命令是要读数据
	sptdwb.sptd.SenseInfoLength = 24;        
	sptdwb.sptd.DataTransferLength = 2148;//传输的数据长度
	sptdwb.sptd.TimeOutValue = 90;        			// 200秒的超时
	sptdwb.sptd.DataBuffer = pbData;					//发送或接收的数据缓冲区
	sptdwb.sptd.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER,ucSenseBuf);
	sptdwb.sptd.Cdb[0] = 0xef;						//SCSI
	sptdwb.sptd.Cdb[1] = 0x02;						//READ
	length = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER);
	iRet = DeviceIoControl(hKey,IOCTL_SCSI_PASS_THROUGH_DIRECT,&sptdwb,length,&sptdwb,length,&bytesReturn,NULL);
	if(iRet == 0 || sptdwb.sptd.DataTransferLength == 0)
		return FALSE;
	*pdwDataLen = (UINT16)sptdwb.sptd.DataTransferLength;


至此,关于HID和SCSI设备的通信已经介绍完毕,抛砖引玉,见笑了!

6、CCID即智能读卡器的通信,这一部分是时隔一段时间补上的,最近做了一个CCID通信的项目,完成后补上,如果有什么不正确的地方,希望大家指正,谢谢。话不多说,

我就直接贴上代码,代码中讲解吧:

	retVal = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &ghContext);
	if(retVal != SCARD_S_SUCCESS)
		return FALSE;

	retVal = SCardListReaders(ghContext, NULL, NULL, &dwReaders);
	if(retVal != SCARD_S_SUCCESS)
		return FALSE;

	mszReaders = (LPTSTR)malloc(dwReaders);
	retVal = SCardListReaders(ghContext, NULL, mszReaders, &dwReaders);
	if(retVal != SCARD_S_SUCCESS)
	{
		free(mszReaders);
		mszReaders = NULL;
		return FALSE;
	}

	totalLen = dwReaders;
	while(tmpLen < totalLen)
	{
		if(mszReaders[tmpLen] == NULL)
			break;
		strcpy_s(gUkeyInfo[index].ukeyPathName, strlen((const char*)&mszReaders[tmpLen])+1, (const char*)&mszReaders[tmpLen]);
		if(strstr(gUkeyInfo[index].ukeyPathName,VID_PID_INFO) != NULL)
			index++;
		else
			ZeroMemory(gUkeyInfo[index].ukeyPathName, sizeof(gUkeyInfo[index].ukeyPathName));
		tmpLen += strlen((const char *)&mszReaders[tmpLen]) + 1;
	}
	if(mszReaders != NULL)
	{
		free(mszReaders);
		mszReaders = NULL;
	}
	if(gUkeyInfo[0].ukeyPathName[0] == '\0')
	{
		MessageBox("没有找到指定的设备");
		return TRUE;
	}
	retVal = SCardConnect(ghContext, (LPCTSTR)gUkeyInfo[0].ukeyPathName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, &ghCard, &gActiveProtocol);
	if(retVal != SCARD_S_SUCCESS)
	{
		SCardReleaseContext(ghContext);
		return FALSE;
	}
	gCurChannelNum = 0;

	DWORD					dwRecvLength;
	SCARD_IO_REQUEST		pioSendPci;
	pioSendPci.dwProtocol = gActiveProtocol;
	pioSendPci.cbPciLength = sizeof(SCARD_IO_REQUEST);
	dwRecvLength = sizeof(recvBuf);

	BYTE command[9];
	ZeroMemory(command, sizeof(command));
	command[0] = gCurChannelNum;
	command[1] = 0x80;
	command[2] = 0x04;
	command[8] = (BYTE)sizeof(DEVINFO);

	retVal = SCardTransmit(ghCard, &pioSendPci, command, sizeof(command), NULL, recvBuf, &dwRecvLength);
	if(retVal != SCARD_S_SUCCESS)
		return FALSE;

代码首处调用了SCardEstablishContext函数,用以建立在用户的域或者系统的域中操作数据库的上下文环境。如之上的代码显示的是在用户的域中,此步完成之后,就可以在

用户的域中枚举CCID设备,代码中显示两次调用SCardListReaders函数,第一次调用返回存储CCID设备VIDPID信息的长度,第二次返回才是将CCID设备信息存储在缓冲区中

,这个函数各个形参的意思自行在MSDN中查询,不在赘述。在while循环中,淘汰不符合我要找设备的那些,我找的设备的信息中包含VID_PID_INFO。特别注意的是,该函数

返回的设备信息的存储方式:各个设备之间以'\0'分开,最后的设备以'\0''\0'结尾。参照我取设备信息然后存储在另外的缓冲区的方法。传输命令的手字节为通道号,我设置为0

,通信的函数为SCardTransmit,具体形参的意思,自己查看MSDN。说道这里了,CCID设备通信要比SCSI、HID设备相对简单,就这样了,午觉了






评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值