DelphiでPaSoRi RC-S380を使ってNFCカード(NTAG215)のテキスト・URLを読み書きする方法【サンプルコード付き】
DelphiでNFCカード(NTAG215)にテキストやURLを読み書きしたい方へ。
本記事では、PaSoRi RC-S380とWindows APIを使って、スマートフォンで直接読み取れるNDEF形式のデータを扱うクラス(TSCard)を作成し、DelphiからNTAG215カードに読み書きする方法を詳しく解説します。
NFCタグはアプリ不要で読み取れるのが特長で、AndroidではテキストとURL、iPhoneではURLのみが対応しています。
Delphiで扱いやすい「api.winscard.pas」「NFCCard.pas」ユニットを用いて、NTAG215カードに対応した読み書き処理を実装する手順を、サンプルコード付きで紹介します。
NFCカード(NXP NTAG215)の仕様や、カードリーダー関連のWindows API関数、NDEF(NFC Data Exchange Format)についての詳細は、
PaSoRiとDelphiでNFCカード(NXP NTAG215)を読み書きをご参照ください。
DelphiでPaSoRi RC-S380を使ってNFCカード(NTAG215)のテキスト・URLを読み書きする方法【サンプルコード付き】
プロジェクトを作成する
Delphiを起動し、メニューから「ファイル」⇒「新規作成」⇒「Windows VCLアプリケーション -Delphi(W)」 をクリックする。
TButton×2個、TLabel×2個、TMemo×3個を配置します。
[ファイル]⇒[すべて保存]からプロジェクトとユニットの保存をします。
ユニットを追加する
[ファイル]⇒[新規作成]⇒[ユニット]をクリックして新規ユニットを追加します。
以下のソースコードをコピペして、「api.winscard.pas」として保存します。
unit api.winscard;
interface
uses Winapi.Windows,System.SysUtils,System.Math;
type
SCARDCONTEXT=ULONG;
PSCARDCONTEXT=^SCARDCONTEXT;
SCARDHANDLE = ULONG;
PSCARDHANDLE = ^SCARDHANDLE;
SCARD_IO_REQUEST = Record
dwProtocol :DWORD;
cbPciLength :DWORD;
end;
PSCARD_IO_REQUEST=^SCARD_IO_REQUEST;
//スマートカードリソースマネージャへの接続
Function SCardEstablishContext(
dwScope:DWORD;
pvReserved1:Pointer;
pvReserved2: Pointer;
phContext :PSCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
//コンテキストの解放
Function SCardReleaseContext(
hContext:SCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
//カードリーダーのリスト取得
Function SCardListReadersA(
hContext : SCARDCONTEXT;
mszGroups:PAnsiChar;
szReaders:PAnsiChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードリーダーのリスト取得
Function SCardListReadersW(
hContext : SCARDCONTEXT;
mszGroups:PWideChar;
szReaders:PWideChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの接続
Function SCardConnectA(
hContext : SCARDCONTEXT;
szReaders:PAnsiChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの接続
Function SCardConnectW(
hContext : SCARDCONTEXT;
szReaders:PWideChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの切断
Function SCardDisconnect(
hCard : SCARDHANDLE;
dwDisposition :DWORD
):LONG; stdcall; external 'Winscard.dll';
//カードのステータス取得
Function SCardStatusA(
hCard : SCARDHANDLE;
szReaderNames :PAnsiChar;
pcchReaderLen : PDWORD;
pdwState :PDWORD;
pdwProtocol :PDWORD;
pbATR :PBYTE;
pcbAtrLen :PDWORD
):LONG; stdcall; external 'Winscard.dll';
Function SCardStatusW(
hCard : SCARDHANDLE;
szReaderNames :PWideChar;
pcchReaderLen : PDWORD;
pdwState :PDWORD;
pdwProtocol :PDWORD;
pbATR :PBYTE;
pcbAtrLen :PDWORD
):LONG; stdcall; external 'Winscard.dll';
//サービス要求を送信し、カードからデータを受信する
Function SCardTransmit(
hCard : SCARDHANDLE;
pioSendPci :PSCARD_IO_REQUEST;
pbSendBuffer :PByte;
cbSendLength :DWORD;
pioRecvPci :PSCARD_IO_REQUEST;
pbRecvBuffer :PByte;
pcbRecvLength:PDWORD
):LONG; stdcall; external 'Winscard.dll';
const
//スマートカードの戻り値
ERROR_BROKEN_PIPE =$00000109;
SCARD_E_BAD_SEEK =$80100029;
SCARD_E_CANCELLED =$80100002;
SCARD_E_CANT_DISPOSE =$8010000E;
SCARD_E_CARD_UNSUPPORTED =$8010001C;
SCARD_E_CERTIFICATE_UNAVAILABLE=$8010002D;
SCARD_E_COMM_DATA_LOST =$8010002;
SCARD_E_DIR_NOT_FOUND =$80100023;
SCARD_E_DUPLICATE_READER =$8010001B;
SCARD_E_FILE_NOT_FOUND =$80100024;
SCARD_E_ICC_CREATEORDER =$80100021;
SCARD_E_ICC_INSTALLATION =$80100020;
SCARD_E_INSUFFICIENT_BUFFER=$80100008;
SCARD_E_INVALID_ATR =$80100015;
SCARD_E_INVALID_CHV =$8010002A;
SCARD_E_INVALID_HANDLE =$80100003;
SCARD_E_INVALID_PARAMETER=$80100004;
SCARD_E_INVALID_TARGET =$80100005;
SCARD_E_INVALID_VALUE =$80100011;
SCARD_E_NO_ACCESS =$80100027;
SCARD_E_NO_DIR =$80100025;
SCARD_E_NO_FILE =$80100026;
SCARD_E_NO_KEY_CONTAINER =$80100030;
SCARD_E_NO_MEMORY =$80100006;
SCARD_E_NO_PIN_CACHE =$80100033;
SCARD_E_NO_READERS_AVAILABLE=$8010002E;
SCARD_E_NO_SERVICE =$8010001D;
SCARD_E_NO_SMARTCARD =$8010000C;
SCARD_E_NO_SUCH_CERTIFICATE=$8010002C;
SCARD_E_NOT_READY =$80100010;
SCARD_E_NOT_TRANSACTED =$80100016;
SCARD_E_PCI_TOO_SMALL =$80100019;
SCARD_E_PIN_CACHE_EXPIRED=$80100032;
SCARD_E_PROTO_MISMATCH =$8010000F;
SCARD_E_READ_ONLY_CARD =$80100034;
SCARD_E_READER_UNAVAILABLE=$80100017;
SCARD_E_READER_UNSUPPORTED=$8010001A;
SCARD_E_SERVER_TOO_BUSY =$80100031;
SCARD_E_SERVICE_STOPPED =$8010001E;
SCARD_E_SHARING_VIOLATION=$8010000B;
SCARD_E_SYSTEM_CANCELLED =$80100012;
SCARD_E_TIMEOUT =$8010000A;
SCARD_E_UNEXPECTED =$8010001F;
SCARD_E_UNKNOWN_CARD =$8010000D;
SCARD_E_UNKNOWN_READER =$80100009;
SCARD_E_UNKNOWN_RES_MNG =$8010002B;
SCARD_E_UNSUPPORTED_FEATURE=$80100022;
SCARD_E_WRITE_TOO_MANY =$80100028;
SCARD_F_COMM_ERROR =$80100013;
SCARD_F_INTERNAL_ERROR =$80100001;
SCARD_F_UNKNOWN_ERROR =$80100014;
SCARD_F_WAITED_TOO_LONG =$80100007;
SCARD_P_SHUTDOWN =$80100018;
SCARD_S_SUCCESS =$00000000;
SCARD_W_CANCELLED_BY_USER=$8010006E;
SCARD_W_CACHE_ITEM_NOT_FOUND=$80100070;
SCARD_W_CACHE_ITEM_STALE =$80100071;
SCARD_W_CACHE_ITEM_TOO_BIG=$80100072;
SCARD_W_CARD_NOT_AUTHENTICATED=$8010006F;
SCARD_W_CHV_BLOCKED =$8010006C;
SCARD_W_EOF =$8010006D;
SCARD_W_REMOVED_CARD =$80100069;
SCARD_W_RESET_CARD =$80100068;
SCARD_W_SECURITY_VIOLATION=$8010006A;
SCARD_W_UNPOWERED_CARD =$80100067;
SCARD_W_UNRESPONSIVE_CARD=$80100066;
SCARD_W_UNSUPPORTED_CARD =$80100065;
SCARD_W_WRONG_CHV =$8010006B;
//SCardEstablishContext の dwScope
//データベース操作は、ユーザーのドメイン内で実行されます
SCARD_SCOPE_USER=0;
//コンテキストは現在の端末のコンテキストであり、
//データベース操作はすべてその端末のドメイン内で実行されます
//(呼び出し側アプリケーションには、データベース アクションに対する
// 適切なアクセス許可が必要です。)
SCARD_SCOPE_TERMINAL=1;
//コンテキストはシステム コンテキストであり、
//データベース操作はすべてシステムのドメイン内で実行されます
//(呼び出し側アプリケーションには、データベース アクションに対する
//適切なアクセス許可が必要です。)
SCARD_SCOPE_SYSTEM=2;
//SCardConnectA,SCardConnectW の dwShareMode
SCARD_SHARE_EXCLUSIVE =1;
SCARD_SHARE_SHARED =2;
SCARD_SHARE_DIRECT =3;
//SCardConnectA,SCardConnectW の dwPreferredProtocols
SCARD_PROTOCOL_UNDEFINED =$00000000;
SCARD_PROTOCOL_T0 =$00000001;
SCARD_PROTOCOL_T1 =$00000002;
SCARD_PROTOCOL_RAW =$00010000;
SCARD_PROTOCOL_DEFAULT =$80000000;
//SCardDisconnect の dwDisposition
SCARD_LEAVE_CARD =$00000000;
SCARD_RESET_CARD =$00000001;
SCARD_UNPOWER_CARD =$00000002;
SCARD_EJECT_CARD =$00000003;
//SCardTransmit の pioSendPci
SCARD_PCI_T0 : SCARD_IO_REQUEST = (dwProtocol:1; cbPciLength:8);
SCARD_PCI_T1 : SCARD_IO_REQUEST = (dwProtocol:2; cbPciLength:8);
implementation
end.
更にユニットを追加する
[ファイル]⇒[新規作成]⇒[ユニット]をクリックして更に新規ユニットを追加します。
以下のソースコードをコピペして、「NFCCard.pas」として保存します。
unit NFCCard;
interface
uses Winapi.Windows, System.SysUtils, System.Math, System.Classes,
api.winscard;
type
TSCardDataType=(cdtString,cdtUrl);
TSCardData=class(TObject)
private
fData:TBytes;
public
function getData():string;virtual;abstract;
procedure setData(v:string);virtual;abstract;
function getDataType():TSCardDataType;virtual;abstract;
constructor Create();virtual;abstract;
destructor Destroy();override;
property Data:TBytes read fData write fData;
end;
TSCardString=class(TSCardData)
private
fString:String;
public
function getData():string;override;
procedure setData(v:string);override;
function getDataType():TSCardDataType;override;
constructor Create();override;
destructor Destroy();override;
public
end;
TSCardUrl=class(TSCardData)
fUrl:String;
public
function getData():string;override;
procedure setData(v:string);override;
function getDataType():TSCardDataType;override;
constructor Create();override;
destructor Destroy();override;
end;
TSCard=class(TObject)
private
fReader:String;//リーダーの名称が入る
fErr:LONG; //エラー番号
fErrStr:String;//エラー文字列
fContext:SCARDCONTEXT;//リーダーへのコンテキスト
fHCard:SCARDHandle;//カード接続
fProtocol:DWORD;
fSendPci:SCARD_IO_REQUEST;
fATR:TBytes;
fUID:TBytes;
fCardName:String;
fDataList:TList;
//fATRからカードの名称を推察する
procedure GetCardName();
//指定ブロック(0~134)を読む(4*135=540Byte)
//先頭0-3、130-134ブロック(合計9ブロック=36Byte)はロック
//540-36=504Byte使用可能 4-129ブロック
function ReadBlock(BlockNum:Byte):TBytes;
//カードに接続していることが前提で4バイト書き込む
function UpdateBuffer4Byte(BlockNum: Byte; Data: TBytes): Boolean;
function UpdateBuffer(Data: TBytes; BlockNum: Byte): Boolean;
//リーダーに接続
function ConnectReader():Boolean;
//リーダーの切断(コンテキストの開放)
procedure DisConnectReader();
//ConnectReaderを呼んだ後カードに接続
function ConnectCard():Boolean;
//カード切断
procedure DisConnectCard();
public
property ErrStr:String read fErrStr;
property Err:LONG read fErr;
property ATR:TBytes read fATR;
property UID:TBytes read fUID;
property Reader:string read fReader;
property DataList:TList read fDataList;
constructor Create;
destructor Destroy();override;
//ATRの取得
procedure GetATR();
//UIDの取得
procedure GetUID();
//UIDを文字で取得
function GetUIDAsText():String;
//カードから情報を読む
function Read():Boolean;
//カードに情報を書く
function Write():boolean;
//保持しているデータを削除する
procedure ClearDataList();
end;
implementation
{ TSCard }
constructor TSCard.Create;
begin
fReader:='';//リーダーの名前
fErr:=0;
fErrStr:='';
fContext:=0;//リーダーへのコンテキスト
fHCard:=0;
fCardName:='';
fDataList:=TList.Create;
end;
destructor TSCard.Destroy;
var i:Integer;
begin
//コンテキストの解放
self.DisConnectReader();
for i := 0 to fDataList.Count-1 do
TSCardData(fDataList[i]).Free;
fDataList.Free;
end;
procedure TSCard.DisConnectCard;
begin
if fHCard<>0 then
begin
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
fHCard:=0;
end;
end;
procedure TSCard.DisConnectReader;
begin
if fContext<>0 then
begin
SCardReleaseContext(fContext);
fContext:=0;
fReader:='';
fHCard:=0;
fCardName:='';
//SetLength(Self.fATR,0);
//SetLength(Self.fUID,0);
end;
end;
procedure TSCard.GetATR();
var AtrLen:DWORD;
ReadersLen,State:DWORD;
begin
if ConnectCard() then
begin
AtrLen:=32;
SetLength(fATR,AtrLen);
fErr:=SCardStatusW(
fHCard,PChar(fReader),@ReadersLen,@State,
@fProtocol,
@fATR[0],@AtrLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(fATR,AtrLen);
self.GetCardName();//カード種類名も取得
end
else
begin
SetLength(fATR,0);
fErrStr:='ATR取得失敗';
end;
//カードの切断
DisConnectCard;
//コンテキストの解放
self.DisConnectReader();
end;
end;
procedure TSCard.GetCardName;
begin
if Length(fATR)<15 then
fCardName:='UnKnown'
else if (fATR[13]=$00) and (fATR[14]=$01) then
fCardName:='MIFARE Classic 1K'
else if (fATR[13]=$00) and (fATR[14]=$02) then
fCardName:='MIFARE Classic 4K'
else if (fATR[13]=$00) and (fATR[14]=$03) then //64バイト
fCardName:='MIFARE Ultralight'
else if (fATR[13]=$00) and (fATR[14]=$26) then
fCardName:='MIFARE Mini'
else if (fATR[13]=$00) and (fATR[14]=$3A) then //540バイト
fCardName:='MIFARE Ultralight C'
else if (fATR[13]=$00) and (fATR[14]=$36) then
fCardName:='MIFARE Plus SL1 2k'
else if (fATR[13]=$00) and (fATR[14]=$37) then
fCardName:='MIFARE Plus SL1 4k'
else if (fATR[13]=$00) and (fATR[14]=$38) then
fCardName:='MIFARE Plus SL2 2k'
else if (fATR[13]=$00) and (fATR[14]=$39) then
fCardName:='MIFARE Plus SL2 4k'
else if (fATR[13]=$00) and (fATR[14]=$30) then
fCardName:='Topaz and Jewel'
else if (fATR[13]=$00) and (fATR[14]=$3B) then
fCardName:='FeliCa'
else if (fATR[13]=$FF) and (fATR[14]=$28) then
fCardName:='JCOP 30'
else if (fATR[13]=$00) and (fATR[14]=$07) then
fCardName:='SRIX'
else
fCardName:='UnKnown';
end;
procedure TSCard.GetUID();
var SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
begin
self.fErrStr:='';
self.fErr:=0;
if ConnectCard() then
begin
SendLen:=5;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF;//CLA
SendBuf[1]:=$CA;//INS
SendBuf[2]:=$00;//P1 $00:UIDを取得 $01:ATSを取得
SendBuf[3]:=$00;//P2
SendBuf[4]:=$00;//Le
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
ZeroMemory(RecvBuf,RecvLen);
fErr:=SCardTransmit(
fHCard,@fSendPci,@SendBuf[0],SendLen,nil,@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
SetLength(fUID,Length(RecvBuf));
move(RecvBuf[0],fUID[0],Length(RecvBuf));
end
else
begin
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end;
end
else
begin
fErrStr:='UID取得失敗';
end;
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
fHCard:=0;
//コンテキストの解放
self.DisConnectReader();
end;
end;
function TSCard.GetUIDAsText: String;
var i:Integer;
begin
result:='';
for i := Low(fUID) to High(fUID) do
result:=result+IntToHex(fUID[i],2);
end;
function TSCard.Read: Boolean;
var i:Integer;
Buf,b:TBytes;
Bufs:TBytes;
rec:Integer;
//DataLen:Integer;
flag:Boolean;
scd:TSCardData;
st:string;
begin
if not ConnectCard then
begin
result:=False;
exit;
end;
self.ClearDataList();
Result:=true;
SetLength(Bufs,504);
ZeroMemory(@Bufs[0],504);
//指定ブロック(0~134)を読む(4*135=540Byte)
//先頭0-3、130-134ブロック(合計9ブロック=36Byte)はロック
//540-36=504Byte使用可能 4-129ブロック
for i := 4 to 129 do
begin
Buf:=ReadBlock(i);
move(Buf[0],Bufs[(i-4)*4],4);
end;
DisConnectCard;
if Bufs[0]=$03 then
begin
if Bufs[1]=0 then
begin
//空っぽ
self.fErr:=-1;
self.fErrStr:='データが空です';
end
else
begin
setLength(Buf,Bufs[1]);
move(Bufs[2],Buf[0],Bufs[1]);
rec:=0;
flag:=true;
while flag do
begin
if Buf[rec+3]=ord('T') then
begin
if (Buf[rec+4]=$02) and
(Buf[rec+5]=ord('j')) and (Buf[rec+6]=ord('a')) then
begin
setlength(b,Buf[rec+2]-3);
move(Buf[rec+4+3],b[0],Length(b));
st:=TEncoding.UTF8.GetString(b);
scd:=TSCardString.Create;
scd.setData(st);
self.fDataList.Add(scd);
end;
end
else if Buf[rec+3]=ord('U') then
begin
setlength(b,Buf[rec+2]-1);
move(Buf[rec+4+1],b[0],Length(b));
st:=TEncoding.UTF8.GetString(b);
{ $00=N/A $01=http://www. $02=https://www. $03=http://
$04=https:// $05=tel: $06=mailto: }
if Buf[rec+4]=$01 then
st:='http://www.'+st
else if Buf[rec+4]=$02 then
st:='https://www.'+st
else if Buf[rec+4]=$03 then
st:='http://'+st
else if Buf[rec+4]=$04 then
st:='https://'+st
else if Buf[rec+4]=$05 then
st:='tel://'+st
else if Buf[rec+4]=$06 then
st:='mailto://'+st;
scd:=TSCardUrl.Create;
scd.setData(st);
self.fDataList.Add(scd);
end;
flag:=not((Buf[rec] and $40)=$40);
rec:=rec+4+Buf[rec+2];
end;
end;
self.fErr:=0;
self.fErrStr:='';
end
else
begin
//NDEFフォーマットではない
self.fErr:=-1;
self.fErrStr:='NDEFフォーマットではありません';
end;
end;
function TSCard.ReadBlock(BlockNum: Byte): TBytes;
var res:DWORD;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
begin
if fHCard=0 then exit;
SetLength(Result,4);
ZeroMemory(Result,4);
SendLen:=255;
SetLength(SendBuf,SendLen);
ZeroMemory(@SendBuf[0],SendLen);
SendBuf[0]:=$FF; //CLA
SendBuf[1]:=$B0; //INS FFB0:ReadBinary
SendBuf[2]:=$00; //P1
SendBuf[3]:=BlockNum;//P2
SendBuf[4]:=$04; //Le 読み取るバイト数
RecvLen:=255;
SetLength(RecvBuf,RecvLen);
ZeroMemory(@RecvBuf[0],RecvLen);
Res:=SCardTransmit(
fHCard,@fSendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if Res=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
// 最後バイトが「$90$00」なら正常読み取り
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
//16バイト(4Block)読み取ってるけど1ブロックのみ返す(4Byte)
Move(Recvbuf[0],Result[0],4);
end;
end;
end;
function TSCard.UpdateBuffer(Data: TBytes; BlockNum: Byte): Boolean;
var i:Integer;
d:TBytes;
WriteBlockNum:Byte;
len:Integer;
begin
self.fErr:=0;
self.fErrStr:='';
Result:=False;
if not self.ConnectCard() then
begin
exit;
end;
if Length(Data)=0 then exit;
SetLength(d,4);
for i := 0 to (Ceil(Length(Data)/4)-1) do
begin
ZeroMemory(@d[0],4);
len:=Length(Data)-i*4;
if len>4 then len:=4;
Move(Data[i*4],d[0],len);
WriteBlockNum:=BlockNum+i;
result:=self.UpdateBuffer4Byte(WriteBlockNum,d);
if result=false then
begin
break;
end;
end;
self.DisConnectCard();
end;
function TSCard.UpdateBuffer4Byte(BlockNum: Byte; Data: TBytes): Boolean;
var len:Integer;
d:TBytes;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
begin
Result:=False;
len:=Length(Data);
if len=0 then exit;
if len>4 then len:=4;
SetLength(d,4);
ZeroMemory(d,4);
Move(Data[0],d[0],len);
SendLen:=9;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF;//CLA
SendBuf[1]:=$D6;//INS FFD6:Update Binary
SendBuf[2]:=$00;//P1
SendBuf[3]:=BlockNum;//P2 ブロック番号4以上
SendBuf[4]:=$04;//Lc 書き込みバイト数4固定
SendBuf[5]:=d[0];//書き込みデータ
SendBuf[6]:=d[1];//書き込みデータ
SendBuf[7]:=d[2];//書き込みデータ
SendBuf[8]:=d[3];//書き込みデータ
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
fErr:=SCardTransmit(
self.fHCard,@self.fSendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
result:=True;
end
else
begin
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end;
end;
function TSCard.Write():boolean;
var buf:TBytes;
i,ct:Integer;
dl:TSCardData;
PayLength:Integer;
begin
if not ConnectCard then
begin
result:=False;
exit;
end;
if fDataList.Count=0 then
begin
SetLength(buf,3);
buf[0]:=$03;//NDEF識別子 TVL開始
buf[1]:=$00;//バイト数
buf[2]:=$FE;//Terminal TVL
end
else
begin
PayLength:=0;
for i := 0 to fDataList.Count-1 do
begin
dl:=fDataList[i];
//MB: $80 Message Begin
//ME $40 Message End
//CF $20 Chunk Flag(分割されたペイロードに続きがある)
//SR $10 ShortRecord(ペイロード長さが1バイト[255]以下)
//IL $08 ID Length(IDの長さフィールドが存在する)
//TNF $01 Type Name Format(レコードタイプ)
//レコードが1つの場合は「$D1」
//最初のレコード「$91」、中間「$11」、最後「$51」
dl.fData[0]:=$11;
if i=0 then
dl.fData[0]:= dl.fData[0] or $80;
if i=(fDataList.Count-1) then
dl.fData[0]:= dl.fData[0] or $40;
PayLength:=PayLength+Length(dl.fData);
end;
SetLength(buf,2+PayLength+1);
ct:=0;
buf[ct]:=$03; //NDEF識別子 TVL開始
inc(ct);
buf[ct]:=PayLength;//バイト数
inc(ct);
for i := 0 to fDataList.Count-1 do
begin
dl:=fDataList[i];
move(dl.fData[0],buf[ct],Length(dl.fData));
inc(ct,Length(dl.fData));
end;
buf[ct]:=$FE;//Terminal TVL
end;
Result:=self.UpdateBuffer(buf,4);//4ブロック目以降に書く
end;
procedure TSCard.ClearDataList;
var i:Integer;
begin
for i := (fDataList.Count-1) downto 0 do
begin
TSCardData(fDataList.Items[i]).Free;
fDataList.Delete(i);
end;
end;
function TSCard.ConnectCard: Boolean;
var ActiveProtocols:DWORD;
begin
result:=False;
if ConnectReader() then
begin
fErr:=SCardConnectW(
fContext,PChar(fReader),SCARD_SHARE_SHARED,
SCARD_PROTOCOL_T0 or SCARD_PROTOCOL_T1,@fHCard,@ActiveProtocols
);
if fErr=SCARD_S_SUCCESS then
begin
if (ActiveProtocols and SCARD_PROTOCOL_T1)>0 then
begin
fProtocol:=SCARD_PROTOCOL_T1;
fSendPci:=SCARD_PCI_T1;
end
else
begin
fProtocol:=SCARD_PROTOCOL_T0;
fSendPci:=SCARD_PCI_T0;
end;
result:=True;
end
else
begin
fErrStr:='カードリーダーにカードが見つかりません';
//コンテキストの解放
self.DisConnectReader();
end;
end
else
begin
fErrStr:='カードリーダーが見つかりません';
//コンテキストの解放
self.DisConnectReader();
end;
end;
function TSCard.ConnectReader: Boolean;
var Readers:String;
ReadersLen:DWORD;
begin
result:=False;
fReader:='';//リーダーの名前
fErr:=$00;
fErrStr:='';
if fHCard<>0 then
DisConnectCard;
if fContext<>0 then
DisConnectReader;
//スマートカードリソースマネージャへの接続
fErr:=SCardEstablishContext(SCARD_SCOPE_USER,nil,nil,@fContext);
if fErr=SCARD_S_SUCCESS then
begin
ReadersLen:=255;
SetLength(Readers,ReadersLen);
//リーダーの一覧を取得する
fErr:=SCardListReadersW(fContext,nil,PChar(Readers),@ReadersLen);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(Readers,ReadersLen);
//一覧の最初の1つ目のリーダーの名前を取得する
fReader:=Readers;
result:=True;
end
else
begin
fErrStr:='カードリーダーが見つかりません';
//コンテキストの解放
self.DisConnectReader();
end;
end
else
begin
fErrStr:='不明なエラー';
fContext:=0;
end;
end;
{ TSCardData }
destructor TSCardData.Destroy;
begin
inherited;
end;
{ TSCardString }
constructor TSCardString.Create;
begin
end;
destructor TSCardString.Destroy;
begin
inherited;
end;
function TSCardString.getData: string;
begin
result:=fString;
end;
function TSCardString.getDataType: TSCardDataType;
begin
result:=TSCardDataType.cdtString;
end;
procedure TSCardString.setData(v: string);
var barr:TBytes;
DataLen:Byte;
i:Integer;
begin
fString:=v;
barr:=TEncoding.UTF8.GetBytes(fString);
DataLen:=Length(barr);//UTF8のバイト配列の長さ
SetLength(fdata,4+3+DataLen);
//fdata[0]:=4+3+DataLen;//全レコード長=レコードヘッダ+言語コード+文字長
//MB: $80 Message Begin
//ME $40 Message End
//CF $20 Chunk Flag(分割されたペイロードに続きがある)
//SR $10 ShortRecord(ペイロード長さが1バイト[255]以下)
//IL $08 ID Length(IDの長さフィールドが存在する)
//TNF $01 Type Name Format(レコードタイプ)
//レコードが1つの場合は「$D1」
//最初のレコード「$91」、中間「$11」、最後「$51」
fdata[0]:=$D1; //MB ME SR TNF
fdata[1]:=$01; //レコードタイプ $00:Empty $01:Well-Known・・・
fdata[2]:=DataLen+3; //ペイロード(★)の長さ
fdata[3]:=Ord('T'); ///ペイロードタイプ Text:$54 Uri:$55
fdata[4]:=$02; //★言語コードの長さ2
fdata[5]:=Ord('j'); //★ j
fdata[6]:=Ord('a'); //★ a
for i := Low(barr) to High(barr) do
fdata[7+i]:=barr[i]; //★ 文字列
end;
{ TSCardUrl }
constructor TSCardUrl.Create;
begin
end;
destructor TSCardUrl.Destroy;
begin
inherited;
end;
function TSCardUrl.getData: string;
begin
result:=fUrl;
end;
function TSCardUrl.getDataType: TSCardDataType;
begin
result:=TSCardDataType.cdtUrl;
end;
procedure TSCardUrl.setData(v: string);
var barr:TBytes;
DataLen:Byte;
i:Integer;
begin
fUrl:=v;
barr:=TEncoding.UTF8.GetBytes(fUrl);
DataLen:=Length(barr);//UTF8のバイト配列の長さ
SetLength(fdata,4+1+DataLen);
//fdata[0]:=4+1+DataLen;//全データ長さレコードヘッダ+言語コード+文字列長さ
//MB: $80 Message Begin
//ME $40 Message End
//CF $20 Chunk Flag(分割されたペイロードに続きがある)
//SR $10 ShortRecord(ペイロード長さが1バイト[255]以下)
//IL $08 ID Length(IDの長さフィールドが存在する)
//TNF $01 Type Name Format(レコードタイプ)
//レコードが1つの場合は「$D1」
//最初のレコード「$91」、中間「$11」、最後「$51」
fdata[0]:=$D1; //MB ME SR TNF
fdata[1]:=$01; //レコードタイプ $00:Empty $01:Well-Known・・・
fdata[2]:=DataLen+1; //ペイロード(★)の長さ
fdata[3]:=Ord('U'); //ペイロードタイプ Text:$54 Uri:$55
{ $00=N/A $01=http://www. $02=https://www. $03=http://
$04=https:// $05=tel: $06=mailto: }
fdata[4]:=$00; //★URLの種類
for i := Low(barr) to High(barr) do
fdata[5+i]:=barr[i]; //★ 文字列
end;
end.
フォーム(Unit1)のソースコードの入力
Form1のOnCreate、OnDestroy、Button1のOnClick、Button2のOnClick に以下のソースコードを記述します。
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls
,NFCCard;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Label1: TLabel;
Label2: TLabel;
Memo1: TMemo;
Memo2: TMemo;
Memo3: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
sc:TSCard;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var i:Integer;
scd:TSCardData;
begin
Memo1.Clear;
Memo2.Clear;
if sc.Read then
begin
Memo3.Lines.Add(sc.Reader);
sc.GetATR();
sc.GetUID();
Memo3.Lines.Add('UID:'+sc.GetUIDAsText());
for i := 0 to sc.DataList.Count-1 do
begin
scd:=TSCardData(sc.DataList[i]);
if scd.getDataType=TSCardDataType.cdtString then
Memo1.Lines.Add(scd.getData)
else
Memo2.Lines.Add(scd.getData)
end;
Memo3.Lines.Add('読み取り成功');
end
else
begin
Memo3.Lines.Add('エラー:'+sc.ErrStr);
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var d:TSCardData;
i:Integer;
begin
sc.ClearDataList;
for i := 0 to Memo1.Lines.Count-1 do
begin
d:=TSCardString.Create;
d.setData(Memo1.Lines[i]);
sc.DataList.Add(d);
end;
for i := 0 to Memo2.Lines.Count-1 do
begin
d:=TSCardUrl.Create;
d.setData(Memo2.Lines[i]);
sc.DataList.Add(d);
end;
if sc.Write then
begin
Memo3.Lines.Add('書き込み成功');
end
else
begin
Memo3.Lines.Add('書き込み失敗');
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
sc:=TSCard.Create;
Label1.Caption:='文字列:';
Label2.Caption:='URL:';
Memo1.Clear;
Memo2.Clear;
Memo3.Clear;
Memo1.ScrollBars:=ssBoth;
Memo2.ScrollBars:=ssBoth;
Memo3.ScrollBars:=ssBoth;
Button1.Caption:='読み取り';
Button2.Caption:='書き込み';
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(sc);
end;
end.
実行
PaSoRi(RC-S3800)をパソコンに接続し、ICタグを載せます。
Delphi IDEの「実行」ボタンを押して実行します。
Button1をクリックすると、カードの文字列とURLが登録されていれば読み込みます。UIDも読みます。
ICカードに登録したい文字列をMemo1に、URLをMemo2入力し、Button2をクリックすると、ICカードに登録されます。
