Getting started with smartcard & ISO 7816 in excel vba ( SCardEstablishContext )
<p>I just received a standard cheap <a href="http://stanleyglobaltech.com/sgt111.html" rel="nofollow">usb smartcard reader</a>.</p>

<p>I'm trying to find out how to interact with it using VBA in excel.</p>

<p>-- I wrote this as I attempted to create basic smartcard functionality in a workbook. I figured at some point I would get stuck (and I did). If I get unstuck I will update this question until I reach my goal of working smartcard in excel.</p>

<p>TL;DR at this point the error is "Bad DLL calling convention" when calling function SCardListReaders</p>

<p>Smartcards are microcontrollers like AT88SC1608R powered by the reader.</p>

<p>There is a standard windows interface for dealing with the readers centered around winscard.dll.</p>

<p>Some of the documentation is here "<a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa380141(v=vs.85).aspx" rel="nofollow">Smart Card and Reader Access Functions</a>"</p>

<p>After some research, it seems that the first thing to do is to receive a handle to a "<a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms721604(v=vs.85).aspx#_security_resource_manager_context_gly" rel="nofollow">resource manager context</a>" using the function <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379479(v=vs.85).aspx" rel="nofollow">SCardEstablishContext</a>.</p>

<p>This "context" object has "scopes", USER or SYSTEM. These are selected by the two constants SCARD_SCOPE_USER and SCARD_SCOPE_SYSTEM.</p>

<p>From <a href="http://www.yqcomputer.com/2_9a5639a3ec9042a7_1.htm" rel="nofollow">this thread</a> , it seems that SCARD_SCOPE_USER = 1 and SCARD_SCOPE_SYSTEM = 2 .  I don't know if these values are signed. Also according to <a href="https://retep998.github.io/doc/winapi/winscard/constant.SCARD_SCOPE_USER.html" rel="nofollow">this page</a>, the value of USER might be 0.</p>

<p>So, I have attempted to create some code to use SCardEstablishContext &amp; SCardReleaseContext as follows.</p>

<pre><code>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 &amp; " " &amp; myContext.ReaderName

    lReturn = SCardReleaseContext(myContext)
    Debug.Print lReturn

End Sub
</code></pre>

<p>Running this code returns</p>

<pre><code>-2146435055 
0 0
 6 
</code></pre>

<p>Using a decimal to hex converter I found that the hex value of this -2146435055 is FFFFFFFF80100011 and according to this chart <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa374738(v=vs.85).aspx#smart_card_return_values" rel="nofollow">Authentication Return Values</a></p>

<p>The first return value would be </p>

<pre><code>SCARD_E_INVALID_VALUE
0x80100011
One or more of the supplied parameter values could not be properly interpreted.
</code></pre>

<p>I then tried using a value of 0 for SCARD_SCOPE_USER and got this more promising output</p>

<pre><code> 0 
-855572480 0
 6 
</code></pre>

<p>This might be working so moving on, the next function appears to be SCardConnect to establish a link to the card in the reader. A successful call here probably means the entire system is working.</p>

<p>I created the following declarations for SCardConnect</p>

<p>I found a list of the constants at <a href="https://docs.omniref.com/github/costan/smartcard/smartcard-0.5.4/symbols/Smartcard::PCSC::FFILib::Consts::SCARD_SHARE_SHARED#line=68" rel="nofollow">this address</a></p>

<pre><code>Public Const SCARD_SHARE_SHARED As Long = &amp;H2
Public Const SCARD_SHARE_EXCLUSIVE As Long = &amp;H1
Public Const SCARD_SHARE_DIRECT As Long = &amp;H3
Public Const SCARD_PROTOCOL_T0 As Long = &amp;H1
Public Const SCARD_PROTOCOL_T1 As Long = &amp;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
</code></pre>

<p>To call this function, I will need the name of the reader. It seems that the SCARDCONTEXT type was supposed to contain the name of the reader but my type declaration might be wrong, I only get an empty byte out of it. I tried changing the type of "ReaderName" variable to string, but then I just get an empty string.</p>

<p>So I will now attempt to use the SCardListReaders function to get the name.</p>

<p>This requires a new constant defined SCARD_DEFAULT_READERS containing text "SCard$DefaultReaders\000"</p>

<pre><code>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
</code></pre>

<p>It appears that this function is to be used twice, first to get the length of the output string, by setting mszReaders to NULL the lenght will be outputted by pcchReaders. The second time we prepare a buffer to receive the string from mszReaders.</p>

<p>Now about to give this a try, here is the entire code as it exists.</p>

<pre><code>Public Const SCARD_SCOPE_USER As Long = &amp;H0
Public Const SCARD_SCOPE_SYSTEM As Long = &amp;H2
Public Const SCARD_SHARE_SHARED As Long = &amp;H2
Public Const SCARD_SHARE_EXCLUSIVE As Long = &amp;H1
Public Const SCARD_SHARE_DIRECT As Long = &amp;H3
Public Const SCARD_PROTOCOL_T0 As Long = &amp;H1
Public Const SCARD_PROTOCOL_T1 As Long = &amp;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 =" &amp; lReturn &amp; _
                " myContext.CardContext1 = " &amp; myContext.CardContext1 &amp; _
                " myContext.ReaderName = " &amp; Chr(34) &amp; myContext.ReaderName &amp; Chr(34)

    Dim ListOfReaders As String, lenListOfReaders As Long

    lReturn = SCardListReaders(myContext, SCARD_SHARE_SHARED, SCARD_DEFAULT_READERS, ListOfReaders, lenListOfReaders)

    Debug.Print "SCardListReaders: Return =" &amp; lReturn &amp; _
                " ListOfReaders = " &amp; Chr(34) &amp; ListOfReaders &amp; Chr(34) &amp; _
                " lenListOfReaders = " &amp; lenListOfReaders

    lReturn = SCardReleaseContext(myContext)
    Debug.Print "SCardReleaseContext: Return =" &amp; lReturn

End Sub
</code></pre>

<p>I attempt to run and get the error</p>

<p>On line </p>

<pre><code>lReturn = SCardListReaders(myContext, SCARD_SHARE_SHARED, SCARD_DEFAULT_READERS, ListOfReaders, lenListOfReaders)
</code></pre>

<p>Error </p>

<pre><code>Run-time error '453':
Can't find DLL entry point SCardListReaders in winscard.dll
</code></pre>

<p>Reviewing the documentation for <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379793(v=vs.85).aspx" rel="nofollow">SCardListReaders function</a> I find that it does list this DLL, winscard.dll for this function</p>

<p>There is also a line that says</p>

<pre><code>Unicode and ANSI names
SCardListReadersW (Unicode) and SCardListReadersA (ANSI)
</code></pre>

<p>So I tried adding an "Alias" parameter to the declation for SCardListReaders and now the declaration is like this</p>

<pre><code>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
</code></pre>

<p>Running this code I get the error </p>

<pre><code>Run-time error '49':
Bad DLL calling convention
</code></pre>

<p>According to <a href="https://support.microsoft.com/en-us/kb/85108" rel="nofollow">VB documentation</a> it seems that this error is often caused by " incorrectly omitting or including the ByVal keyword from the Declare statement".</p>

<p>Now I failed to mention something earlier, in the declaration for SCardListReaders, when I first tried it, I declared phContext as </p>

<pre><code>ByVal phContext As SCARDCONTEXT
</code></pre>

<p>Since this is an input only, I figured it didn't need to be ByRef.
However, when I did this I got the following error</p>

<pre><code>Complile error:
User-defined type may not be passed ByVal
</code></pre>

<p>So I modified the line to be </p>

<pre><code>ByRef phContext As SCARDCONTEXT
</code></pre>

<p>Which leads to the Bad DLL calling convention error.</p>

<p>To attempt to resolve this, I now replace all instances of </p>

<pre><code>phContext As SCARDCONTEXT
</code></pre>

<p>with 
    phContext As long</p>

<p>and give it another go</p>

<p>This gives the same "Bad DLL calling convention" error</p>

<p>So perhaps it really needed that SCARDCONTEXT type variable and looking at it again, I changed the type of ReaderName from Byte to String at some point</p>

<p>So I change the type declaration back to</p>

<pre><code>Public Type SCARDCONTEXT
    CardContext1 As Long
    ReaderName As Byte
End Type
</code></pre>

<p>And I change back all phContext As long to phContext As SCARDCONTEXT and still I get the "Bad DLL calling convention" error !!</p>

<p>So I went back to the <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379479(v=vs.85).aspx" rel="nofollow">SCardEstablishContext function</a> documentation for clues on the structure of that "LPSCARDCONTEXT phContext"</p>

<p>At this point I am stuck, I can't find how to properly declare this SCARDCONTEXT type or if that really is my error.</p>

<p>I hope you can find where I went wrong before and I also hope that this charts some of the road to working with smartcards in VBA for others.</p>

<p>thanks for reading, bye !</p>