single.php

PaSoRi RC-S380でカード情報の取得(felica_nfc_library.dllが使えない訳)

e-Taxでの確定申告用に購入した、ICカードリーダー「PaSoRi RC-S380」ですが、SDKを使うとSuicaなどのIDmやPMmを取得することができますが、運賃などのデータ領域を取得することが出来なくなっているようです。調べてみると、felica_nfc_library.dllが提供されておらず、カードのデータ領域にはPC/SC経由になっていました。

ICカード リーダライタ

PaSoRiは、JRや地下鉄の乗車券として有名な「FeliCa」カードを読み取ることができるデバイスです。別名「ICカード リーダライタ」として呼ばれることもある機器です。

Amazonで2,484円で購入しました。数年に1度モデルチェンジをするので型番(私が購入したモデル RC-S380)は変わっていきますが、仕様は同じで、利用するアプリは継続して使えます。

SDK for NFC for Windows

SONY FeliCa 非接触カード技術のホームページ上にSDK for NFC for Windowsの開発ツールとしてダウンロードすることができます。詳細な部分は別記事「SDK for NFC for Windowsの使い方」をご覧ください。

現在提供されている RC-S380に対応している「SDK for NFC Starter Kit(Version 2.0.7)」には、「felica.lib」は提供されていますが、「felica_nfc_library.dll」が含まれていません。

felica_basic_lite_s.hを確認してみても

typedef struct {
  char* port_name;               // ポートの名前
  unsigned long baud_rate;       // 0
  unsigned char encryption_mode; // 暗号化モード
  unsigned char* kar;            // dummy
  unsigned char* kbr;            // dummy
} structure_reader_writer_mode;

typedef struct {
  unsigned char* system_code; // システムコード (2 バイト)
  unsigned char time_slot;    // タイムスロット (0x00, 0x01, 0x03, 0x07, 0x0f のいずれか)
} structure_polling;

typedef struct {
  unsigned char* card_idm; // カードの IDm の格納領域 (8 バイト)
  unsigned char* card_pmm; // カードの PMm の格納領域 (8 バイト)
} structure_card_information;

typedef struct {
  unsigned char device_info_type;         //  USBリーダ/ライタ種別
  unsigned char device_info_connect;      //  USBリーダ/ライタ接続方式
                                            //  0x00: 内蔵
                                            //  0x01: 外付け
} structure_device_information;

4種類の構造体が定義されているだけで、ICカードのデータ領域を取得するための関数などは用意されていません。

実際、structure_card_information構造体を使うことで、ICカード内のシリアル番号的な「IDm」は取得できます。詳しい取得方法は別記事「SDK for NFC for Windowsの使い方(VC++編)」をご覧ください。

winscard.libで取得

色々調べた結果、PaSoRiの前品番「RC-S370」と以前のSDKに含まれていた、「felica_nfc_library.dll」を利用することで運賃残額などのデータ領域を取得出来ていたようですが、今は出来なくなっているようです。

では、どうすれば良いのか?

結論的には、Windowsの場合にはMicrosoftが提供している「winscard」で取得が出来ました。つまり、「felica_nfc_library.dll」が使えなくなった訳は、その役割を終えて、 OSレベルで取得するAPIが提供されるようになっているという結論です。

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

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

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

#include "felica.h"

の部分を

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

に書き換えます。

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

SCARDCONTEXT hContext;
SCARDHANDLE  hCard;
LPTSTR       lpszReaderWriterName;
LONG         lResult;
DWORD        dwScardAutoAllocate = SCARD_AUTOALLOCATE;
DWORD        dwActiveProtocol;

lResult = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext);
if (lResult != SCARD_S_SUCCESS)
{
  if (lResult == SCARD_E_NO_SERVICE)
  {
    AfxMessageBox(_T("Smart Cardサービスエラー"));
    return FALSE;
  }
}

lResult = SCardListReaders(hContext, SCARD_ALL_READERS, (LPTSTR)&lpszReaderWriterName, &dwActiveProtocol);
if (lResult != SCARD_S_SUCCESS)
{
  if (lResult == SCARD_E_NO_READERS_AVAILABLE)
  {
    AfxMessageBox(_T("カードリーダの取得エラー"));
    return FALSE;
  }
}
lResult = SCardConnect(hContext, lpszReaderWriterName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
if (lResult != SCARD_S_SUCCESS)
{
  if (lResult == SCARD_W_REMOVED_CARD)
  {
    AfxMessageBox(_T("ICカードがありません"));
    SCardFreeMemory(hContext, lpszReaderWriterName);
    SCardReleaseContext(hContext);
    return FALSE;
  }
}

AfxMessageBox(lpszReaderWriterName);

GetCardName(hContext, hCard);
SCardDisconnect(hCard, SCARD_LEAVE_CARD);
SCardFreeMemory(hContext, lpszReaderWriterName);
SCardReleaseContext(hContext);

最後に、インライン関数「GetCardName」を追加します。

void GetCardName(SCARDCONTEXT hContext, SCARDHANDLE hCard)
{
  BYTE   btAtr[MAXIMUM_ATTR_STRING_LENGTH];
  LPTSTR lpszCardName;
  LONG   lResult;
  DWORD  dwAtrSize;
  DWORD  dwAutoAllocate = SCARD_AUTOALLOCATE;

  dwAtrSize = sizeof(btAtr);
  lResult = SCardStatus(hCard, NULL, NULL, NULL, NULL, btAtr, &dwAtrSize);
  if (lResult != SCARD_S_SUCCESS)
  {
    AfxMessageBox(_T("カードステータス 取得失敗"));
    return;
  }

  lResult = SCardListCards(hContext, btAtr, NULL, 0, (LPTSTR)&lpszCardName, &dwAutoAllocate);
  if (lResult != SCARD_S_SUCCESS) {
    AfxMessageBox(_T("カード名 取得失敗"));
    return;
  }

  AfxMessageBox(lpszCardName);
  SCardFreeMemory(hContext, lpszCardName);
  return;
}

ビルドして実行すると、リーダライターの機種名とカード名が取得されます。

サンプルコード

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;
  SCARDHANDLE  hCard;
  LPTSTR       lpszReaderWriterName;
  LONG         lResult;
  DWORD        dwScardAutoAllocate = SCARD_AUTOALLOCATE;
  DWORD        dwActiveProtocol;

  lResult = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext);
  if (lResult != SCARD_S_SUCCESS)
  {
    if (lResult == SCARD_E_NO_SERVICE)
    {
      AfxMessageBox(_T("Smart Cardサービスエラー"));
      return FALSE;
    }
  }

  lResult = SCardListReaders(hContext, SCARD_ALL_READERS, (LPTSTR)&lpszReaderWriterName, &dwActiveProtocol);
  if (lResult != SCARD_S_SUCCESS)
  {
    if (lResult == SCARD_E_NO_READERS_AVAILABLE)
    {
      AfxMessageBox(_T("カードリーダの取得エラー"));
      return FALSE;
    }
  }


  lResult = SCardConnect(hContext, lpszReaderWriterName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
  if (lResult != SCARD_S_SUCCESS)
  {
    if (lResult == SCARD_W_REMOVED_CARD)
    {
      AfxMessageBox(_T("ICカードがありません"));
      SCardFreeMemory(hContext, lpszReaderWriterName);
      SCardReleaseContext(hContext);
      return FALSE;
    }
  }

  AfxMessageBox(lpszReaderWriterName);

  GetCardName(hContext, hCard);
  SCardDisconnect(hCard, SCARD_LEAVE_CARD);

  SCardFreeMemory(hContext, lpszReaderWriterName);
  SCardReleaseContext(hContext);

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

void GetCardName(SCARDCONTEXT hContext, SCARDHANDLE hCard)
{
  BYTE   btAtr[MAXIMUM_ATTR_STRING_LENGTH];
  LPTSTR lpszCardName;
  LONG   lResult;
  DWORD  dwAtrSize;
  DWORD  dwAutoAllocate = SCARD_AUTOALLOCATE;

  dwAtrSize = sizeof(btAtr);
  lResult = SCardStatus(hCard, NULL, NULL, NULL, NULL, btAtr, &dwAtrSize);
  if (lResult != SCARD_S_SUCCESS)
  {
    AfxMessageBox(_T("カードステータス 取得失敗"));
    return;
  }

  lResult = SCardListCards(hContext, btAtr, NULL, 0, (LPTSTR)&lpszCardName, &dwAutoAllocate);
  if (lResult != SCARD_S_SUCCESS) {
    AfxMessageBox(_T("カード名 取得失敗"));
    return;
  }

  AfxMessageBox(lpszCardName);
  SCardFreeMemory(hContext, lpszCardName);
  return;
}

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カードのデータ領域を読み込む場合には、従来の「felica_nfc_library.dll」を利用する方法は廃止されて、Windows側の「Winscard API」を利用する方法に変更されたようです。

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

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

スポンサーリンク

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

コメントを残す

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