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カードを使ったアプリケーションを開発する方の参考になれば幸いです。
スポンサーリンク
最後までご覧いただきありがとうございます。