I'm trying to look into smart card reading and I've immediately encountered an issue that I'm struggling with. I've called the SCardEstablishContext and I'm now trying to list the cards readers. I'm getting success and Its telling me there are 89 bytes worth of string data but the string isn't being written out to me. I'm not sure what the issue could be, my code is pretty much identical to the example code on msdn only I've gotten some memory using virtualalloc for the string to.
I've tried changing types and connecting, disconnecting smart card reader, restarting the system etc etc stepping through in the debugger for each and every change attempted. Same issue, either in getting 89 empty bytes or im hitting my asserts for not succeeding at getting the context or so on.
#include <Windows.h>
#include <stdint.h>
#include <winscard.h>
#include <stdio.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef uint32_t b32;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
#define l_persist static
#define gvar static
#define internal static
#define KiloBytes(Value) ((Value) * 1024LL)
#define MegaBytes(Value) (KiloBytes(Value) * 1024LL)
#if INTERNAL
#define Assert(exp) if (!(exp)) {*((u8*)0) = 0;}
#define AssertWithMessage(exp, message) if (!(exp)) {OutputDebugString("\nAssertion failed: "); OutputDebugString(message); OutputDebugString("\n"); Assert(exp);}
#else
#define Assert(exp)
#define AssertWithMessage(exp, message)
#endif
#define U32_MAX 0xFFFFFFFF
b32 G_IsRunning = true;
LRESULT CALLBACK MainWindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam)
{
LRESULT Result = 0;
switch(Message)
{
case WM_CLOSE:
case WM_QUIT:
case WM_DESTROY:
{
G_IsRunning = false;
} break;
default:
{
Result = DefWindowProc(Window, Message, WParam, LParam);
}
}
return Result;
}
void MessagePump(HWND Window)
{
MSG Message = {};
u32 InputCount = 0;
while(PeekMessage(&Message, Window, 0, 0, PM_REMOVE))
{
switch(Message.message)
{
case WM_QUIT:
{
G_IsRunning = false;
} break;
default:
{
TranslateMessage(&Message);
DispatchMessage(&Message);
} break;
}
}
}
INT CALLBACK
WinMain(HINSTANCE Instance, HINSTANCE PrevInstance, LPSTR CommandLine, INT ShowCommand)
{
WNDCLASS WindowClass = {};
WindowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
WindowClass.lpfnWndProc = MainWindowCallback;
WindowClass.hInstance = Instance;
WindowClass.lpszClassName = "MainWindowClass";
if (RegisterClass(&WindowClass))
{
HWND Window = CreateWindowEx(0,WindowClass.lpszClassName, "Main Window", WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,0,0,Instance, 0);
if (Window)
{
SCARDCONTEXT CardContext = U32_MAX;
LONG CardContextSuccess = SCardEstablishContext(SCARD_SCOPE_USER, 0, 0, &CardContext);
Assert(CardContextSuccess == SCARD_S_SUCCESS);
u8 *Memory = (u8*)VirtualAlloc(0, KiloBytes(1), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
u8 *MemoryHead = Memory;
LPSTR ReadersList = (char*)MemoryHead;
LPSTR ReadersListWip = 0;
DWORD ReadersListSize = 0;
LONG CardReadersListStatus = SCardListReadersA(CardContext, 0, (LPSTR)&ReadersList, &ReadersListSize);
Assert(ReadersList);
Assert(ReadersListSize);
switch(CardReadersListStatus)
{
case SCARD_S_SUCCESS:
{
ReadersListWip = ReadersList;
while ( '\0' != *ReadersListWip )
{
printf("Reader: %S\n", (wchar_t*)ReadersListWip );
// Advance to the next value.
ReadersListWip = ReadersListWip + wcslen((wchar_t *)ReadersListWip) + 1;
}
} break;
case SCARD_E_NO_READERS_AVAILABLE:
{
AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "There were no card readers available");
G_IsRunning = false;
} break;
case SCARD_E_READER_UNAVAILABLE:
{
AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "The card reader was unavailable");
G_IsRunning = false;
} break;
default:
{
AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "Some other issue with card reader listing");
G_IsRunning = false;
}
}
while(G_IsRunning)
{
MessagePump(Window);
LPSCARDHANDLE CardHandle = 0;
DWORD ActiveProtocol = 0;
LONG CardConnectStatus = SCardConnectA(CardContext, ReadersList, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, (LPSCARDHANDLE)&CardHandle, &ActiveProtocol);
Assert(CardConnectStatus == SCARD_S_SUCCESS);
}
}
}
return 0;
}
Above is the code as it stands after my last batch of tests. I am quite rusty but I can't see why I'm getting a size back but not getting the strings.
Is there an alternative api for card reading? I recognize that the loop is no good at the end there that was an artefact left from some previous efforts earlier on. I'm stepping through it anyway so I'm ignoring it for now.
Thanks to Raymond Chen's comments I've realised my errors and can progress in learning the SCARD api. Mostly my mistake, but I feel that the documentation on MSDN could have been clearer. Particularly in regards to the third argument to SCardListReaders() which is an in/out parameter. I did not realise that setting the parameter to zero would return the size and then would require a second call to the same function with the buffer of sufficient length. I failed to notice the SCARD_AUTOALLOCATE section of the documentation. Not entirely sure how I manage to overlook that but I somehow did. I found this function quite confusing for those reasons.
Credit for helping me realise my errors goes to the infamous Raymond Chen.
Below I've written some annotated demonstration code for anyone in future who might find the api confusing as I found earlier this evening.
LPSTR ReadersList; //im no longer initializing to zero here because a null argument tells the function to just return the size of the string(s).
#define one_call_version 0
#if one_call_version
DWORD ReadersListSize = SCARD_AUTOALLOCATE; //passing this will cause the function to allocate some memory for you and return the strings inside that block of memory.
LONG CardReadersListStatus = SCardListReadersA(CardContext, 0, (LPSTR)&ReadersList, &ReadersListSize);
//ReadersListSize now points to a populate block of memory that needs to be frees via SCardFreeMemory or process termination.
#else //ReadersList = (LPSTR)calloc(ReadersListSize, sizeof(char)); //ReadersList = (LPSTR)malloc(ReadersListSize);
DWORD ReadersListSize = 0;
LONG CardReadersListStatus = SCardListReadersA(CardContext, 0, 0, &ReadersListSize); //1ST// call with null 3rd argument will just get the size of the readers list string
//ReadersListSize should now be modified to contain the minimum required size of memory allocation to contain the string(s) in bytes.
ReadersList = (LPSTR)VirtualAlloc(0, ReadersListSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
CardReadersListStatus = SCardListReaders(CardContext, 0, ReadersList, &ReadersListSize); //2ND// call with the buffer of sufficient size for the string(s) as 3rd argument should populate the buffer
#endif //Should now have the ReadersList populated with the names of the connected smart card reader devices.