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)代码如下:

扫描二维码关注公众号,回复: 1577842 查看本文章
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),代码如下:

[cpp] view plain copy
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通信的项目,完成后补上,如果有什么不正确的地方,希望大家指正,谢谢。话不多说,

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

[cpp] view plain copy
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设备相对简单,就这样了,午觉了

猜你喜欢

转载自blog.csdn.net/zy1049677338/article/details/80402733
今日推荐