PCSC那事儿(二十--SCardTransmit)

SCardTransmit函数代码解析与相关探讨
本文详细解析了PCSC的SCardTransmit函数实现,涉及参数检查、共享内存处理、APDU的发送与接收,以及在不同情况下的结构体使用。通过源代码分析,阐述了该函数在智能卡通信中的关键作用。
部署运行你感兴趣的模型镜像

 

SCardTransmit

245 SCardTransmit 定义在 winscard_clnt.c

实现如下:

2915 LONG SCardTransmit(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci,

2916         LPCBYTE pbSendBuffer, DWORD cbSendLength,

2917         LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer,

 

2918         LPDWORD pcbRecvLength)

2919 {

 

 

 

问:为什么从代码解说开始,每个函数 API 都不说明参数和用法?

答案是, 这些 API 都是符合 PCSC 规范的。第一章的各卷部分都对应做了说明了。所以不再重复。或者也可以看看源代码的这些函数实现的注释部分,这些参数都有注释说明。注释格式符合 Doxygen 要求。

如果这里重复说明,也就是重复昨天的故事,很罗曼蒂克。一可以增加篇幅,二可以增加工作量,三可以倍显所谓的耐心和专业精神。但此时此地,还是不要。

2920         LONG rv;

2921         int i;

2922         DWORD dwContextIndex, dwChannelIndex;

2923

2924         PROFILE_START

2925

2926         if (pbSendBuffer == NULL || pbRecvBuffer == NULL ||

2927                         pcbRecvLength == NULL || pioSendPci == NULL)

2928                 return SCARD_E_INVALID_PARAMETER;

2929

 

 

 

2920~2929 常规检查。

 

问:上面提到了很多 API 了,实现的前部都有检查。能不能不检查?

答案是,在 c 的世界里,不检查意味着崩溃。意味着用彻夜的调试来消耗生命。不过

目前看,生命没有房价值钱,是吧?在 metaprogram 中可以实现这个想法。如果不接受

检查,那么赶快投入 metaprogram 的怀抱,把运行期的错误转移到编译期。

把一个人的温暖转移到另一个人的胸膛

 

2934         /*

2935          * Make sure this handle has been opened

2936          */

2937         rv = SCardGetIndicesFromHandle(hCard, &dwContextIndex, &dwChannelIndex);

2938         if (rv == -1)

2939         {

2940                 *pcbRecvLength = 0;

2941                 return SCARD_E_INVALID_HANDLE;

2942         }

2943

2944         (void)SYS_MutexLock(psContextMap[dwContextIndex].mMutex);

2945

2946         /* check the handle is still valid */

 

2947         rv = SCardGetIndicesFromHandle(hCard, &dwContextIndex, &dwChannelIndex);

2948         if (rv == -1)

2949                 /* the handle is now invalid

2950                   * -> another thread may have called SCardReleaseContext

2951                  * -> so the mMutex has been unlocked */

2952                 return SCARD_E_INVALID_HANDLE;

2953

 

 

 

 

2934~2953 行,前面讲过了?似乎?是讲解过类似的片段,讲了 n 次。

但这个片段不太一样。

找出以前的片段对比对比。有对比才有发现,有攀比才有仇富,有发现才会警醒。

1872 LONG SCardGetStatusChange(SCARDCONTEXT hContext, DWORD dwTimeout,

1873          LPSCARD_READERSTATE_A rgReaderStates, DWORD cReaders)

 

...

1918         dwContextIndex = SCardGetContextIndice(hContext);

1919         if (dwContextIndex == -1)

1920                 return SCARD_E_INVALID_HANDLE;

1921

1922         (void)SYS_MutexLock(psContextMap[dwContextIndex].mMutex);

1923

1924         /* check the context is still opened */

1925         dwContextIndex = SCardGetContextIndice(hContext);

1926         if (dwContextIndex == -1)

1927                 /* the context is now invalid

1928                  * -> another thread may have called SCardReleaseContext

1929                  * -> so the mMutex has been unlocked */

1930                   return SCARD_E_INVALID_HANDLE;

 

 

 

重要差别在

一个是

2937         rv = SCardGetIndicesFromHandle(hCard, &dwContextIndex, &dwChannelIndex);

 

另一个是

 

 

1925         dwContextIndex = SCardGetContextIndice(hContext);

 

 

 

SCardGetContextIndice 上面讲解过了,从 APPLICATION 大佬的 psContextMap 数组

那里获得索引号。

SCardGetIndicesFromHandle 做什么呢?

 

3740 static LONG SCardGetIndicesFromHandle(SCARDHANDLE hCard,

3741         PDWORD pdwContextIndice, PDWORD pdwChannelIndice)

3742 {

3743         LONG rv;

3744

3745         if (0 == hCard)

3746                  return -1;

3747

3748         (void)SCardLockThread();

3749         rv = SCardGetIndicesFromHandleTH(hCard, pdwContextIndice, pdwChannelIndice);

3750         (void)SCardUnlockThread();

3751

3752         return rv;

3753 }

 

 

 

SCardGetIndicesFromHandle SCardGetIndicesFromHandleTH 加锁并做 wrapper.

还是看看 SCardGetIndicesFromHandleTH.

3755 static LONG SCardGetIndicesFromHandleTH(SCARDHANDLE hCard,

3756         PDWORD pdwContextIndice, PDWORD pdwChannelIndice)

3757 {

3758         int i;

3759

3760         for (i = 0; i < PCSCLITE_MAX_APPLICATION_CONTEXTS; i++)

3761         {

3762                 if (psContextMap[i].hContext != 0)

3763                 {

3764                         int j;

3765

3766                         for (j = 0; j < PCSCLITE_MAX_APPLICATION_CONTEXT_CHANNELS; j++)

3767                         {

3768                                 if (psContextMap[i].psChannelMap[j].hCard == hCard)

3769                                 {

3770                                         *pdwContextIndice = i;

 

3771                                          *pdwChannelIndice = j;

3772                                         return SCARD_S_SUCCESS;

3773                                 }

3774                         }

3775

3776                 }

3777         }

3778

3779          return -1;

3780 }

 

 

 

3755~3780 行,从 psContextMap 大佬那里获知 ContextIndice ChannelIndice.

后面要用到 ContextIndice psContextMap[ContextIndice] 获得应用上下文。

而上下文中维护的读卡器的名字就存在

  psContextMap[dwContextIndex].psChannelMap[dwChannelIndex].readerName

而这个读卡器名字的信息什么时候被维护到上下文中?

SCardAddHandle 的时候,而 SCardAddHanle SCardConnect 中。

明白了吧。

特别说明, psChannelMap 里的每个元素用来代表同一个 psContextMap 元素上下文中,

的一个逻辑连接 (SCardConnect).

 

回头看看

214         rv = SCardConnect(hContext, &mszReaders[iList[iReader]],

215                 SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,

216                   &hCard, &dwPref);

 

 

SCardTransmit 的第一个参数 hCard SCardConnect 的时候建立的。

 

所以可以推理,只要用到和卡通讯的 API, 比如

SCardTransmit

SCardControl

SCardGetAttrib

SCardStatus

SCardReconnect

第一个参数都是 hCard. 而不需要和卡交换数据的 API, 第一个参数是 hContext.

回到  SCardTransmit

2954         for (i = 0; i < PCSCLITE_MAX_READERS_CONTEXTS; i++)

2955         {

2956                 char *r = psContextMap[dwContextIndex].psChannelMap[dwChannelIndex].readerName;

 

2957

2958                 /* by default r == NULL */

2959                 if (r && strcmp(r, (readerStates[i])->readerName) == 0)

2960                         break;

2961         }

2962

2963         if (i == PCSCLITE_MAX_READERS_CONTEXTS)

2964         {

2965                 (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

2966                  return SCARD_E_READER_UNAVAILABLE;

2967          }

 

 

 

2954~2961 行,前面的 SCardGetIndicesFromHandlez 之后已经获得了 dwChannelIndex,

dwContextIndex. 接下来与共享内存 readerStates 逐个进行比较,若没有匹配,则退出。

为什么会没有匹配?因为读卡器可能又被拔出。

 

 

2969         if ((cbSendLength > MAX_BUFFER_SIZE_EXTENDED)

2970                 || (*pcbRecvLength > MAX_BUFFER_SIZE_EXTENDED))

2971         {

2972                 (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

2973                 return SCARD_E_INSUFFICIENT_BUFFER;

2974          }

 

 

 

 

以下是两个最大值的定义

#define MAX_BUFFER_SIZE                 264     /**< Maximum Tx/Rx Buffer for short APDU */

 

# define MAX_BUFFER_SIZE_EXTENDED        (4 + 3 + (1<<16) + 3)   /**< enhanced (64K + APDU + Lc + Le) Tx/Rx Buffer */

 

 

 

这样就知道了

2969~2974 的存在原因了。

2975

2976          if ((cbSendLength > MAX_BUFFER_SIZE) || (*pcbRecvLength > MAX_BUFFER_SIZE))

 

2977         {

2978                 /* extended APDU */

2979                 unsigned char buffer[sizeof(sharedSegmentMsg) + MAX_BUFFER_SIZE_EXTENDED];

2980                 transmit_struct_extended *scTransmitStructExtended = (transmit_struct_extended *)buffer;

2981                 sharedSegmentMsg *pmsgStruct = (psharedSegmentMsg)buffer;

2982

2983                 scTransmitStructExtended->hCard = hCard;

2984                 scTransmitStructExtended->cbSendLength = cbSendLength;

2985                 scTransmitStructExtended->pcbRecvLength = *pcbRecvLength;

2986                 /* The size of data to send is the size of

2987                  * struct control_struct_extended WITHOUT the data[] field

2988                  * plus the effective data[] size

2989                  */

2990                 scTransmitStructExtended->size = sizeof(*scTransmitStructExtended)

2991                         - (sizeof(transmit_struct_extended) - offsetof(transmit_struct_extended, data))

2992                         + cbSendLength;

2993                 scTransmitStructExtended->ioSendPciProtocol = pioSendPci->dwProtocol;

2994                 scTransmitStructExtended->ioSendPciLength = pioSendPci->cbPciLength;

2995                 memcpy(scTransmitStructExtended->data, pbSendBuffer, cbSendLength);

2996                 scTransmitStructExtended->rv = SCARD_S_SUCCESS;

2997

 

 

2979 行, sharedSegmentMsg 的结构如下,在 winscard_msg.h

        typedef struct rxSharedSegment

        {

                uint32_t mtype;         /** one of the /c pcsc_adm_commands */

                uint32_t user_id;

                uint32_t group_id;

                uint32_t command;       /** one of the /c pcsc_msg_commands */

                uint64_t date;

                unsigned char key[PCSCLITE_MSG_KEY_LEN]; /* 16 bytes */

                union

                {

 

                        unsigned char data[PCSCLITE_MAX_MESSAGE_SIZE];

 

                        struct version_struct veStr;

                };

        }

        sharedSegmentMsg, *psharedSegmentMsg;

#define PCSCLITE_MAX_MESSAGE_SIZE     2048

 

 

transmit_struct_extended 的结构如下,在 winscard_msg.h

  struct transmit_struct_extended

  {

                int32_t hCard;

                uint32_t ioSendPciProtocol;

                uint32_t ioSendPciLength;

                uint32_t cbSendLength;

                uint32_t ioRecvPciProtocol;

                uint32_t ioRecvPciLength;

                uint32_t pcbRecvLength;

                uint32_t rv;

                uint64_t size;

                uint8_t data[1];

  };

 

  typedef struct transmit_struct_extended transmit_struct_extended;

 

 

 

2990~2991 行,

2990                 scTransmitStructExtended->size = sizeof(*scTransmitStructExtended)

2991                         - (sizeof(transmit_struct_extended) - offsetof(transmit_struct_extended, data))

 

2986                 /* The size of data to send is the size of

2987                  * struct control_struct_extended WITHOUT the data[] field

2988                  * plus the effective data[] size

2989                   */

 

 

 

这些行的解释足够说明上面的 size 的意义。

特别注意 offsetof ,查下 man 手册,

打开终端,

#man offsetof

 

       #include <stddef.h>

       size_t offsetof(type, member);

不用看文档,直接告诉您,

结构体成员 member 相对于 type 结构体基地址的偏移量。

当然了,也许您早就知道了。多言了。

 

2977~2997 行,初始化

scTransmitStructExtended 的各个成员变量。

 

2998                 if (pioRecvPci)

2999                 {

3000                         scTransmitStructExtended->ioRecvPciProtocol = pioRecvPci->dwProtocol;

3001                         scTransmitStructExtended->ioRecvPciLength = pioRecvPci->cbPciLength;

3002                 }

3003                 else

3004                 {

3005                          scTransmitStructExtended->ioRecvPciProtocol = SCARD_PROTOCOL_ANY;

3006                         scTransmitStructExtended->ioRecvPciLength = sizeof(SCARD_IO_REQUEST);

3007                  }

 

 

 

2998~3007 行,初始化接收使用的协议。

SCARD_IO_REQUEST 定义如下,在 PCSC/pcsclite.h

/** Protocol Control Information (PCI) */

typedef struct

{

        unsigned long dwProtocol;       /**< Protocol identifier */

        unsigned long cbPciLength;      /**< Protocol Control Inf Length */

}

SCardTransmit

SCARD_IO_REQUEST, *PSCARD_IO_REQUEST, *LPSCARD_IO_REQUEST;

 

 

3008

3009                 rv = WrapSHMWrite(SCARD_TRANSMIT_EXTENDED,

3010                         psContextMap[dwContextIndex].dwClientID,

3011                         scTransmitStructExtended->size,

3012                         PCSCLITE_CLIENT_ATTEMPTS, buffer);

3013

3014                 if (rv == -1)

3015                 {

3016                         (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

 

3017                          return SCARD_E_NO_SERVICE;

3018                 }

3019

3020                 /*

3021                  * Read a message from the server

3022                  */

3023                 /* read the first block */

3024                 rv = SHMMessageReceive(buffer, sizeof(sharedSegmentMsg), psContextMap[dwContextIndex].dwCl     ientID, PCSCLITE_CLIENT_ATTEMPTS);

3025                 if (rv == -1)

3026                 {

3027                         (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

3028                         return SCARD_F_COMM_ERROR;

3029                  }

 

 

 

 

3009 行, 3024 行, WrapSHMWrite SHMMessageReceive 前面都解释过了。

 

需要注意的是,发送的数据格式是 transmit_struct_extended ,而接收第一个块的数据格式是 sharedSegmentMsg.

3024 行首先接收一个大小为 sharedSegmentMsg 的数据包。 data 域才是 transmit_struct_extended 结构。

 

再看看这个定义

  typedef struct rxSharedSegment

        {

                uint32_t mtype;         /** one of the /c pcsc_adm_commands */

                uint32_t user_id;

                uint32_t group_id;

                uint32_t command;       /** one of the /c pcsc_msg_commands */

                uint64_t date;

                unsigned char key[PCSCLITE_MSG_KEY_LEN]; /* 16 bytes */

                union

                {

                        unsigned char data[PCSCLITE_MAX_MESSAGE_SIZE];

                        struct version_struct veStr;

                };

        }

sharedSegmentMsg, *psharedSegmentMsg;

 

 

 

把返回的 transmit_struct_extended 存在 sharedSegmentMsg data 字段。

上面分析过了,发送的时候 transmit_struct_extended size 成员,表示了

transmit_struct_extended

的第一个成员 hCard size 的长度包括 size ,再加上实际的

发送数据长度。在接收的时候,也一样。 transmit_struct_extended size 成员,表示了

transmit_struct_extended

的第一个成员 hCard size 的长度包括 size 本身所占的大小,

再加上实际的接收数据长度。

 

3030

3031                 /* we receive a sharedSegmentMsg and not a transmit_struct_extended */

3032                 scTransmitStructExtended = (transmit_struct_extended *)&(pmsgStruct -> data);

3033

3034                  /* a second block is present */

3035                 if (scTransmitStructExtended->size > PCSCLITE_MAX_MESSAGE_SIZE)

3036                 {

3037                         rv = SHMMessageReceive(buffer + sizeof(sharedSegmentMsg),

3038                                  scTransmitStructExtended->size-PCSCLITE_MAX_MESSAGE_SIZE,

3039                                 psContextMap[dwContextIndex].dwClientID,

3040                                 PCSCLITE_CLIENT_ATTEMPTS);

3041                         if (rv == -1)

3042                         {

3043                                 (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

3044                                 return SCARD_F_COMM_ERROR;

3045                         }

3046                  }

 

 

 

3035 行,如果比较成立,证明还有一个数据块需要接收。因为 sharedSegmentMsg

data 成员长度为 PCSCLITE_MAX_MESSAGE_SIZE

 

3036~3046 行,经过上面的解释,很好理解了。就是再接收第二个块。

 

3047

3048                 if (scTransmitStructExtended -> rv == SCARD_S_SUCCESS)

3049                 {

3050                         /*

3051                          * Copy and zero it so any secret information is not leaked

 

3052                          */

3053                         memcpy(pbRecvBuffer, scTransmitStructExtended -> data,

3054                                 scTransmitStructExtended -> pcbRecvLength);

3055                         memset(scTransmitStructExtended -> data, 0x00,

3056                                 scTransmitStructExtended -> pcbRecvLength);

3057

3058                         if (pioRecvPci)

3059                          {

3060                                 pioRecvPci->dwProtocol = scTransmitStructExtended->ioRecvPciProtocol;

3061                                 pioRecvPci->cbPciLength = scTransmitStructExtended->ioRecvPciLength;

3062                         }

3063                 }

3064

3065                 *pcbRecvLength = scTransmitStructExtended -> pcbRecvLength;

3066                 (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

3067

3068                 rv = scTransmitStructExtended -> rv;

3069          }

 

 

 

3047~3069 行,把实际接收的数据,实际接收的数据长度,接收时使用的协议类型,通讯的结果 rv

复制给函数入口处对应的参数。

 

到了这里,长 APDU transmit 解说结束。

当然了,下面的 else 分支就开始了短 APDU 的发送接收了。

上面提到的长 APDU 用到的那两种结构。发送采用 transmit_struct_extended

结构,接收采用 sharedSegmentMsg 结构, data 域内嵌 transmit_struct_extended 结构。

相比,短 APDU 也用到了两种结构。发送采用 transmit_struct 结构,接收采用 sharedSegmentMsg 结构, data 域内嵌 transmit_struct

 

transmit_struct 的结构如下,在 winscard_msg.h

        struct transmit_struct

        {

                int32_t hCard;

                uint32_t ioSendPciProtocol;

                uint32_t ioSendPciLength;

                uint8_t pbSendBuffer[MAX_BUFFER_SIZE];

                uint32_t cbSendLength;

 

                uint32_t ioRecvPciProtocol;

                uint32_t ioRecvPciLength;

                uint8_t pbRecvBuffer[MAX_BUFFER_SIZE];

                 uint32_t pcbRecvLength;

                uint32_t rv;

        };

 

 

3070         else

3071         {

3072                 /* short APDU */

3073                 transmit_struct scTransmitStruct;

3074                 sharedSegmentMsg msgStruct;

3075

3076                 scTransmitStruct.hCard = hCard;

3077                 scTransmitStruct.cbSendLength = cbSendLength;

3078                 scTransmitStruct.pcbRecvLength = *pcbRecvLength;

3079                 scTransmitStruct.ioSendPciProtocol = pioSendPci->dwProtocol;

3080                 scTransmitStruct.ioSendPciLength = pioSendPci->cbPciLength;

3081                 memcpy(scTransmitStruct.pbSendBuffer, pbSendBuffer, cbSendLength);

3082                 memset(scTransmitStruct.pbSendBuffer+cbSendLength, 0, sizeof(scTransmitStruct.pbSendBuffer     )-cbSendLength);

3083                 memset(scTransmitStruct.pbRecvBuffer, 0, sizeof(scTransmitStruct.pbRecvBuffer));

3084                  scTransmitStruct.rv = SCARD_S_SUCCESS;

 

 

 

3070~3084 行,初始化 scTransmitStruct 各个成员,

3082 行,还对 transmit_struct

结构中,用剩的 pbSendBuffer 进行 0 填充。

3083 行,还对接收缓冲区 pbRecvBuffer 0.

APDU 的处理,没有进行这些。实际上,可以用也可以不用这两步。

 

3085

3086                 if (pioRecvPci)

3087                 {

3088                         scTransmitStruct.ioRecvPciProtocol = pioRecvPci->dwProtocol;

3089                         scTransmitStruct.ioRecvPciLength = pioRecvPci->cbPciLength;

3090                 }

3091                 else

3092                 {

3093                         scTransmitStruct.ioRecvPciProtocol = SCARD_PROTOCOL_ANY;

3094                         scTransmitStruct.ioRecvPciLength = sizeof(SCARD_IO_REQUEST);

 

3095                  }

 

 

 

 

3085~3095 行,和长 APDU 的收发处理是一样的。

 

3096

3097                 rv = WrapSHMWrite(SCARD_TRANSMIT,

3098                         psContextMap[dwContextIndex].dwClientID, sizeof(scTransmitStruct),

3099                         PCSCLITE_CLIENT_ATTEMPTS, (void *) &scTransmitStruct);

3100

3101                 if (rv == -1)

3102                 {

3103                         (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

3104                         return SCARD_E_NO_SERVICE;

3105                 }

3106

3107                  /*

3108                  * Read a message from the server

3109                  */

3110                 rv = SHMClientRead(&msgStruct, psContextMap[dwContextIndex].dwClientID,

3111                         PCSCLITE_CLIENT_ATTEMPTS);

3112

3113                  memcpy(&scTransmitStruct, &msgStruct.data, sizeof(scTransmitStruct));

3114

3115                 if (rv == -1)

3116                 {

3117                         (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

3118                          return SCARD_F_COMM_ERROR;

3119                  }

 

 

 

 

3096~3119 行,发,然后收,从 msgStruct.data 中,复制出内嵌的 scTransmitStruct 结构。

 

3120

3121                 /*

3122                  * Zero it and free it so any secret information cannot be leaked

3123                  */

 

3124                 memset(scTransmitStruct.pbSendBuffer, 0x00, cbSendLength);

3125

3126                 if (scTransmitStruct.rv == SCARD_S_SUCCESS)

3127                 {

3128                         /*

3129                          * Copy and zero it so any secret information is not leaked

3130                           */

3131                         memcpy(pbRecvBuffer, scTransmitStruct.pbRecvBuffer,

3132                                 scTransmitStruct.pcbRecvLength);

3133                         memset(scTransmitStruct.pbRecvBuffer, 0x00,

3134                                  scTransmitStruct.pcbRecvLength);

3135

3136                         if (pioRecvPci)

3137                         {

3138                                 pioRecvPci->dwProtocol = scTransmitStruct.ioRecvPciProtocol;

3139                                  pioRecvPci->cbPciLength = scTransmitStruct.ioRecvPciLength;

3140                         }

3141                 }

3142

3143                 *pcbRecvLength = scTransmitStruct.pcbRecvLength;

3144                 (void)SYS_MutexUnLock(psContextMap[dwContextIndex].mMutex);

3145

3146                 rv = scTransmitStruct.rv;

3147         }

3148

3149         PROFILE_END(rv)

3150

3151         return rv;

3152 }

 

 

 

 

3120~3147 行,把实际接收的数据,实际接收的数据长度,接收时使用的协议类型,通讯的结果 rv 复制给函数入口处对应的参数。

 

SCardTransmit 解说结束。

 

回头,回头,回到 testpcsc.c

247         test_rv(rv, hContext, PANIC);

248         printf(" card response:" GREEN);

249         for (i=0; i<length; i++)

 

250                 printf(" %02X", bRecvBuffer[i]);

251         printf("/n" NORMAL );

252

 

 

 

 

247~252 行把在进行 MF 选择后,接收的报文打印出。

253         printf("Testing SCardControl/t/t: ");

254 #ifdef PCSC_PRE_120

255         {

256                 char buffer[1024] = "Foobar";

257                 DWORD cbRecvLength = sizeof(buffer);

258

259                 rv = SCardControl(hCard, buffer, 7, buffer, &cbRecvLength);

260         }

261 #else

262         {

263                 char buffer[1024] = { 0x02 };

264                 DWORD cbRecvLength = sizeof(buffer);

265

266                 rv = SCardControl(hCard, SCARD_CTL_CODE(1), buffer, 1, buffer,

267                         sizeof(buffer), &cbRecvLength);

268                 if (cbRecvLength && (SCARD_S_SUCCESS == rv))

269                 {

270                         for (i=0; i<cbRecvLength; i++)

271                                 printf("%c", buffer[i]);

272                         printf(" ");

273                 }

274         }

275 #endif

 

 

 

 

253~275 行进行 SCardControl 测试。

可以使用 SCardControl ,进行一些产商特殊控制。

控制读卡器上的一些特殊功能,比如和读卡器集成一体的键盘操作,生物识别等。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值