Visual BasicからPC/SCを介してNFCカードのUIDを取得する

NFCカードのUIDを取得したい

自分の夢に近づくためのステップの一つとして、PCからFeliCa、Mifare等のNFCカードに読み書きする技術の習得があります。
最低限、UIDが取得できれば、あとはアプリケーション側で時間情報や場所情報を付加し、データベースに読み書きすることで、かなりのことができると考えています。

NFCカードに読み書きするために、PC/SC(Personal Computer/Smart Card)というインタフェースがあります。PC/SCを使えばSDKを購入する必要がないようです。

C言語などでは、PC/SCのソースコードが出回っているようです。でも、あいにく僕には理解できません。
僕が使えるのはExcelのVBAとVisual Basicです。
ググってみると、こちらの記事でVisual Basicのコードを見つけました。でも、そのまま使っても、うまく動きませんでした。

そこで、コードを自分流に書き換えながら、動かないところに手を入れてみたところ、UIDが取れるようになりました。
ちなみに、開発環境はVisual Basic 2019です。

Visual Basic 2019で作成したコードと使用手順

使用手順は次の通りです。コードは使用手順の下に示します。

  • カードリーダのドライバをインストールします
  • カードリーダをつなぎます
  • 「PC/SC」が有効になるようにドライバを設定します
  • フォームにボタンを一つ置いて、Form1.vbのコードをコピペします
  • モジュールを1つ追加して、Module1.vbをコピペします
    (ドキュメントコメントのタグは、WordPressのタグと干渉してしまうようなので、丸カッコにしました。ご了承ください)
  • デバッグモードで実行します。
  • ボタンをクリックしてからカードをかざすと、出力ウィンドウにUIDが表示されます。

動作が確認出来たら、UIDをテキストボックスに表示させるとか、無限ループを作って次々とUIDを取得できるようにするとかして、改造してください。

Form1.vb

Imports System.Runtime.InteropServices

Public Class Form1
    ''' --------------------------------------------------------------------
    ''' (summary) Button1をクリックしたときの処理                 (/summary)
    ''' --------------------------------------------------------------------
    Private Sub Button1_Click(sender As Object, e As EventArgs) _
                Handles Button1.Click

        ' --- リソースマネージャコンテキストを確立する ---
        Dim hContext As IntPtr  ' コンテキストを識別するハンドル
        Dim URet As UInteger = SCardEstablishContext(
                SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, hContext)
        If URet <> SCARD_S_SUCCESS Then GoTo ErrorHandler

        ' --- カードリーダのリストを取得する ---
        ' バッファ長を取得する
        Dim pcchReaders As UInteger ' mszReadersバッファ長
        URet = SCardListReaders(hContext, Nothing, Nothing, pcchReaders)
        If URet <> SCARD_S_SUCCESS Then GoTo ErrorHandler
        ' Byteの配列でバッファを用意し、リストを取得する
        Dim mszReaders As Byte() =
                New Byte(Convert.ToInt32(pcchReaders) * 2 - 1) {}
        URet = SCardListReaders(hContext, Nothing, mszReaders, pcchReaders)
        If URet <> SCARD_S_SUCCESS Then GoTo ErrorHandler
        ' Byte配列を文字に変換して1つ目のリーダ名を取得する
        Dim szReader As String =
                System.Text.Encoding.ASCII.GetString(mszReaders).
                Split(vbNullChar.ToCharArray)(0)

        ' --- リーダにカードがかざされるのを待つ ---
        Dim dwTimeout As Integer = System.Threading.Timeout.Infinite
        Dim rgReaderStates(0) As SCARD_READERSTATE  ' 監視対象リーダ用構造体
        rgReaderStates(0).szReader = szReader       ' 監視対象のリーダ名
        ' すぐに初回のレポートを受信する設定
        rgReaderStates(0).dwCurrentState = SCARD_STATE_UNAWARE
        ' リーダにカードがかざされるまで待機
        Do
            ' 監視する
            URet = SCardGetStatusChange(hContext, dwTimeout,
                    rgReaderStates(0), rgReaderStates.Count)
            If URet <> SCARD_S_SUCCESS Then GoTo ErrorHandler
            ' カードが確認されたらループを抜ける
            If (rgReaderStates(0).dwEventState And
                    SCARD_STATE_PRESENT) <> 0 Then Exit Do
        Loop

        ' --- アプリケーションとカードの接続を確立する ---
        Dim hCard As IntPtr
        Dim pdwActiveProtocol As IntPtr = IntPtr.Zero
        URet = SCardConnect(hContext, szReader,
                SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 Or SCARD_PROTOCOL_T1,
                hCard, pdwActiveProtocol)
        If URet <> SCARD_S_SUCCESS Then GoTo ErrorHandler

        ' --- サービスリクエストを送信しデータバックを受信する ---
        ' 命令プロトコルヘッダへのポインタを作成する
        Dim pioSendPci As IntPtr    ' ヘッダへのポインタ
        ' winscard.dllのハンドラを取得する
        Dim hModule As IntPtr = LoadLibrary("winscard.dll")
        ' プロトコルに応じて分岐
        If pdwActiveProtocol = SCARD_PROTOCOL_T0 Then
            ' 調歩式半二重キャラクタ伝送プロトコルの場合
            pioSendPci = GetProcAddress(hModule, "g_rgSCardT0Pci")
        ElseIf pdwActiveProtocol = SCARD_PROTOCOL_T1 Then
            ' 調歩式半二重ブロック伝送プロトコルの場合
            pioSendPci = GetProcAddress(hModule, "g_rgSCardT1Pci")
        End If
        ' DLLモジュールを解放する
        FreeLibrary(hModule)

        ' カードUIDの取得コマンド
        Dim pbSendBuffer As Byte() = New Byte() {&HFF, &HCA, &H0, &H0, &H0}
        ' 受信プロトコルヘッダのポインタ
        Dim pioRecvRequest As SCARD_IO_REQUEST = Nothing
        ' 返信データ用バッファ
        Dim pbrecvBuffer As Byte() = New Byte(511) {}
        ' 返信データ用バッファサイズ
        Dim pcbRecvLength As Integer = pbrecvBuffer.Length
        ' サービスリクエストを送信しデータバックを受信する
        URet = SCardTransmit(
                hCard, pioSendPci, pbSendBuffer, pbSendBuffer.Length,
                pioRecvRequest, pbrecvBuffer, pcbRecvLength)
        If URet <> SCARD_S_SUCCESS Then GoTo ErrorHandler
        ' 結果出力
        Console.WriteLine(BitConverter.ToString(pbrecvBuffer, 0, pcbRecvLength - 2))

        ' --- 接続を終了する ---
        URet = SCardDisconnect(hCard, SCARD_LEAVE_CARD)
        If URet <> SCARD_S_SUCCESS Then GoTo ErrorHandler
        Exit Sub

ErrorHandler:
        ' エラーの場合の処理
        Throw New Exception($"{URet}:{GetErrMsg(URet)}")
    End Sub
End Class

 

Module1vb

Imports System.Runtime.InteropServices

Module Module1
    ' ───────────────────────────────────
    '  定数の宣言       
    ' ───────────────────────────────────
    ''' --------------------------------------------------------------------
    ''' (summary) 定数                                            (/summary)
    ''' --------------------------------------------------------------------
    Public Const SCARD_S_SUCCESS As Integer = 0
    Public Const SCARD_F_INTERNAL_ERROR As Integer = &H80100001
    Public Const SCARD_E_CANCELLED As Integer = &H80100002
    Public Const SCARD_E_INVALID_HANDLE As Integer = &H80100003
    Public Const SCARD_E_INVALID_PARAMETER As Integer = &H80100004
    Public Const SCARD_E_INVALID_TARGET As Integer = &H80100005
    Public Const SCARD_E_NO_MEMORY As Integer = &H80100006
    Public Const SCARD_F_WAITED_TOO_LONG As Integer = &H80100007
    Public Const SCARD_E_INSUFFICIENT_BUFFER As Integer = &H80100008
    Public Const SCARD_E_UNKNOWN_READER As Integer = &H80100009
    Public Const SCARD_E_TIMEOUT As Integer = &H8010000A
    Public Const SCARD_E_SHARING_VIOLATION As Integer = &H8010000B
    Public Const SCARD_E_NO_SMARTCARD As Integer = &H8010000C
    Public Const SCARD_E_UNKNOWN_CARD As Integer = &H8010000D
    Public Const SCARD_E_CANT_DISPOSE As Integer = &H8010000E
    Public Const SCARD_E_PROTO_MISMATCH As Integer = &H8010000F
    Public Const SCARD_E_NOT_READY As Integer = &H80100010
    Public Const SCARD_E_INVALID_VALUE As Integer = &H80100011
    Public Const SCARD_E_SYSTEM_CANCELLED As Integer = &H80100012
    Public Const SCARD_E_COMM_ERROR As Integer = &H80100013
    Public Const SCARD_F_UNKNOWN_ERROR As Integer = &H80100014
    Public Const SCARD_E_INVALID_ATR As Integer = &H80100015
    Public Const SCARD_E_NOT_TRANSACTED As Integer = &H80100016
    Public Const SCARD_E_READER_UNAVAILABLE As Integer = &H80100017
    Public Const SCARD_P_SHUTDOWN As Integer = &H80100018
    Public Const SCARD_E_PCI_TOO_SMALL As Integer = &H80100019
    Public Const SCARD_E_READER_UNSUPPORTED As Integer = &H8010001A
    Public Const SCARD_E_DUPLICATE_READER As Integer = &H8010001B
    Public Const SCARD_E_CARD_UNSUPPORTED As Integer = &H8010001C
    Public Const SCARD_E_NO_SERVICE As Integer = &H8010001D
    Public Const SCARD_E_SERVICE_STOPPED As Integer = &H8010001E
    Public Const SCARD_E_UNEXPECTED As Integer = &H8010001F
    Public Const SCARD_E_ICC_INSTALLATION As Integer = &H80100020
    Public Const SCARD_E_ICC_CREATEORDER As Integer = &H80100021
    Public Const SCARD_E_UNSUPPORTED_FEATURE As Integer = &H80100022
    Public Const SCARD_E_DIR_NOT_FOUND As Integer = &H80100023
    Public Const SCARD_E_FILE_NOT_FOUND As Integer = &H80100024
    Public Const SCARD_E_NO_DIR As Integer = &H80100025
    Public Const SCARD_E_NO_FILE As Integer = &H80100026
    Public Const SCARD_E_NO_ACCESS As Integer = &H80100027
    Public Const SCARD_E_WRITE_TOO_MANY As Integer = &H80100028
    Public Const SCARD_E_BAD_SEEK As Integer = &H80100029
    Public Const SCARD_E_INVALID_CHV As Integer = &H8010002A
    Public Const SCARD_E_UNKNOWN_RES_MNG As Integer = &H8010002B
    Public Const SCARD_E_NO_SUCH_CERTIFICATE As Integer = &H8010002C
    Public Const SCARD_E_CERTIFICATE_UNAVAILABLE As Integer = &H8010002D
    Public Const SCARD_E_NO_READERS_AVAILABLE As Integer = &H8010002E
    Public Const SCARD_E_COMM_DATA_LOST As Integer = &H8010002F
    Public Const SCARD_E_NO_KEY_CONTAINER As Integer = &H80100030
    Public Const SCARD_E_SERVER_TOO_BUSY As Integer = &H80100031
    Public Const SCARD_E_PIN_CACHE_EXPIRED As Integer = &H80100032
    Public Const SCARD_E_NO_PIN_CACHE As Integer = &H80100033
    Public Const SCARD_E_READ_ONLY_CARD As Integer = &H80100034
    Public Const SCARD_W_UNSUPPORTED_CARD As Integer = &H80100065
    Public Const SCARD_W_UNRESPONSIVE_CARD As Integer = &H80100066
    Public Const SCARD_W_UNPOWERED_CARD As Integer = &H80100067
    Public Const SCARD_W_RESET_CARD As Integer = &H80100068
    Public Const SCARD_W_REMOVED_CARD As Integer = &H80100069
    Public Const SCARD_W_SECURITY_VIOLATION As Integer = &H8010006A
    Public Const SCARD_W_WRONG_CHV As Integer = &H8010006B
    Public Const SCARD_W_CHV_BLOCKED As Integer = &H8010006C
    Public Const SCARD_W_EOF As Integer = &H8010006D
    Public Const SCARD_W_CANCELLED_BY_USER As Integer = &H8010006E
    Public Const SCARD_W_CARD_NOT_AUTHENTICATED As Integer = &H8010006F
    Public Const SCARD_W_CACHE_ITEM_NOT_FOUND As Integer = &H80100070
    Public Const SCARD_W_CACHE_ITEM_STALE As Integer = &H80100071
    Public Const SCARD_W_CACHE_ITEM_TOO_BIG As Integer = &H80100072

    Public Const SCARD_PROTOCOL_T0 As Integer = 1
    Public Const SCARD_PROTOCOL_T1 As Integer = 2
    Public Const SCARD_PROTOCOL_RAW As Integer = 4
    Public Const SCARD_SCOPE_USER As UInteger = 0
    Public Const SCARD_SCOPE_TERMINAL As UInteger = 1
    Public Const SCARD_SCOPE_SYSTEM As UInteger = 2
    Public Const SCARD_STATE_UNAWARE As Integer = &H0
    Public Const SCARD_STATE_IGNORE As Integer = &H1
    Public Const SCARD_STATE_CHANGED As Integer = &H2
    Public Const SCARD_STATE_UNKNOWN As Integer = &H4
    Public Const SCARD_STATE_UNAVAILABLE As Integer = &H8
    Public Const SCARD_STATE_EMPTY As Integer = &H10
    Public Const SCARD_STATE_PRESENT As Integer = &H20
    Public Const SCARD_STATE_ATRMATCH As Integer = &H40
    Public Const SCARD_STATE_EXCLUSIVE As Integer = &H80
    Public Const SCARD_STATE_INUSE As Integer = &H100
    Public Const SCARD_STATE_MUTE As Integer = &H200
    Public Const SCARD_STATE_UNPOWERED As Integer = &H400
    Public Const SCARD_SHARE_EXCLUSIVE As Integer = &H1
    Public Const SCARD_SHARE_SHARED As Integer = &H2
    Public Const SCARD_SHARE_DIRECT As Integer = &H3
    Public Const SCARD_LEAVE_CARD As Integer = 0
    Public Const SCARD_RESET_CARD As Integer = 1
    Public Const SCARD_UNPOWER_CARD As Integer = 2
    Public Const SCARD_EJECT_CARD As Integer = 3

    ' ───────────────────────────────────
    '  構造体の宣言       
    ' ───────────────────────────────────
    ''' --------------------------------------------------------------------
    ''' (summary) スマートカードトラッキング用構造体              (/summary)
    ''' --------------------------------------------------------------------
    Public Structure SCARD_READERSTATE
        Friend szReader As String           ' モニタしているリーダへのポインタ
        Friend pvUserData As IntPtr         ' 不使用
        Friend dwCurrentState As UInt32     ' アプリケーションから見た状態
        Friend dwEventState As UInt32       ' リソースマネージャから見た状態
        Friend cbAtr As UInt32              ' ATRのバイト数
        <MarshalAs(UnmanagedType.ByValArray, SizeConst:=36)>' 配列サイズの固定
        Friend rgbAtr() As Byte             ' カードのATR
    End Structure

    ' ───────────────────────────────────
    '  クラスの宣言       
    ' ───────────────────────────────────
    ''' --------------------------------------------------------------------
    ''' (summary) プロトコル制御情報クラス                        (/summary)
    ''' --------------------------------------------------------------------
    Public Class SCARD_IO_REQUEST
        Friend dwProtocol As UInteger   ' 使用中のプロトコル
        Friend cbPciLength As Integer   ' クラスのサイズとPCI固有の情報
    End Class

    ' ───────────────────────────────────
    '  変数の宣言       
    ' ───────────────────────────────────
    ' ───────────────────────────────────
    '  API関数の宣言       
    ' ───────────────────────────────────
    ''' --------------------------------------------------------------------
    ''' (summary) リソースマネージャコンテキストを確立する        (/summary)
    ''' (param name="dwScope">     コンテキストのスコープ           (/param)
    ''' (param name="pvReserved1"> 予約変数。NULLにする             (/param)
    ''' (param name="pvReserved2"> 予約変数。NULLにする             (/param)
    ''' (param name="phContext">   コンテキストへのハンドル         (/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardEstablishContext(
            ByVal dwScope As UInteger,
            ByVal pvReserved1 As IntPtr,
            ByVal pvReserved2 As IntPtr,
            ByRef phContext As IntPtr) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) リーダーグループ中のリーダリストを提供する      (/summary)
    ''' (param name="hContext">    コンテキストを識別するハンドル   (/param)
    ''' (param name="mszGroups">   リーダーグループの名前           (/param)
    ''' (param name="mszReaders">  カードリーダー一覧               (/param)
    ''' (param name="pcchReaders"> mszReadersバッファ長             (/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardListReaders(
            ByVal hContext As IntPtr,
            ByVal mszGroups As Byte(),
            ByVal mszReaders As Byte(),
            ByRef pcchReaders As UInt32) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) リーダが特定の状態になるまで実行を待機する      (/summary)
    ''' (param name="hContext">       コンテキストを識別するハンドル(/param)
    ''' (param name="dwTimeout">      待ち時間[ms]。INFINITEなら∞  (/param)
    ''' (param name="rgReaderStates"> 監視するリーダ構造体の配列    (/param)
    ''' (param name="cReaders">       rgReaderStatesの配列サイズ    (/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardGetStatusChange(
            ByVal hContext As IntPtr,
            ByVal dwTimeout As Integer,
            ByRef rgReaderStates As SCARD_READERSTATE,
            ByVal cReaders As Integer) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) アプリケーションとカードの接続を確立する        (/summary)
    ''' (param name="hContext">             コンテキスト識別ハンドル(/param)
    ''' (param name="szReader">             リーダ名                (/param)
    ''' (param name="dwShareMode">          他アプリ排除フラグ      (/param)
    ''' (param name="dwPreferredProtocols"> 接続プロトコル          (/param)
    ''' (param name="phCard">               カードへのハンドル      (/param)
    ''' (param name="pdwActiveProtocol">    確立されたプロトコル    (/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardConnect(
            ByVal hContext As IntPtr,
            ByVal szReader As String,
            ByVal dwShareMode As UInteger,
            ByVal dwPreferredProtocols As UInteger,
            ByRef phCard As IntPtr,
            ByRef pdwActiveProtocol As IntPtr) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) リーダの直接制御                                (/summary)
    ''' (param name="hCard">SCardConnectから返される参照値          (/param)
    ''' (param name="dwControlCode">   操作の制御コード             (/param)
    ''' (param name="lpInBuffer">      データバッファへのポインタ   (/param)
    ''' (param name="cbInBufferSize">  lpInBufferのサイズ           (/param)
    ''' (param name="lpOutBuffer">     出力バッファへのポインタ     (/param)
    ''' (param name="cbOutBufferSize"> lpOutBufferのサイズ          (/param)
    ''' (param name="lpBytesReturned"> lpOutBufferに格納されたサイズ(/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardControl(
            ByVal hCard As IntPtr,
            ByVal dwControlCode As Integer,
            ByVal lpInBuffer As Byte(),
            ByVal cbInBufferSize As Integer,
            ByVal lpOutBuffer As Byte(),
            ByVal cbOutBufferSize As Integer,
            ByRef lpBytesReturned As Integer) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) カードの状態                                    (/summary)
    ''' (param name="hCard">          SCardConnectから返される参照値(/param)
    ''' (param name="mszReaderNames"> リーダから返された表示名      (/param)
    ''' (param name="pcchReaderLen">  szReaderName bufferのサイズ   (/param)
    ''' (param name="pdwState">       リーダ内のカードの状態        (/param)
    ''' (param name="pdwProtocol">    現在プロトコル                (/param)
    ''' (param name="pbAtr">          ATR文字列へのポインタ         (/param)
    ''' (param name="pcbAtrLen">      用意したサイズと受信したサイズ(/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardStatus(
            ByVal hCard As IntPtr,
            ByVal mszReaderNames As String,
            ByVal pcchReaderLen As Integer,
            ByVal pdwState As Integer,
            ByVal pdwProtocol As Integer,
            ByVal pbAtr As Byte,
            ByVal pcbAtrLen As Integer) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) サービスリクエストを送信しデータバックを受信する(/summary)
    ''' (param name="hCard">          SCardConnectから返される参照値(/param)
    ''' (param name="pioSendPci">     命令プロトコルヘッダのポインタ(/param)
    ''' (param name="pbSendBuffer">   送信データへのポインタ        (/param)
    ''' (param name="cbSendLength">   pbSendBufferパラメーターの長さ(/param)
    ''' (param name="pioRecvPci">     受信プロトコルヘッダのポインタ(/param)
    ''' (param name="pbRecvBuffer">   返信データへのポインタ        (/param)
    ''' (param name="pcbRecvvLength"> pbRecvBufferパラメーターの長さ(/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardTransmit(
            ByVal hCard As IntPtr,
            ByVal pioSendPci As IntPtr,
            ByVal pbSendBuffer As Byte(),
            ByVal cbSendLength As Integer,
            ByVal pioRecvPci As SCARD_IO_REQUEST,
            ByVal pbRecvBuffer As Byte(),
            ByRef pcbRecvvLength As Integer) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) 接続を終了する                                  (/summary)
    ''' (param name="hCard">         SCardConnectから返される参照値 (/param)
    ''' (param name="dwDisposition"> 実行時のカードへのアクション   (/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardDisconnect(
            ByVal hCard As IntPtr,
            ByVal dwDisposition As Integer) _
            As UInteger
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary)コンテクストを閉じ、リソースを解放する           (/summary)
    ''' (param name="hContext">             コンテキスト識別ハンドル   (/param)
    ''' (returns) エラーコード                                    (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("winscard.dll")>
    Public Function SCardReleaseContext(
            ByVal hContext As IntPtr) _
            As UInteger
    End Function

    ' ───────────────────────────────────

    ''' --------------------------------------------------------------------
    ''' (summary) DLLモジュールを解放する                         (/summary)
    ''' (param name="handle"> ロードされたライブラリへのハンドル    (/param)
    ''' --------------------------------------------------------------------
    <DllImport("kernel32.dll")>
    Public Sub FreeLibrary(ByVal handle As IntPtr)
    End Sub

    ''' --------------------------------------------------------------------
    ''' (summary) DLLからの関数や変数へのアドレスを取得する       (summary)
    ''' (param name="hModule">    DLLモジュールのハンドル           (/param)
    ''' (param name="lpProcName"> 関数または変数名、または関数の序数(/param)
    ''' (returns) 関数または変数のアドレス                        (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("kernel32.dll")>
    Public Function GetProcAddress(
            ByVal hModule As IntPtr,
            ByVal lpProcName As String) _
            As IntPtr
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) 指定したモジュールをアドレス空間にロードする    (/summary)
    ''' (param name="lpLibFileName"> モジュール名                   (/param)
    ''' (returns) 成功したらモジュールのハンドル、失敗したらNULL  (/returns)
    ''' --------------------------------------------------------------------
    <DllImport("kernel32.dll")>
    Public Function LoadLibrary(
            ByVal lpLibFileName As String) _
            As IntPtr
    End Function

    ' ───────────────────────────────────
    '  本体       
    ' ───────────────────────────────────

    ''' --------------------------------------------------------------------
    ''' (summary) エラーコードからエラーメッセージを取得する      (/summary)
    ''' (param name="uintErr"> エラーコード                         (/param)
    ''' (returns) エラーメッセージ                                (/returns)
    ''' --------------------------------------------------------------------
    Public Function GetErrMsg(ByVal uintErr As UInteger) As String
        Select Case Uint2Int(uintErr)
            Case SCARD_S_SUCCESS
                Return "SUCCESS"
            Case SCARD_F_INTERNAL_ERROR
                Return "INTERNAL_ERROR"
            Case SCARD_E_CANCELLED
                Return "CANCELLED"
            Case SCARD_E_INVALID_HANDLE
                Return "INVALID_HANDLE"
            Case SCARD_E_INVALID_PARAMETER
                Return "INVALID_PARAMETER"
            Case SCARD_E_INVALID_TARGET
                Return "INVALID_TARGET"
            Case SCARD_E_NO_MEMORY
                Return "NO_MEMORY"
            Case SCARD_F_WAITED_TOO_LONG
                Return "WAITED_TOO_LONG"
            Case SCARD_E_INSUFFICIENT_BUFFER
                Return "INSUFFICIENT_BUFFER"
            Case SCARD_E_UNKNOWN_READER
                Return "UNKNOWN_READER"
            Case SCARD_E_TIMEOUT
                Return "TIMEOUT"
            Case SCARD_E_SHARING_VIOLATION
                Return "SHARING_VIOLATION"
            Case SCARD_E_NO_SMARTCARD
                Return "NO_SMARTCARD"
            Case SCARD_E_UNKNOWN_CARD
                Return "UNKNOWN_CARD"
            Case SCARD_E_CANT_DISPOSE
                Return "CANT_DISPOSE"
            Case SCARD_E_PROTO_MISMATCH
                Return "PROTO_MISMATCH"
            Case SCARD_E_NOT_READY
                Return "NOT_READY"
            Case SCARD_E_INVALID_VALUE
                Return "INVALID_VALUE"
            Case SCARD_E_SYSTEM_CANCELLED
                Return "SYSTEM_CANCELLED"
            Case SCARD_E_COMM_ERROR
                Return "COMM_ERROR"
            Case SCARD_F_UNKNOWN_ERROR
                Return "UNKNOWN_ERROR"
            Case SCARD_E_INVALID_ATR
                Return "INVALID_ATR"
            Case SCARD_E_NOT_TRANSACTED
                Return "NOT_TRANSACTED"
            Case SCARD_E_READER_UNAVAILABLE
                Return "READER_UNAVAILABLE"
            Case SCARD_P_SHUTDOWN
                Return "SHUTDOWN"
            Case SCARD_E_PCI_TOO_SMALL
                Return "PCI_TOO_SMALL"
            Case SCARD_E_READER_UNSUPPORTED
                Return "READER_UNSUPPORTED"
            Case SCARD_E_DUPLICATE_READER
                Return "DUPLICATE_READER"
            Case SCARD_E_CARD_UNSUPPORTED
                Return "CARD_UNSUPPORTED"
            Case SCARD_E_NO_SERVICE
                Return "NO_SERVICE"
            Case SCARD_E_SERVICE_STOPPED
                Return "SERVICE_STOPPED"
            Case SCARD_E_UNEXPECTED
                Return "UNEXPECTED"
            Case SCARD_E_ICC_INSTALLATION
                Return "ICC_INSTALLATION"
            Case SCARD_E_ICC_CREATEORDER
                Return "ICC_CREATEORDER"
            Case SCARD_E_UNSUPPORTED_FEATURE
                Return "UNSUPPORTED_FEATURE"
            Case SCARD_E_DIR_NOT_FOUND
                Return "DIR_NOT_FOUND"
            Case SCARD_E_FILE_NOT_FOUND
                Return "FILE_NOT_FOUND"
            Case SCARD_E_NO_DIR
                Return "NO_DIR"
            Case SCARD_E_NO_FILE
                Return "NO_FILE"
            Case SCARD_E_NO_ACCESS
                Return "NO_ACCESS"
            Case SCARD_E_WRITE_TOO_MANY
                Return "WRITE_TOO_MANY"
            Case SCARD_E_BAD_SEEK
                Return "BAD_SEEK"
            Case SCARD_E_INVALID_CHV
                Return "INVALID_CHV"
            Case SCARD_E_UNKNOWN_RES_MNG
                Return "UNKNOWN_RES_MNG"
            Case SCARD_E_NO_SUCH_CERTIFICATE
                Return "NO_SUCH_CERTIFICATE"
            Case SCARD_E_CERTIFICATE_UNAVAILABLE
                Return "CERTIFICATE_UNAVAILABLE"
            Case SCARD_E_NO_READERS_AVAILABLE
                Return "NO_READERS_AVAILABLE"
            Case SCARD_E_COMM_DATA_LOST
                Return "COMM_DATA_LOST"
            Case SCARD_E_NO_KEY_CONTAINER
                Return "NO_KEY_CONTAINER"
            Case SCARD_E_SERVER_TOO_BUSY
                Return "SERVER_TOO_BUSY"
            Case SCARD_E_PIN_CACHE_EXPIRED
                Return "PIN_CACHE_EXPIRED"
            Case SCARD_E_NO_PIN_CACHE
                Return "NO_PIN_CACHE"
            Case SCARD_E_READ_ONLY_CARD
                Return "READ_ONLY_CARD"
            Case SCARD_W_UNSUPPORTED_CARD
                Return "UNSUPPORTED_CARD"
            Case SCARD_W_UNRESPONSIVE_CARD
                Return "UNRESPONSIVE_CARD"
            Case SCARD_W_UNPOWERED_CARD
                Return "UNPOWERED_CARD"
            Case SCARD_W_RESET_CARD
                Return "RESET_CARD"
            Case SCARD_W_REMOVED_CARD
                Return "REMOVED_CARD"
            Case SCARD_W_SECURITY_VIOLATION
                Return "SECURITY_VIOLATION"
            Case SCARD_W_WRONG_CHV
                Return "WRONG_CHV"
            Case SCARD_W_CHV_BLOCKED
                Return "CHV_BLOCKED"
            Case SCARD_W_EOF
                Return "EOF"
            Case SCARD_W_CANCELLED_BY_USER
                Return "CANCELLED_BY_USER"
            Case SCARD_W_CARD_NOT_AUTHENTICATED
                Return "CARD_NOT_AUTHENTICATED"
            Case SCARD_W_CACHE_ITEM_NOT_FOUND
                Return "CACHE_ITEM_NOT_FOUND"
            Case SCARD_W_CACHE_ITEM_STALE
                Return "CACHE_ITEM_STALE"
            Case SCARD_W_CACHE_ITEM_TOO_BIG
                Return "CACHE_ITEM_TOO_BIG"
            Case Else
                Return "UNKENOWN"
        End Select
    End Function

    ''' --------------------------------------------------------------------
    ''' (summary) UIntegerをIntegerに変換する                     (/summary)
    ''' (param name="uint"> UInteger                                (/param)
    ''' (returns) Integer                                         (/returns)
    ''' --------------------------------------------------------------------
    Private Function Uint2Int(ByVal uint As UInteger) As Integer
        Dim lng As Long = uint - 2 ^ 32
        Return CInt(lng)
    End Function
End Module

苦労したところ

SCardGetStatusChangeの戻り値がSCARD_S_SUCCESSにならない

MicrosoftのDocumentationには、引数であるrgReaderStatesの説明として、「An array of SCARD_READERSTATE structures」と書いてあります。つまり、rgReaderStatesは配列です。でも、APIの呼び出しを配列にすると、戻り値がSCARD_S_SUCCESSになりません。

そこで、配列にせず、「ByRef rgReaderStates As SCARD_READERSTATE」として定義し、呼び出し元でrgReaderStates(0)としたら、カードを置かないときの戻り値がSCARD_S_SUCCESSになりました。

どうしてSCARD_S_SUCCESSが戻るようになるのか、理由はよくわかりません。

カードを置くとエラーが発生する

次に、カードを置いてみたら、「System.Runtime.InteropServices.SafeArrayTypeMismatchException: ‘配列のランタイム型とメタデータに記録されている sub 型が一致していません。’」というエラーが出て止まりました。

ウォッチ式で、引数であるrgAtrを確認するとNothingでした。

MicrosoftのDocumentationを確認すると、「BYTE rgbAtr[36];」とあります。つまり、固定バイト長が必要なようです。

そこで、<MarshalAs(UnmanagedType.ByValArray, SizeConst:=36)>というおまじないをかけたら、エラーを生じなくなりました。

実施例

UID取得例

UID取得例

フォームのコマンドボタンをクリックし、随分前に秋葉原で買ってきたMifare Ultralight Cの白紙カードをかざしてみたら、出力ウィンドウに「04-53-86-DA-1B-5C-80」などと、UIDが取れました。

Suicaやwaonカードでは、8桁のUIDが取れました。

まとめ

Visual Basic 2019からPC/SCインタフェースを介して、NFCカードのUIDを取得することができました。

UIDが取れれば、SCardTransmitのpbSendBufferを自分の好きなコマンドに換えることで、読み書きもできるようになると思います。

コメント

タイトルとURLをコピーしました