个人较少接触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设备相对简单,就这样了,午觉了