Some smart card applications need to detect when a card is inserted or a reader is added to the system. One way to solve the problem is to call SCardListReaders()
and SCardGetStatusChange()
in a loop.
SCardListReaders()
SCardListReaders() is a function to list the smart card reader(s) connected to the system.
The function API is:
LONG SCardListReaders(
SCARDCONTEXT hContext,
LPCSTR mszGroups,
LPSTR mszReaders,
LPDWORD pcchReaders
)
The result in
mszReaders
is a multi-string containing the list of readers.
SCardGetStatusChange()
SCardGetStatusChange() is a function to get the status of a list of readers and wait for events.
The function API is:
LONG SCardGetStatusChange(
SCARDCONTEXT hContext,
DWORD dwTimeout,
SCARD_READERSTATE * rgReaderStates,
DWORD cReaders
)
The function will return any change from the reader(s) listed in the parameter
rgReaderStates
. See the
function documentation for more details.
dwTimeout
is a timeout in milliseconds. The function will block until the timeout expires or a change is reported.
Real world (bad) application
Here is a PC/SC log trace (see "
PCSC API spy, third try") of an real world (non Free Software) application:
SCardEstablishContext
i dwScope: SCARD_SCOPE_SYSTEM (0x00000002)
o hContext: 0x40DB4852
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000189]
SCardListReaders
i hContext: 0x40DB4852
i mszGroups: (null)
o pcchReaders: 0x00000001
o mszReaders:
=> Cannot find a smart card reader. (SCARD_E_NO_READERS_AVAILABLE [0x8010002E]) [0.000090]
SCardReleaseContext
i hContext: 0x40DB4852
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000032]
SCardEstablishContext
i dwScope: SCARD_SCOPE_SYSTEM (0x00000002)
o hContext: 0x21793B9A
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000294]
SCardListReaders
i hContext: 0x21793B9A
i mszGroups: (null)
o pcchReaders: 0x00000001
o mszReaders:
=> Cannot find a smart card reader. (SCARD_E_NO_READERS_AVAILABLE [0x8010002E]) [0.000124]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000014]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000047]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000035]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000037]
SCardListReaders
i hContext: 0x21793B9A
i mszGroups: (null)
o pcchReaders: 0x00000001
o mszReaders:
=> Cannot find a smart card reader. (SCARD_E_NO_READERS_AVAILABLE [0x8010002E]) [0.000186]
[... lots of similar lines removed...]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000014]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000034]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000046]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000035]
SCardListReaders
i hContext: 0x21793B9A
i mszGroups: (null)
o pcchReaders: 0x00000001
o mszReaders:
=> Cannot find a smart card reader. (SCARD_E_NO_READERS_AVAILABLE [0x8010002E]) [0.000160]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000014]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000036]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000035]
SCardGetStatusChange
i hContext: 0x21793B9A
i dwTimeout: 0x000000C8 (200)
i cReaders: 0
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000037]
Thread 1/2
Results sorted by total execution time
total time: 10.209201 sec
0.000189 sec ( 1 calls) 0.00% SCardEstablishContext
0.000090 sec ( 1 calls) 0.00% SCardListReaders
0.000032 sec ( 1 calls) 0.00% SCardReleaseContext
Thread 2/2
Results sorted by total execution time
total time: 10.209201 sec
0.002053 sec ( 13 calls) 0.02% SCardListReaders
0.001655 sec ( 52 calls) 0.02% SCardGetStatusChange
0.000294 sec ( 1 calls) 0.00% SCardEstablishContext
The application was executed during 10 seconds. The application made 14 calls to SCardListReaders() and 52 calls to SCardGetStatusChange().
No reader was connected at the beginning and nothing happened during the 10 seconds. So all the PC/SC calls were made to detect... nothing.
Real world (good) application
For this second example I will use my pcsc_scan application (from
pcsc-tools):
SCardEstablishContext
i dwScope: SCARD_SCOPE_SYSTEM (0x00000002)
o hContext: 0x4D0F7F8C
=> Command successful. (SCARD_S_SUCCESS [0x00000000]) [0.000321]
SCardGetStatusChange
i hContext: 0x4D0F7F8C
i dwTimeout: 0x00000000 (0)
i cReaders: 1
i szReader: \\?PnP?\Notification
i dwCurrentState: (0x00000000)
i dwEventState: SCARD_STATE_UNKNOWN, SCARD_STATE_UNAVAILABLE, SCARD_STATE_EXCLUSIVE, SCARD_STATE_INUSE, SCARD_STATE_MUTE, SCARD_STATE_PRESENT, SCARD_STATE_ATRMATCH (0x55FD66A4FBEC)
i Atr length: 0x55FD66A4FBF2 (94546837175282)
i Atr: NULL
o szReader: \\?PnP?\Notification
o dwCurrentState: (0x00000000)
o dwEventState: (0x00000000)
o Atr length: 0x55FD66A4FBF2 (94546837175282)
o Atr: NULL
=> Command timeout. (SCARD_E_TIMEOUT [0x8010000A]) [0.000379]
SCardListReaders
i hContext: 0x4D0F7F8C
i mszGroups: (null)
o pcchReaders: 0x00000001
o mszReaders: NULL
=> Cannot find a smart card reader. (SCARD_E_NO_READERS_AVAILABLE [0x8010002E]) [0.000129]
SCardListReaders
i hContext: 0x4D0F7F8C
i mszGroups: (null)
o pcchReaders: 0x00000001
o mszReaders:
=> Cannot find a smart card reader. (SCARD_E_NO_READERS_AVAILABLE [0x8010002E]) [0.000098]
SCardGetStatusChange
i hContext: 0x4D0F7F8C
i dwTimeout: 0xFFFFFFFF (4294967295)
i cReaders: 1
i szReader: \\?PnP?\Notification
i dwCurrentState: (0x00000000)
i dwEventState: (0x00000000)
i Atr length: 0x55FD66A4FBF2 (94546837175282)
i Atr: NULL
Thread 1/1
Results sorted by total execution time
total time: -1.000000 sec
0.000379 sec ( 1 calls) -0.04% SCardGetStatusChange
0.000321 sec ( 1 calls) -0.03% SCardEstablishContext
0.000227 sec ( 2 calls) -0.02% SCardListReaders
The execution conditions are the same as in the previous case: no reader connected at the beginning and no change during 10 seconds.
pcsc_scan made 2 initial calls to
SCardListReaders()
and 2 calls to
SCardGetStatusChange()
. The second
SCardGetStatusChange()
call was interrupted after 10 seconds of execution (using Control-C). That is why the returned values of
SCardGetStatusChange()
are not logged.
\\?PnP?\Notification reader name
The difference between the 2 applications is the use of the special reader name
\\?PnP?\Notification
by pcsc_scan.
SCardGetStatusChange()
is perfect to detect card events in the selected readers. But how to know when a new reader has been connected? When PC/SC was designed (PC/SC specification 1.0
released in 1996) most smart card readers were (mostly) serial readers. USB was not yet deployed (USB 1.0 specification were also
released in 1996). (Serial) reader hotplug was not a use case at that time. So it is not surprising this use case is not handled by PC/SC.
To solve this problem a special reader name is used. This is (still) NOT documented in the PC/SC workgroup specification, even in the version 2 (
PC/SC v2 part 5 page 18), and I guess it is a Microsoft invention.
The special reader name will tell
SCardGetStatusChange()
to generate associated events when a reader is added or removed. So no need to continuously call
SCardListReaders()
as it is the case with the first application.
macOS case
Windows WinSCard does support
\\?PnP?\Notification
since a long time.
pcsc-lite (WinSCard for Unix) does support
\\?PnP?\Notification
since version 1.6.0 released in May 2010.
Apple has no support of
\\?PnP?\Notification
reader name. I opened a bug for that in 2015 (see "
OS X El Capitan missing feature: SCardGetStatusChange() and "\\?PnP?\Notification"") but nothing changed since then.
So on macOS you have to regularly call
SCardListReaders()
to detect a reader connection or disconnection.
CPU consumption
To reduce the CPU and battery usage an efficient PC/SC application should always use
\\?PnP?\Notification
when possible (so on Windows and Unix except macOS).
pcsc-tools 1.5.0
Since its version 1.5.0 pcsc_scan provides a graphical animation when nothing happens. To update the animation every second a timeout of 1 second is used in
SCardGetStatusChange()
. So if you redo the experiment with a recent pcsc_scan you will see one
SCardGetStatusChange()
call every second.
Since a new reader connection is detected using
SCardGetStatusChange()
no need to also call
SCardListReaders()
every second.
SCardCancel()
To force
SCardGetStatusChange()
to return before the end of the timeout you must use
SCardCancel() from another thread of the application.
You can use a very long timeout (or even the special value
INFINITE
) and use
SCardCancel()
when needed.
Conclusion
PC/SC provides functions to be notified of events. I invested a lot of time and work to make pcsc-lite efficient regarding events management. Every pcsc-lite internal polling has been removed.
Please use
\\?PnP?\Notification
in your applications. You may not save the world but you will save a few CPU cycles.