single.php

ICカード名を取得する(WinScard API|SCardListCards)

e-Taxでの確定申告用に購入した、ICカードリーダー「PaSoRi RC-S380」ですが、PC/SC経由で情報を取得できるWinScard APIで読み取り装置を取得することができました。今回は、SCardListCards関数を使って、スマートカードデータベースの内容を取得してみます。

SDK for NFC for Windows

NFCカードを使った開発をする前に、ドライバーなどをセットアップしておく必要があります。今回はPaSoRiを使った実装なので、デバイスの他に、PaSoRi用のSDKもインストールしておく必要があります。

詳しいことは別記事「SDK for NFC for Windowsの使い方」をご覧ください。

SCardListCards関数

詳しいWinscard APIに関する詳細な情報はリファレンスは、マイクロソフトさんのサイトをご覧ください。

カード名を取得するための関数で、第一引数に0を指定した場合にレジストリに登録されたリーダーライターの情報からカード名を列挙してくれます。

実際にやってみると、こんな感じで読みだせます。

先回のサンプルアプリを少し書き換えます。

#include "felica.h"

の部分を

#include <winscard.h>
#pragma comment (lib, "winscard.lib")

に書き換えます。

次に、OnInitDialog部分を次のように変更します。

SCARDCONTEXT hContext;
LPTSTR       lpszNames;
LPTSTR       lp;
LONG         nResult;
DWORD        dwAutoAllocate = SCARD_AUTOALLOCATE;
BOOL         bEnumReaders = TRUE;

nResult = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext);
if (nResult != SCARD_S_SUCCESS)
{
  if (nResult == SCARD_E_NO_SERVICE)
  {
    AfxMessageBox(_T("Smart Cardサービスの起動なし"));
  }
  return 0;
}


if (bEnumReaders)
{
  nResult= SCardListCards(0, NULL, NULL, 0, (LPTSTR)&lpszNames, &dwAutoAllocate);
  if (nResult!= SCARD_S_SUCCESS)
  {
    AfxMessageBox(_T("カードのリストの取得失敗"));
    SCardReleaseContext(hContext);
    return 0;
  }
}

AfxMessageBox(lpszNames);

SCardFreeMemory(hContext, lpszNames);
SCardReleaseContext(hContext);

ビルドして実行すると、メッセージボックスに「カード名」が表示されます。

ネタ元はレジストリ

この関数は、動作しているパソコンの以下のレジストリキーから値を取得してきます。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Calais\SmartCards

つまり、ドライバーなどに依存していないので、リーダーライターが接続されていなくても取得ができるようです。逆に言えば、レジストリを検索すれば同じ値が取得できます。

サンプルコード

NFCApp1Dlg.cppを変更したコードは次のようになります。

// NFCApp1Dlg.cpp : 実装ファイル
//

#include "stdafx.h"
#include "NFCApp1.h"
#include "NFCApp1Dlg.h"
#include "afxdialogex.h"

#include <winscard.h>
#pragma comment (lib, "winscard.lib")
#pragma comment (lib, "scarddlg.lib")

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

BOOL ShowCardName(SCARDCONTEXT hContext, SCARDHANDLE hCard);

// アプリケーションのバージョン情報に使われる CAboutDlg ダイアログ

class CAboutDlg : public CDialogEx
{
public:
  CAboutDlg();

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
  enum { IDD = IDD_ABOUTBOX };
#endif

  protected:
  virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV サポート

// 実装
protected:
  DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
  CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CNFCApp1Dlg ダイアログ



CNFCApp1Dlg::CNFCApp1Dlg(CWnd* pParent /*=nullptr*/)
  : CDialogEx(IDD_NFCAPP1_DIALOG, pParent)
{
  m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CNFCApp1Dlg::DoDataExchange(CDataExchange* pDX)
{
  CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CNFCApp1Dlg, CDialogEx)
  ON_WM_SYSCOMMAND()
  ON_WM_PAINT()
  ON_WM_QUERYDRAGICON()
END_MESSAGE_MAP()


// CNFCApp1Dlg メッセージ ハンドラー

BOOL CNFCApp1Dlg::OnInitDialog()
{
  CDialogEx::OnInitDialog();

  // "バージョン情報..." メニューをシステム メニューに追加します。

  // IDM_ABOUTBOX は、システム コマンドの範囲内になければなりません。
  ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
  ASSERT(IDM_ABOUTBOX < 0xF000);

  CMenu* pSysMenu = GetSystemMenu(FALSE);
  if (pSysMenu != nullptr)
  {
    BOOL bNameValid;
    CString strAboutMenu;
    bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
    ASSERT(bNameValid);
    if (!strAboutMenu.IsEmpty())
    {
      pSysMenu->AppendMenu(MF_SEPARATOR);
      pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
    }
  }

  // このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
  //  Framework は、この設定を自動的に行います。
  SetIcon(m_hIcon, TRUE);      // 大きいアイコンの設定
  SetIcon(m_hIcon, FALSE);    // 小さいアイコンの設定

  // TODO: 初期化をここに追加します。
  SCARDCONTEXT hContext;
  LPTSTR       lpszNames;
  LPTSTR       lp;
  LONG         lResult;
  DWORD        dwAutoAllocate = SCARD_AUTOALLOCATE;
  BOOL         bEnumReaders = TRUE;

  lResult = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext);
  if (lResult != SCARD_S_SUCCESS) {
    if (lResult == SCARD_E_NO_SERVICE)
      AfxMessageBox(_T("Smart Cardサービスの起動なし"));
    return 0;
  }


  if (bEnumReaders) {

    lResult = SCardListCards(0, NULL, NULL, 0, (LPTSTR)&lpszNames, &dwAutoAllocate);
    if (lResult != SCARD_S_SUCCESS) {
      AfxMessageBox(_T("カードのリストを取得失敗"));
      SCardReleaseContext(hContext);
      return 0;
    }
  }

  AfxMessageBox(lpszNames);

  SCardFreeMemory(hContext, lpszNames);
  SCardReleaseContext(hContext);

  return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}


void CNFCApp1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
  if ((nID & 0xFFF0) == IDM_ABOUTBOX)
  {
    CAboutDlg dlgAbout;
    dlgAbout.DoModal();
  }
  else
  {
    CDialogEx::OnSysCommand(nID, lParam);
  }
}

// ダイアログに最小化ボタンを追加する場合、アイコンを描画するための
//  下のコードが必要です。ドキュメント/ビュー モデルを使う MFC アプリケーションの場合、
//  これは、Framework によって自動的に設定されます。

void CNFCApp1Dlg::OnPaint()
{
  if (IsIconic())
  {
    CPaintDC dc(this); // 描画のデバイス コンテキスト

    SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

    // クライアントの四角形領域内の中央
    int cxIcon = GetSystemMetrics(SM_CXICON);
    int cyIcon = GetSystemMetrics(SM_CYICON);
    CRect rect;
    GetClientRect(&rect);
    int x = (rect.Width() - cxIcon + 1) / 2;
    int y = (rect.Height() - cyIcon + 1) / 2;

    // アイコンの描画
    dc.DrawIcon(x, y, m_hIcon);
  }
  else
  {
    CDialogEx::OnPaint();
  }


}

// ユーザーが最小化したウィンドウをドラッグしているときに表示するカーソルを取得するために、
//  システムがこの関数を呼び出します。
HCURSOR CNFCApp1Dlg::OnQueryDragIcon()
{
  return static_cast<HCURSOR>(m_hIcon);
}


まとめ

Windows10に対応しているRC-S380を使って、ICカードのデータ領域を読み込む場合には、「Winscard API」をすることでWindowsに対して標準的なインタフェイスを利用して接続することができるように改良されています。

Windowsで利用しているUIも利用ができるので、アプリケーションも標準化しやすいように作られています。

次回は、運賃残額などのデータ領域の取得を試してみて記事を追加していきます。

NFCカードを使ったアプリケーションを開発する方の参考になれば幸いです。

スポンサーリンク

最後までご覧いただきありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です