single.php

スマートカードを挿入する画面表示(WinScard API|ScardUIDlgSelectCard)

e-Taxでの確定申告用に購入した、ICカードリーダー「PaSoRi RC-S380」ですが、PC/SC経由で情報を取得できるWinScard APIに標準として実装されている「スマートカードを挿入してください」画面をC++から表示してみました。

SDK for NFC for Windows

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

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

ScardUIDlgSelectCardで表示

今回表示する画面は、これです。

家庭向けのWindowsでは表示されることは、少ないですが、ICカードで社員証が作られていて、Windowsのログインやスクリーンロックを解除するセキュリティーとして導入していると、見かける画面です。

この画面は、ScardUIDlgSelectCardメソッドを使って呼び出します。

Winscard APIに関する詳細な情報は、こちら(引用元:Microsoft)をご覧ください。

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

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

#include "felica.h"

の部分を

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

に書き換えます。

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

SCARDCONTEXT    hContext;
LONG            nResult;
OPENCARDNAME_EX oc;
TCHAR           szReaderName[256];
TCHAR           szCardName[256];

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

oc.hSCardContext = hContext;
oc.hwndOwner = NULL;
oc.dwFlags = SC_DLG_FORCE_UI;
oc.lpstrTitle = NULL;
oc.lpstrSearchDesc = NULL;
oc.hIcon = NULL;
oc.pOpenCardSearchCriteria = NULL;
oc.lpfnConnect = NULL;
oc.pvUserData = NULL;
oc.dwShareMode = SCARD_SHARE_SHARED;
oc.dwPreferredProtocols = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1;
oc.lpstrRdr = szReaderName;
oc.nMaxRdr = sizeof(szReaderName);
oc.lpstrCard = szCardName;
oc.nMaxCard = sizeof(szCardName);
oc.dwActiveProtocol = 0;
oc.hCardHandle = NULL;
oc.dwStructSize = sizeof(OPENCARDNAME_EX);

nResult = SCardUIDlgSelectCard(&openCard);
if (nResult != SCARD_S_SUCCESS)
{
  if (nResult == SCARD_E_NO_READERS_AVAILABLE)
  {
    AfxMessageBox(_T("カードリーダが接続失敗"));
    SCardReleaseContext(hContext);
    return FALSE;
  }
}

if (openCard.hCardHandle != NULL)
{
  AfxMessageBox(_T("カードに接続"));
}
AfxMessageBox(szCardName, MB_OK);
SCardReleaseContext(hContext);

ビルドして実行すると、「スマート カードを挿入してください」画面が表示されます。

ICカードをデバイスに近づけても反応がない場合には、「詳細」ボタンをクリックすると原因が表示されます。

サンプルコード

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

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

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

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

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

void GetCardName(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;
  LONG            nResult;
  OPENCARDNAME_EX oc;
  TCHAR           szReaderName[256];
  TCHAR           szCardName[256];

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

  oc.hSCardContext = hContext;
  oc.hwndOwner = NULL;
  oc.dwFlags = SC_DLG_FORCE_UI;
  oc.lpstrTitle = NULL;
  oc.lpstrSearchDesc = NULL;
  oc.hIcon = NULL;
  oc.pOpenCardSearchCriteria = NULL;
  oc.lpfnConnect = NULL;
  oc.pvUserData = NULL;
  oc.dwShareMode = SCARD_SHARE_SHARED;
  oc.dwPreferredProtocols = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1;
  oc.lpstrRdr = szReaderName;
  oc.nMaxRdr = sizeof(szReaderName);
  oc.lpstrCard = szCardName;
  oc.nMaxCard = sizeof(szCardName);
  oc.dwActiveProtocol = 0;
  oc.hCardHandle = NULL;
  oc.dwStructSize = sizeof(OPENCARDNAME_EX);

  nResult = SCardUIDlgSelectCard(&openCard);
  if (nResult != SCARD_S_SUCCESS)
  {
    if (nResult == SCARD_E_NO_READERS_AVAILABLE)
    {
      AfxMessageBox(_T("カードリーダに接続失敗"));
      SCardReleaseContext(hContext);
      return FALSE;
    }
  }

  if (openCard.hCardHandle != NULL)
  {
    AfxMessageBox(_T("カードに接続"));
  }
  AfxMessageBox(szCardName, MB_OK);
  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カードを使ったアプリケーションを開発する方の参考になれば幸いです。

スポンサーリンク

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

コメントを残す

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