How to use SCardGetStatusChange()?

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.