PC/SC経由でPaSoRi(RC-S380)を使い、リーダーのシリアル番号とFeliCa IDmを取得するC#のプログラム例。
PC/SCのプログラミング方法はEternalWindows セキュリティ/スマートカード を参考に作成。
DllImportの指定は、PINVOKE.NETのwinscardのカテゴリを参考にした。しかし、そのサイトでもfunction間で指定が統一されておらず、当方も仕組みが分かっていない上、CもC#も不慣れなため、宣言が適切かはかなりあやしい。
その他、かなり乱雑。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace SCardSample
{
class Program
{
[DllImport("winscard.dll")]
static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);
[DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
public static extern uint SCardListReaders(
IntPtr hContext,
byte[] mszGroups,
byte[] mszReaders,
ref UInt32 pcchReaders
);
[DllImport("WinScard.dll")]
static extern uint SCardReleaseContext(IntPtr phContext);
[DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
static extern uint SCardConnect(
IntPtr hContext,
string szReader,
uint dwShareMode,
uint dwPreferredProtocols,
ref IntPtr phCard,
ref IntPtr pdwActiveProtocol);
[DllImport("WinScard.dll")]
static extern uint SCardDisconnect(IntPtr hCard, int Disposition);
[StructLayout(LayoutKind.Sequential)]
internal class SCARD_IO_REQUEST
{
internal uint dwProtocol;
internal int cbPciLength;
public SCARD_IO_REQUEST()
{
dwProtocol = 0;
}
}
[DllImport("winscard.dll")]
public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
byte[] RecvBuff, ref int RecvBuffLen);
[DllImport("winscard.dll")]
public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct SCARD_READERSTATE
{
/// <summary>
/// Reader
/// </summary>
internal string szReader;
/// <summary>
/// User Data
/// </summary>
internal IntPtr pvUserData;
/// <summary>
/// Current State
/// </summary>
internal UInt32 dwCurrentState;
/// <summary>
/// Event State/ New State
/// </summary>
internal UInt32 dwEventState;
/// <summary>
/// ATR Length
/// </summary>
internal UInt32 cbAtr;
/// <summary>
/// Card ATR
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
internal byte[] rgbAtr;
}
[DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);
[DllImport("kernel32.dll", SetLastError = true)]
private extern static IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll")]
private extern static void FreeLibrary(IntPtr handle);
[DllImport("kernel32.dll")]
private extern static IntPtr GetProcAddress(IntPtr handle, string procName);
volatile static bool quit = false;
static void KeyCheck(object userState)
{
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
quit = true;
}
static void Main(string[] args)
{
IntPtr hContext = establishContext();
try
{
ThreadPool.QueueUserWorkItem(new WaitCallback(KeyCheck), null);
List<string> readersList = getReaders(hContext);
Debug.WriteLine(readersList.Count + "台のリーダーがあります。");
foreach (string readerName in readersList)
{
Debug.WriteLine("リーダー:" + readerName);
}
ServerConnector serverConnector = new ServerConnector();
SCARD_READERSTATE[] readerStateArray = initializeReaderState(hContext, readersList);
updateCurrentState(readerStateArray);
while(!quit)
{
const int SCARD_STATE_CHANGED = 0x00000002;// This implies that there is a
// difference between the state
// believed by the application, and
// the state known by the Service
// Manager. When this bit is set,
// the application may assume a
// significant state change has
// occurred on this reader.
const int SCARD_STATE_PRESENT = 0x00000020;// This implies that there is a card
// in the reader.
const int SCARD_STATE_EMPTY = 0x00000010; // This implies that there is not
// card in the reader. If this bit
// is set, all the following bits
// will be clear.
try
{
waitReaderStatusChange(hContext, readerStateArray, 1000);
for (int idx = 0; idx < readerStateArray.Length; idx++)
{
uint eventState = readerStateArray[idx].dwEventState;
if ((eventState & SCARD_STATE_CHANGED) != 0)
{
Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " 状態が変化しました。" + String.Format("{0:X}", eventState));
uint changedStateMask = eventState ^ readerStateArray[idx].dwCurrentState;
if ((changedStateMask & (SCARD_STATE_EMPTY | SCARD_STATE_PRESENT)) != 0) {
if ((eventState & SCARD_STATE_PRESENT) != 0)
{
Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " カードがセットされました。");
ReadResult result = readCard(hContext, readerStateArray[idx].szReader);
// result.readerSerialNumber → リーダーのシリアル番号
// result.cardId → IDm
}
if ((eventState & SCARD_STATE_EMPTY) != 0)
{
Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " カードが外されました。");
}
}
}
}
updateCurrentState(readerStateArray);
}
catch (TimeoutException e)
{
// 無視
}
}
}
finally
{
uint ret = SCardReleaseContext(hContext);
}
}
static ReadResult readCard(IntPtr hContext, string readerName)
{
IntPtr hCard = connect(hContext, readerName);
string readerSerialNumber = readReaderSerialNumber(hCard);
string cardId = readCardId(hCard);
Debug.WriteLine(readerName + " (S/N " + readerSerialNumber + ") から、カードを読み取りました。" + cardId);
disconnect(hCard);
ReadResult result = new ReadResult();
result.readerSerialNumber = readerSerialNumber;
result.cardId = cardId;
return result;
}
static string readCardId(IntPtr hCard)
{
byte maxRecvDataLen = 64;
byte[] recvBuffer = new byte[maxRecvDataLen + 2];
byte[] sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, maxRecvDataLen };
int recvLength = transmit(hCard, sendBuffer, recvBuffer);
// recvBuffer の最後の2バイトはステータスコードなので、応じた処理が必要。
// http://eternalwindows.jp/security/scard/scard07.html
string cardId = BitConverter.ToString(recvBuffer, 0, recvLength - 2).Replace("-", "");
return cardId;
}
static string readReaderSerialNumber(IntPtr hCard)
{
int controlCode = 0x003136b0; // SCARD_CTL_CODE(3500) の値
// IOCTL_PCSC_CCID_ESCAPE
// SONY SDK for NFC M579_PC_SC_2.1j.pdf 3.1.1 IOCTRL_PCSC_CCID_ESCAPE
byte[] sendBuffer = new byte[] {0xc0, 0x08 }; // ESC_CMD_GET_INFO / Product Serial Number
byte[] recvBuffer = new byte[64];
int recvLength = control(hCard, controlCode, sendBuffer, recvBuffer);
ASCIIEncoding asciiEncoding = new ASCIIEncoding();
string serialNumber = asciiEncoding.GetString(recvBuffer, 0, recvLength - 1); // recvBufferには\0で終わる文字列が取得されるので、長さを-1する。
return serialNumber;
}
const uint SCARD_S_SUCCESS = 0;
const uint SCARD_E_NO_SERVICE = 0x8010001D;
const uint SCARD_E_TIMEOUT = 0x8010000A;
static IntPtr establishContext()
{
IntPtr hContext = IntPtr.Zero;
const uint SCARD_SCOPE_USER = 0;
const uint SCARD_SCOPE_TERMINAL = 1;
const uint SCARD_SCOPE_SYSTEM = 2;
uint ret = SCardEstablishContext(SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, out hContext);
if (ret != SCARD_S_SUCCESS)
{
string message;
switch (ret)
{
case SCARD_E_NO_SERVICE:
message = "Smart Cardサービスが起動されていません。";
break;
default:
message= "Smart Cardサービスに接続できません。code = " + ret;
break;
}
Debug.WriteLine(message);
throw new NotSupportedException(message);
}
Debug.WriteLine("Smart Cardサービスに接続しました。");
return hContext;
}
static List<string> getReaders(IntPtr hContext)
{
uint pcchReaders = 0;
// First call with 3rd parameter set to null gets readers buffer length.
uint ret = SCardListReaders(hContext, null, null, ref pcchReaders);
if (ret != SCARD_S_SUCCESS)
{
throw new ApplicationException("リーダーの情報が取得できません。code = " + ret);
}
byte[] mszReaders = new byte[pcchReaders * 2]; // 1文字2byte
// Fill readers buffer with second call.
ret = SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
if (ret != SCARD_S_SUCCESS)
{
throw new ApplicationException("リーダーの情報が取得できません。code = " + ret);
}
UnicodeEncoding unicodeEncoding = new UnicodeEncoding();
string readerNameMultiString = unicodeEncoding.GetString(mszReaders);
Debug.WriteLine("リーダー名を\\0で接続した文字列: " + readerNameMultiString);
Debug.WriteLine(" ");
int len = (int)pcchReaders;
char nullchar = (char)0;
List<string> readersList = new List<string>();
if (len > 0)
{
while (readerNameMultiString[0] != nullchar)
{
int nullindex = readerNameMultiString.IndexOf(nullchar); // Get null end character.
string readerName = readerNameMultiString.Substring(0, nullindex);
readersList.Add(readerName);
len = len - (readerName.Length + 1);
readerNameMultiString = readerNameMultiString.Substring(nullindex + 1, len);
}
}
return readersList;
}
static IntPtr connect(IntPtr hContext, string readerName)
{
const int SCARD_SHARE_SHARED = 0x00000002; // - This application will allow others to share the reader
const int SCARD_SHARE_EXCLUSIVE = 0x00000001; // - This application will NOT allow others to share the reader
const int SCARD_SHARE_DIRECT = 0x00000003; // - Direct control of the reader, even without a card
const int SCARD_PROTOCOL_T0= 1; // - Use the T=0 protocol (value = 0x00000001)
const int SCARD_PROTOCOL_T1= 2;// - Use the T=1 protocol (value = 0x00000002)
const int SCARD_PROTOCOL_RAW = 4;// - Use with memory type cards (value = 0x00000004)
IntPtr hCard = IntPtr.Zero;
IntPtr activeProtocol = IntPtr.Zero;
uint ret = SCardConnect(hContext, readerName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, ref hCard, ref activeProtocol);
if (ret != SCARD_S_SUCCESS)
{
throw new ApplicationException("カードに接続できません。code = " + ret);
}
return hCard;
}
static void disconnect(IntPtr hCard)
{
const int SCARD_LEAVE_CARD = 0; // Don't do anything special on close
const int SCARD_RESET_CARD = 1; // Reset the card on close
const int SCARD_UNPOWER_CARD = 2; // Power down the card on close
const int SCARD_EJECT_CARD = 3; // Eject the card on close
uint ret = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
if (ret != SCARD_S_SUCCESS)
{
throw new ApplicationException("カードとの接続を切断できません。code = " + ret);
}
}
private static IntPtr getPciT1()
{
IntPtr handle = LoadLibrary("Winscard.dll");
IntPtr pci = GetProcAddress(handle, "g_rgSCardT1Pci");
FreeLibrary(handle);
return pci;
}
static int transmit(IntPtr hCard, byte[] sendBuffer, byte[] recvBuffer)
{
SCARD_IO_REQUEST ioRecv = new SCARD_IO_REQUEST();
ioRecv.cbPciLength = 255;
int pcbRecvLength = recvBuffer.Length;
int cbSendLength = sendBuffer.Length;
IntPtr SCARD_PCI_T1 = getPciT1();
uint ret = SCardTransmit(hCard, SCARD_PCI_T1, sendBuffer, cbSendLength, ioRecv, recvBuffer, ref pcbRecvLength);
if (ret != SCARD_S_SUCCESS)
{
throw new ApplicationException("カードへの送信に失敗しました。code = " + ret);
}
return pcbRecvLength; // 受信したバイト数(recvBufferに受け取ったバイト数)
}
static int control(IntPtr hCard, int controlCode, byte[] sendBuffer, byte[] recvBuffer)
{
int bytesReturned = 0;
uint ret = SCardControl(hCard, controlCode, sendBuffer, sendBuffer.Length, recvBuffer, recvBuffer.Length, ref bytesReturned);
if (ret != SCARD_S_SUCCESS)
{
throw new ApplicationException("カードへの制御命令送信に失敗しました。code = " + ret);
}
return bytesReturned;
}
static SCARD_READERSTATE[] initializeReaderState(IntPtr hContext, List<string> readerNameList)
{
const int SCARD_STATE_UNAWARE = 0x00000000;
SCARD_READERSTATE[] readerStateArray = new SCARD_READERSTATE[readerNameList.Count];
int i = 0;
foreach (string readerName in readerNameList)
{
readerStateArray[i].dwCurrentState = SCARD_STATE_UNAWARE;
readerStateArray[i].szReader = readerName;
i++;
}
uint ret = SCardGetStatusChange(hContext, 100/*msec*/, readerStateArray, readerStateArray.Length);
if (ret != SCARD_S_SUCCESS)
{
throw new ApplicationException("リーダーの初期状態の取得に失敗。code = " + ret);
}
return readerStateArray;
}
static void waitReaderStatusChange(IntPtr hContext, SCARD_READERSTATE[] readerStateArray, int timeoutMillis)
{
uint ret = SCardGetStatusChange(hContext, timeoutMillis/*msec*/, readerStateArray, readerStateArray.Length);
switch(ret) {
case SCARD_S_SUCCESS:
break;
case SCARD_E_TIMEOUT:
throw new TimeoutException();
default:
throw new ApplicationException("リーダーの状態変化の取得に失敗。code = " + ret);
}
}
static void updateCurrentState(SCARD_READERSTATE[] readerStateArray)
{
for (int i = 0; i < readerStateArray.Length; i++)
{
readerStateArray[i].dwCurrentState = readerStateArray[i].dwEventState;
}
}
}
class ReadResult
{
public string readerSerialNumber;
public string cardId;
}
}