3

智能卡服务在 Windows 8 上的行为有所不同,并且 MSDN 尚未更新其文档。谁能提供有关如何正确调用 SCardGetStatusChange 以监视 Windows 8 上的智能卡操作的代码片段?提前致谢!

4

2 回答 2

3

这是我为个人博客项目编写的 C++ 模板函数。它使用我在 github 上开发的一个库,但您也可以将逻辑重新设计到您自己的上下文中。

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;
}

用法如下所示:

#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;
}
于 2012-12-11T00:45:28.100 回答
3

我知道我迟到了 2 年多,但也许我的回复仍然可以帮助某人。

我有一些简单的代码作为进一步开发的起点。我首先在 Windows 7 上创建它;AFAICS 它在 Windows 8 上也可以正常工作。这只使用了一个阅读器,但之前的迭代使用了一个阅读器列表,并且效果一样好。相关部分如下。

读取器状态结构的初始化:

memset(&m_State, 0, sizeof(m_State));
m_State.szReader = _wcsdup(m_ReaderName.c_str());
m_State.dwCurrentState = SCARD_STATE_UNAWARE;

等待事件:

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;
}

据我了解文档,关键是您将 set dwCurrentState 设置为您认为是读者的当前状态。SCardGetStatusChange() 会考虑当前状态来决定什么构成状态更改。

于 2014-12-02T09:27:16.570 回答