В этой статье мы рассмотрим, как обнаруживать появление нового NFC-токена с использованием WinAPI в Delphi. Проблема заключается в том, что стандартные события, такие как SCardAccessStartedEvent и WM_DEVICECHANGE, не всегда работают корректно для обнаружения новых NFC-токенов. В этой статье мы рассмотрим возможные решения и предложим альтернативные подходы для улучшения обнаружения новых NFC-токенов.
Введение
NFC (Near Field Communication) — это технология беспроводной связи, которая используется для обмена данными между устройствами на небольшом расстоянии. В Delphi можно использовать WinAPI для взаимодействия с NFC-устройствами и токенами. Однако стандартные события, такие как SCardAccessStartedEvent и WM_DEVICECHANGE, не всегда корректно обнаруживают появление нового NFC-токена.
Стандартные события и их недостатки
SCardAccessStartedEvent
SCardAccessStartedEvent — это событие, которое используется для обнаружения начала доступа к NFC-токену. Однако, как указано в вопросе, это событие срабатывает дважды, но не всегда корректно обнаруживает появление нового NFC-токена.
constructor TEventThread.Create;
begin
inherited Create(True);
FEventHandle := SCardAccessStartedEvent;
if FEventHandle = 0 then
raise Exception.Create('Failed to access started event');
FreeOnTerminate := True;
end;
WM_DEVICECHANGE
WM_DEVICECHANGE — это событие, которое используется для обнаружения изменений в подключенных устройствах. Однако, как указано в вопросе, это событие срабатывает с wParam=7, что соответствует DBT_DEVNODES_CHANGED, а не DBT_DEVICEARRIVAL или DBT_DEVICEREMOVECOMPLETE. В этом случае, информация о добавленном или удаленном устройстве не предоставляется, и вам нужно самостоятельно сканировать устройства, чтобы определить изменения.
procedure TMainF.WMDeviceChange(var Msg: TMessage);
begin
case Msg.WParam of
DBT_DEVICEARRIVAL:
MOutput.Lines.Add('Device arrived');
DBT_DEVICEREMOVECOMPLETE:
MOutput.Lines.Add('Device removed');
end;
end;
Решение проблемы
Для улучшения обнаружения нового NFC-токена можно использовать комбинацию событий и дополнительных проверок. Мы можем использовать WM_DEVICECHANGE для обнаружения изменений в подключенных устройствах и затем сканировать устройства для определения появления нового NFC-токена.
Обновленный код для обнаружения нового NFC-токена
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
WinSCard, SCardErr, WinSmCrd;
type
TEventThread = class(TThread)
private
FEventHandle: THandle;
FLastDeviceCount: Integer;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
end;
TMainF = class(TForm)
BReadManually: TButton;
MOutput: TMemo;
BStartEventThread: TButton;
PButtons: TPanel;
procedure BReadManuallyClick(Sender: TObject);
procedure BStartEventThreadClick(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;
private
{ Private declarations }
eventthread: TEventThread;
public
{ Public declarations }
end;
var
MainF: TMainF;
implementation
const
DBT_DEVICEARRIVAL = $8000; // system detected a new device
DBT_DEVICEQUERYREMOVE = $8001; // wants to remove, may fail
DBT_DEVICEQUERYREMOVEFAILED = $8002; // removal aborted
DBT_DEVICEREMOVEPENDING = $8003; // about to remove, still avail.
DBT_DEVICEREMOVECOMPLETE = $8004; // device is gone
DBT_DEVICETYPESPECIFIC = $8005; // type specific event
DBT_CUSTOMEVENT = $8006; // user-defined event
procedure TMainF.BReadManuallyClick(Sender: TObject);
var
scc: SCardContext;
readers, debug: integer;
name: array of WideChar;
cardhandle: SCardHandle;
activeprotocol: DWORD;
request: WinSCard.PSCARD_IO_REQUEST;
cmdread: array[0..4] of Byte;
recvbuff: array[0..511] of Byte;
begin
if (SCardEstablishContext(SCARD_SCOPE_SYSTEM, nil, nil, @scc) <> SCARD_S_SUCCESS) then
RaiseLastOSError;
if (SCardListReadersW(scc, nil, nil, readers) <> SCARD_S_SUCCESS) then
RaiseLastOSError;
SetLength(name, readers);
if SCardListReadersW(scc, nil, PWideChar(name), readers) <> SCARD_S_SUCCESS then
RaiseLastOSError;
debug := SCardConnectW(scc, PWideChar(name), SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 OR SCARD_PROTOCOL_T1, cardhandle, @activeprotocol);
if (debug <> SCARD_SUCCESS) then
RaiseLastOSError
else begin
request := Getg_rgSCard(activeprotocol);
cmdread[0] := $00;
cmdread[1] := $00;
cmdread[2] := $00;
cmdread[3] := $00;
cmdread[4] := $00;
try
if (SCardTransmit(cardhandle, request, PByte(@cmdread[0]), SizeOf(cmdread), nil, PByte(@recvbuff[0]), @recvbuff) <> SCARD_S_SUCCESS) then
RaiseLastOSError;
finally
SCardDisconnect(cardhandle, SCARD_LEAVE_CARD);
end;
end;
end;
procedure TMainF.BStartEventThreadClick(Sender: TObject);
begin
eventthread := TEventThread.Create;
eventthread.Start;
end;
procedure TMainF.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
eventthread.Terminate;
CanClose := eventthread.Terminated;
end;
procedure TMainF.WMDeviceChange(var Msg: TMessage);
begin
case Msg.WParam of
DBT_DEVICEARRIVAL:
MOutput.Lines.Add('Device arrived');
DBT_DEVICEREMOVECOMPLETE:
MOutput.Lines.Add('Device removed');
end;
eventthread.ScanDevices;
end;
{ TEventThread }
constructor TEventThread.Create;
begin
inherited Create(True);
FEventHandle := SCardAccessStartedEvent;
if FEventHandle = 0 then
raise Exception.Create('Failed to access started event');
FreeOnTerminate := True;
FLastDeviceCount := 0;
end;
destructor TEventThread.Destroy;
begin
inherited Destroy;
end;
procedure TEventThread.Execute;
begin
while not Terminated do
begin
if WaitForSingleObject(FEventHandle, INFINITE) = WAIT_OBJECT_0 then
begin
Synchronize(procedure
begin
ShowMessage('Event occurred!');
end);
SCardReleaseStartedEvent;
end;
end;
end;
procedure TEventThread.ScanDevices;
var
scc: SCardContext;
readers, debug: integer;
name: array of WideChar;
i: Integer;
begin
if SCardEstablishContext(SCARD_SCOPE_SYSTEM, nil, nil, @scc) <> SCARD_S_SUCCESS then
Exit;
try
if SCardListReadersW(scc, nil, nil, readers) <> SCARD_S_SUCCESS then
Exit;
SetLength(name, readers);
if SCardListReadersW(scc, nil, PWideChar(name), readers) <> SCARD_S_SUCCESS then
Exit;
if readers > FLastDeviceCount then
begin
for i := FLastDeviceCount + 1 to readers do
begin
MOutput.Lines.Add('New NFC token detected: ' + name[i - 1]);
end;
end;
FLastDeviceCount := readers;
finally
SCardReleaseContext(scc);
end;
end;
Обновленный код для обнаружения нового NFC-токена
В этом примере мы добавили метод ScanDevices в класс TEventThread, который сканирует подключенные устройства и проверяет, появилось ли новое NFC-токен. Если появилось, он выводит сообщение о новом NFC-токене.
procedure TEventThread.ScanDevices;
var
scc: SCardContext;
readers, debug: integer;
name: array of WideChar;
i: Integer;
begin
if SCardEstablishContext(SCARD_SCOPE_SYSTEM, nil, nil, @scc) <> SCARD_S_SUCCESS then
Exit;
try
if SCardListReadersW(scc, nil, nil, readers) <> SCARD_S_SUCCESS then
Exit;
SetLength(name, readers);
if SCardListReadersW(scc, nil, PWideChar(name), readers) <> SCARD_S_SUCCESS then
Exit;
if readers > FLastDeviceCount then
begin
for i := FLastDeviceCount + 1 to readers do
begin
MOutput.Lines.Add('New NFC token detected: ' + name[i - 1]);
end;
end;
FLastDeviceCount := readers;
finally
SCardReleaseContext(scc);
end;
end;
Запуск сканирования при изменении устройств
Мы также обновили обработчик WM_DEVICECHANGE, чтобы вызывать метод ScanDevices при обнаружении изменений в подключенных устройствах.
procedure TMainF.WMDeviceChange(var Msg: TMessage);
begin
case Msg.WParam of
DBT_DEVICEARRIVAL:
MOutput.Lines.Add('Device arrived');
DBT_DEVICEREMOVECOMPLETE:
MOutput.Lines.Add('Device removed');
end;
eventthread.ScanDevices;
end;
Альтернативные подходы
Использование SCardGetStatusChange
Вместо использования SCardAccessStartedEvent и WM_DEVICECHANGE, можно использовать SCardGetStatusChange, который позволяет получать обновления состояния устройств. Этот подход может быть более надежным для обнаружения новых NFC-токенов.
procedure TEventThread.Execute;
var
statusChange: TSCardStatus;
statusChangeCount: DWORD;
i: Integer;
begin
while not Terminated do
begin
statusChangeCount := 0;
if SCardGetStatusChange(FEventHandle, INFINITE, @statusChange, 1, @statusChangeCount) = SCARD_S_SUCCESS then
begin
for i := 0 to statusChangeCount - 1 do
begin
if (statusChange[i].dwEventMask and SCARD_STATE_PRESENT) <> 0 then
begin
Synchronize(procedure
begin
MOutput.Lines.Add('New NFC token detected: ' + statusChange[i].szReader);
end);
end;
end;
end;
end;
end;
Использование SCardGetStatusChange с периодическим сканированием
Если SCardGetStatusChange не работает корректно, можно использовать периодическое сканирование устройств с использованием таймера.
procedure TEventThread.Execute;
begin
while not Terminated do
begin
Sleep(1000);
ScanDevices;
end;
end;
Заключение
В этой статье мы рассмотрели проблему обнаружения нового NFC-токена с использованием WinAPI в Delphi и предложили несколько решений. Мы обновили код для обнаружения нового NFC-токена, используя комбинацию событий и дополнительных проверок. Также мы рассмотрели альтернативные подходы, такие как использование SCardGetStatusChange и периодического сканирования устройств. Эти решения могут помочь улучшить обнаружение новых NFC-токенов в вашем приложении.
Статья описывает способы обнаружения новых NFC-токенов с использованием WinAPI в Delphi, рассматривая недостатки стандартных событий и предлагая альтернативные подходы для улучшения процесса обнаружения.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.