【发布时间】:2021-04-28 05:05:54
【问题描述】:
我刚刚收到了一个标准便宜的usb smartcard reader。
我正在尝试找出如何在 Excel 中使用 VBA 与之交互。
-- 我在尝试在工作簿中创建基本的智能卡功能时写了这个。我想在某些时候我会卡住(我确实卡住了)。如果我遇到困难,我会更新这个问题,直到我达到在 excel 中使用智能卡的目标。
TL;DR此时调用函数SCardListReaders时的错误是“错误的DLL调用约定”
智能卡是由读卡器供电的微控制器,例如 AT88SC1608R。
有一个标准的windows界面来处理以winscard.dll为中心的阅读器。
一些文档在这里“Smart Card and Reader Access Functions”
经过一番研究,似乎要做的第一件事是使用函数SCardEstablishContext 接收“resource manager context”的句柄。
此“上下文”对象具有“范围”,即 USER 或 SYSTEM。这些由两个常量 SCARD_SCOPE_USER 和 SCARD_SCOPE_SYSTEM 选择。
从this thread 看来, SCARD_SCOPE_USER = 1 和 SCARD_SCOPE_SYSTEM = 2 。我不知道这些值是否已签名。同样根据this page,USER的值可能是0。
所以,我尝试创建一些代码来使用 SCardEstablishContext 和 SCardReleaseContext,如下所示。
Public Declare Function SCardEstablishContext Lib "winscard.dll" (ByVal dwScope As Long, _
ByVal pvReserved1 As Long, _
ByVal pvReserved2 As Long, _
ByRef phContext As SCARDCONTEXT _
) As Long
Public Declare Function SCardReleaseContext Lib "winscard.dll" (ByRef phContext As SCARDCONTEXT) As Long
Public Type SCARDCONTEXT
CardContext1 As Long
ReaderName As Byte
End Type
Sub GetContext()
Dim lReturn As Long
Dim RSVD1 As Long, RSVD2 As Long
Dim myContext As SCARDCONTEXT
' Constants, maybe unsigned ?
Dim SCARD_SCOPE_USER As Long
Dim SCARD_SCOPE_SYSTEM As Long
SCARD_SCOPE_USER = 1
SCARD_SCOPE_SYSTEM = 2
lReturn = SCardEstablishContext(SCARD_SCOPE_USER, RSVD1, RSVD2, myContext)
Debug.Print lReturn
Debug.Print myContext.CardContext1 & " " & myContext.ReaderName
lReturn = SCardReleaseContext(myContext)
Debug.Print lReturn
End Sub
运行此代码返回
-2146435055
0 0
6
使用十进制到十六进制转换器我发现这个 -2146435055 的十六进制值是 FFFFFFFF80100011 并且根据这张图表Authentication Return Values
第一个返回值是
SCARD_E_INVALID_VALUE
0x80100011
One or more of the supplied parameter values could not be properly interpreted.
然后我尝试为 SCARD_SCOPE_USER 使用 0 值并得到了这个更有希望的输出
0
-855572480 0
6
这可能会继续工作,下一个功能似乎是 SCardConnect,用于在读卡器中建立到卡的链接。这里调用成功可能意味着整个系统都在工作。
我为 SCardConnect 创建了以下声明
我在this address找到了一个常量列表
Public Const SCARD_SHARE_SHARED As Long = &H2
Public Const SCARD_SHARE_EXCLUSIVE As Long = &H1
Public Const SCARD_SHARE_DIRECT As Long = &H3
Public Const SCARD_PROTOCOL_T0 As Long = &H1
Public Const SCARD_PROTOCOL_T1 As Long = &H2
Public Declare Function SCardConnect Lib "winscard.dll" (ByVal phContext As SCARDCONTEXT, _
ByVal dwShareMode As Long, _
ByVal szReader As String, _
ByVal dwPreferredProtocols As Long, _
ByRef phCard As Long, _
ByRef pdwActiveProtocol As Long _
) As Long
要调用这个函数,我需要读者的名字。似乎 SCARDCONTEXT 类型应该包含阅读器的名称,但我的类型声明可能是错误的,我只能从中得到一个空字节。我尝试将“ReaderName”变量的类型更改为字符串,但后来我得到一个空字符串。
所以我现在将尝试使用 SCardListReaders 函数来获取名称。
这需要一个新的常量定义 SCARD_DEFAULT_READERS 包含文本“SCard$DefaultReaders\000”
Public Const SCARD_DEFAULT_READERS As String = "SCard$DefaultReaders\000"
Public Declare Function SCardListReaders Lib "winscard.dll" (ByRef phContext As SCARDCONTEXT, _
ByVal dwShareMode As Long, _
ByVal mszGroups As String, _
ByRef mszReaders As String, _
ByRef pcchReaders As Long _
) As Long
看来这个函数要使用两次,第一次是获取输出字符串的长度,通过将 mszReaders 设置为 NULL 长度将由 pcchReaders 输出。第二次我们准备一个缓冲区来接收来自 mszReaders 的字符串。
现在要试一试,这里是完整的代码。
Public Const SCARD_SCOPE_USER As Long = &H0
Public Const SCARD_SCOPE_SYSTEM As Long = &H2
Public Const SCARD_SHARE_SHARED As Long = &H2
Public Const SCARD_SHARE_EXCLUSIVE As Long = &H1
Public Const SCARD_SHARE_DIRECT As Long = &H3
Public Const SCARD_PROTOCOL_T0 As Long = &H1
Public Const SCARD_PROTOCOL_T1 As Long = &H2
Public Const SCARD_DEFAULT_READERS As String = "SCard$DefaultReaders\000"
Public Declare Function SCardEstablishContext Lib "winscard.dll" (ByVal dwScope As Long, _
ByVal pvReserved1 As Long, _
ByVal pvReserved2 As Long, _
ByRef phContext As SCARDCONTEXT _
) As Long
Public Declare Function SCardReleaseContext Lib "winscard.dll" (ByRef phContext As SCARDCONTEXT) As Long
Public Declare Function SCardConnect Lib "winscard.dll" (ByVal phContext As SCARDCONTEXT, _
ByVal dwShareMode As Long, _
ByVal szReader As String, _
ByVal dwPreferredProtocols As Long, _
ByRef phCard As Long, _
ByRef pdwActiveProtocol As Long _
) As Long
Public Declare Function SCardListReaders Lib "winscard.dll" (ByRef phContext As SCARDCONTEXT, _
ByVal dwShareMode As Long, _
ByVal mszGroups As String, _
ByRef mszReaders As String, _
ByRef pcchReaders As Long _
) As Long
Public Type SCARDCONTEXT
CardContext1 As Long
ReaderName As String
End Type
Sub GetContext()
Dim lReturn As Long
Dim RSVD1 As Long, RSVD2 As Long
Dim myContext As SCARDCONTEXT
lReturn = SCardEstablishContext(SCARD_SCOPE_USER, RSVD1, RSVD2, myContext)
Debug.Print "SCardEstablishContext: Return =" & lReturn & _
" myContext.CardContext1 = " & myContext.CardContext1 & _
" myContext.ReaderName = " & Chr(34) & myContext.ReaderName & Chr(34)
Dim ListOfReaders As String, lenListOfReaders As Long
lReturn = SCardListReaders(myContext, SCARD_SHARE_SHARED, SCARD_DEFAULT_READERS, ListOfReaders, lenListOfReaders)
Debug.Print "SCardListReaders: Return =" & lReturn & _
" ListOfReaders = " & Chr(34) & ListOfReaders & Chr(34) & _
" lenListOfReaders = " & lenListOfReaders
lReturn = SCardReleaseContext(myContext)
Debug.Print "SCardReleaseContext: Return =" & lReturn
End Sub
我尝试运行并得到错误
上线
lReturn = SCardListReaders(myContext, SCARD_SHARE_SHARED, SCARD_DEFAULT_READERS, ListOfReaders, lenListOfReaders)
错误
Run-time error '453':
Can't find DLL entry point SCardListReaders in winscard.dll
查看SCardListReaders function 的文档我发现它确实列出了这个DLL,winscard.dll 用于这个函数
还有一行写着
Unicode and ANSI names
SCardListReadersW (Unicode) and SCardListReadersA (ANSI)
所以我尝试在 SCardListReaders 的声明中添加“别名”参数,现在声明是这样的
Public Declare Function SCardListReaders Lib "winscard.dll" _
Alias "SCardListReadersA" (ByRef phContext As SCARDCONTEXT, _
ByVal dwShareMode As Long, _
ByVal mszGroups As String, _
ByRef mszReaders As String, _
ByRef pcchReaders As Long _
) As Long
运行这段代码我得到了错误
Run-time error '49':
Bad DLL calling convention
根据VB documentation 看来,此错误通常是由“在 Declare 语句中错误地省略或包含 ByVal 关键字”引起的。
现在我之前没有提到一些东西,在 SCardListReaders 的声明中,当我第一次尝试它时,我将 phContext 声明为
ByVal phContext As SCARDCONTEXT
由于这只是一个输入,我认为它不需要是 ByRef。 但是,当我这样做时,出现以下错误
Complile error:
User-defined type may not be passed ByVal
所以我将这一行修改为
ByRef phContext As SCARDCONTEXT
这会导致 Bad DLL 调用约定错误。
为了尝试解决这个问题,我现在替换
的所有实例phContext As SCARDCONTEXT
与 phContext 只要
再试一次
这给出了相同的“错误的 DLL 调用约定”错误
所以也许它真的需要 SCARDCONTEXT 类型变量并再次查看它,我在某个时候将 ReaderName 的类型从 Byte 更改为 String
所以我把类型声明改回
Public Type SCARDCONTEXT
CardContext1 As Long
ReaderName As Byte
End Type
我将所有 phContext 只要改回 phContext As SCARDCONTEXT ,但我仍然收到“Bad DLL calling convention”错误!!
所以我回到了SCardEstablishContext function 文档,以获取有关“LPSCARDCONTEXT phContext”结构的线索
此时我被卡住了,我找不到如何正确声明此 SCARDCONTEXT 类型,或者这确实是我的错误。
我希望你能找到我以前出错的地方,我也希望这能为其他人在 VBA 中使用智能卡绘制一些道路。
感谢阅读,再见!
【问题讨论】:
-
为什么
SCardListReadersA有5个参数?您在 MSDN 上的链接文档只有 4 个。 -
谢谢你这是我的错误!