トップへ(mam-mam.net/)

DelphiでPaSoRi RC-S380を使ってNFCカード(NTAG215)のテキスト・URLを読み書きする方法【サンプルコード付き】

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カードの文字列とURLとUIDを読む

ICカードに登録したい文字列をMemo1に、URLをMemo2入力し、Button2をクリックすると、ICカードに登録されます。

ICカードに文字列とURLを書く