问题
The smart card service behaves differently on Windows 8 and MSDN hasn't updated their documentation. Can anyone give a code snippet on how to call SCardGetStatusChange correctly to monitor smart card actions on Windows 8? Thanks in advance!
回答1:
Here is a C++ template function that I wrote for a personal blog project. It uses a library I am developing that is up on github, but you can also just rework the logic into your own context.
template<typename SetContext, typename ClearContext, typename Wait, typename Report>
unique_winerror monitor_smartcard_readers(
SetContext&& setContext,
ClearContext&& clearContext,
Wait&& wait,
Report&& report
)
{
unique_winerror winerror;
std::vector<wchar_t> readernames;
std::vector<SCARD_READERSTATE> readers;
while (winerror)
{
//
// make sure that the scard service has started
// and that the loop has not been cancelled
//
if (!std::forward<Wait>(wait)())
{
return winerror_cast(SCARD_E_CANCELLED);
}
monitor_error_contract(
[&] ()
{
unique_close_scardcontext context;
ON_UNWIND_AUTO(
[&]
{
std::forward<ClearContext>(clearContext)();
}
);
//
// need a fresh context whenever we start over.
// lots of sytem changes could have caused this
// restart including the scard service stopping
// and then restarting.
//
winerror.reset(
SCardEstablishContext(
SCARD_SCOPE_USER,
NULL,
NULL,
context.replace()
)
);
if (!winerror || !context)
{
return;
}
std::forward<SetContext>(setContext)(context.get());
//
// make sure that loop has not been cancelled.
// without this there is a race where the new
// context is not cancelled because the caller
// cancelled at a time when there was no
// context yet.
//
if (!std::forward<Wait>(wait)())
{
winerror = winerror_cast(SCARD_E_CANCELLED);
return;
}
if (readers.empty())
{
//
// add PnP state query
// setting the state to unaware causes SCardGetStatusChange
// to return immediately with the actual pnp state.
//
readers.push_back(make(L"\\\\?PnP?\\Notification"));
}
for(;;)
{
auto readersstaterange = lib::rng::make_range_raw(readers);
winerror.reset(
SCardGetStatusChange(
context.get(),
INFINITE,
readersstaterange.begin(),
lib::rng::size_cast<DWORD>(readersstaterange.size())
)
);
if (!winerror)
{
// exit
return;
}
//
// report changes
//
auto readersrange = lib::rng::make_range_raw(readers, 0, -1);
if (!readersrange.empty())
{
std::forward<Report>(report)(readersrange);
}
//
// record the changes we have reported
//
for (auto& state : readers)
{
state.dwCurrentState = state.dwEventState;
}
if ((readers.back().dwEventState & SCARD_STATE_CHANGED) == SCARD_STATE_CHANGED)
{
// Pnp event - list readers.
break;
}
}
// keep the old allocations for use to build the new list.
std::vector<wchar_t> oldreadernames(std::move(readernames));
std::vector<SCARD_READERSTATE> oldreaders(std::move(readers));
// exclude the pnp reader
auto oldreaderssortedrange = lib::rng::make_range(oldreaders, 0, -1);
LPWSTR concatreaderstrings = nullptr;
ON_UNWIND_AUTO(
[&] { if (concatreaderstrings) {SCardFreeMemory(context.get(), concatreaderstrings);};}
);
DWORD totallength = SCARD_AUTOALLOCATE;
winerror.reset(
SCardListReaders(
context.get(),
nullptr,
reinterpret_cast<LPWSTR>(&concatreaderstrings),
&totallength
)
);
if (winerror == winerror_cast(SCARD_E_NO_READERS_AVAILABLE))
{
// no readers is not an error, loop around to wait
// for a reader to be connected
winerror.suppress().release();
return;
}
else if (!winerror)
{
return;
}
// keep the names around because the state array will have pointers into this
readernames.assign(concatreaderstrings, concatreaderstrings + totallength);
auto readerstateless = [](const SCARD_READERSTATE& lhs, const SCARD_READERSTATE& rhs) -> bool
{
return _wcsicmp(lhs.szReader, rhs.szReader) < 0;
};
//
// all the reader names are concatenated in this array with
// embedded nulls for each and two nulls to mark the end
//
auto cursorreadernames = lib::rng::make_range_raw(readernames);
while(!cursorreadernames.empty() && cursorreadernames.front() != L'\0')
{
// access the current name
auto namerange = lib::rng::make_range(
cursorreadernames,
0,
wcslen(cursorreadernames.begin()) - cursorreadernames.size()
);
// skip to the next name
cursorreadernames = lib::rng::make_range(namerange, namerange.size() + 1, 0);
auto oldreader = std::equal_range(
oldreaderssortedrange.begin(),
oldreaderssortedrange.end(),
make(namerange.begin()),
readerstateless
);
if (oldreader.first != oldreader.second)
{
// keep the old state for this reader
readers.push_back(*oldreader.first);
// must use the new string allocation,
// the old one will be gone soon
readers.back().szReader = namerange.begin();
}
else
{
readers.push_back(make(namerange.begin()));
}
}
// keeping them sorted makes the updates more stable and allows the
// equal_range above instead of a linear find.
std::sort(readers.begin(), readers.end(), readerstateless);
//
// add PnP state query
// keep the existing state, and keep it at the
// end, out of the sorted area.
//
readers.push_back(oldreaders.back());
}
);
}
return winerror;
}
usage looks like this:
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define NOMINMAX
// Windows Header Files:
#include <windows.h>
#include <Unknwn.h>
#include <winscard.h>
#include <ncrypt.h>
#include <Wincrypt.h>
#include <credentialprovider.h>
// TODO: reference additional headers your program requires here
#include <type_traits>
#include <algorithm>
#include <new>
#include <memory>
#include <utility>
#include <limits>
#include <iterator>
#include <thread>
#include <future>
#include <mutex>
#include <vector>
#include <iostream>
#include <iomanip>
int wmain(int argc, WCHAR* argv[])
{
unique_winerror winerror;
for (;;)
{
SCARDCONTEXT context = NULL;
// if you monitor in a separate thread, then add a cancel or shutdown event
// into the waitfor array and handle it in the Wait lambda
HANDLE waitfor[] = {SCardAccessStartedEvent()};
ON_UNWIND_AUTO([] {SCardReleaseStartedEvent();});
winerror = smart_card::monitor_smartcard_readers(
[&](SCARDCONTEXT context)
{
context = context;
},
[&]()
{
context = NULL;
},
[&]() -> bool
{
if (WAIT_OBJECT_0 != WaitForMultipleObjects(lib::rng::size(waitfor), waitfor, FALSE, INFINITE))
{
// monitor_smardcard_readers will return SCARD_E_CANCELLED
return false;
}
return true;
},
[&](lib::rng::range<SCARD_READERSTATE*> readersrange)
{
for (auto& state : readersrange)
{
auto stateChanges = (state.dwCurrentState ^ state.dwEventState) & std::numeric_limits<unsigned short>::max();
std::wcout
<< L"nothread - "
<< state.szReader
<< L" changes: " << std::hex << std::showbase << stateChanges
<< L"["
;
printSCardState(std::wcout, stateChanges)
<< L"] state: " << std::hex << std::showbase << state.dwEventState
<< L"["
;
printSCardState(std::wcout, state.dwEventState)
<< L"]"
<< std::endl
;
if (state.dwCurrentState != SCARD_STATE_UNAWARE &&
((state.dwEventState & SCARD_STATE_PRESENT) != SCARD_STATE_PRESENT ||
stateChanges == SCARD_STATE_INUSE ||
stateChanges == SCARD_STATE_UNPOWERED ||
(state.dwEventState & (SCARD_STATE_UNPOWERED | SCARD_STATE_EMPTY | SCARD_STATE_IGNORE | SCARD_STATE_UNKNOWN | SCARD_STATE_UNAVAILABLE | SCARD_STATE_MUTE)) ||
state.cbAtr == 0))
{
// we have seen this reader before and one of:
// no card
// only flipped INUSE
// only flipped UNPOWERED
// UNPOWERED EMPTY UNKNOWN UNAVAILABLE MUTE
// no atr
//
// don't try to read the card
continue;
}
// read the card in the reader and list the certs on the card
}
}
);
winerror.suppress();
}
return 0;
}
回答2:
I know I'm over 2 years late, but maybe my reply can help someone nonetheless.
I have some simple code as a starting base for further development. I first created it on Windows 7; AFAICS it works fine on Windows 8 too. This uses only a single reader, but a previous iteration used a list of readers and it worked just as well. The relevant parts are as follows.
Initialization of the reader state structure:
memset(&m_State, 0, sizeof(m_State));
m_State.szReader = _wcsdup(m_ReaderName.c_str());
m_State.dwCurrentState = SCARD_STATE_UNAWARE;
Waiting for events:
bool TSmartCardReader::WaitForEvent(DWORD Timeout, TCardEvent &CardEvent)
{
CardEvent = None;
// Reset reader structure, except the specific fields we need
// (because that's what the docs say: "Important: Each member of each structure
// in this array must be initialized to zero and then set to specific values as
// necessary. If this is not done, the function will fail in situations that
// involve remote card readers.")
const wchar_t *szReader = m_State.szReader;
DWORD dwCurrentState = m_State.dwCurrentState;
memset(&m_State, 0, sizeof(m_State));
m_State.szReader = szReader;
m_State.dwCurrentState = dwCurrentState;
LONG rv = SCardGetStatusChangeW(m_hContext, Timeout, &m_State, 1);
if (rv == SCARD_S_SUCCESS)
{
HandleStatusChange(CardEvent);
// I'm not sure we really need to reset the SCARD_STATE_CHANGED bit
m_State.dwCurrentState = m_State.dwEventState & ~SCARD_STATE_CHANGED;
}
else if (rv == SCARD_E_TIMEOUT)
return false; // No status changes
else if (rv == SCARD_E_NO_READERS_AVAILABLE)
throw ESCNoReaders("No readers available");
else
throw ESCWaitForEvent(GetErrorText(rv));
return CardEvent != None;
}
As far as I understand the documentation, the key thing is that you set set dwCurrentState to what you believe is the current state of the reader. SCardGetStatusChange() takes that current state into account to decide what constitutes a state change.
来源:https://stackoverflow.com/questions/11294638/how-to-use-scardgetstatuschange-correctly-on-windows-8