本文提供了在读卡器中插入智能卡时发生的错误的解决方案。
原始 KB 数: 976832
现象
将智能卡插入智能卡读卡器时,Windows 会尝试通过即插即用服务下载和安装卡的智能卡微型驱动程序。 如果智能卡的驱动程序在任何预配置位置(如 Windows 更新、WSUS 或 Intranet 路径)上不可用,并且系统上尚未安装自定义加密服务提供商,则会在通知区域中收到以下错误消息:
设备驱动程序软件未成功安装
单击此处了解详细信息。
此错误消息在几秒钟后消失。
此外,在设备管理器,在“其他设备”下,智能卡设备的状态为 DNF(找不到驱动程序)。
这经常要求用户从智能卡颁发者获取以下项之一来解决此错误:
- Windows 记录的智能卡微型驱动程序。
- 智能卡的自定义加密服务提供程序(CSP)。
- Windows 非徽标智能卡微型驱动程序。
- 其他中间件,例如 ActiveX 控件、PKCS#11 软件或其他自定义软件。
但是,如果用户仅提供此列表中的项目 3 或 4,智能卡将继续在系统上工作。 但是,用户每次插入智能卡时都会收到本节中提到的错误消息。
此问题会影响 Windows 7、Windows Server 2008 R2 和这两种操作系统的更高版本中的所有版本。
原因
所有智能卡都需要其他软件才能在 Windows 中工作,除非有一个收件箱驱动程序,让用户无需安装其他软件即可使用该卡。 Windows 7 中改进了 Windows 智能卡框架,以便在智能卡插入读卡器时自动从Windows 更新或从其他类似位置(例如 WSUS 服务器)下载智能卡微型驱动程序。 成功通过 Windows 徽标计划发布的徽标要求的所有智能卡都受益于此功能。
但是,如果 Windows 中使用智能卡所需的软件未标记或类型不同于微型驱动程序(例如 PKCS#11 驱动程序、自定义 CSP、中间件或 ActiveX 控件),则自动下载选项将失败,因为Microsoft仅认证智能卡微型驱动程序。 因此,如果用户插入了尚未注册自定义 CSP 的卡,则用户会收到一条错误消息,指出智能卡设备缺少驱动程序软件,即使用户可以通过自定义安装中安装在用户计算机上的其他软件来使用智能卡。
解决方法
尽管智能卡仍可正常工作,尽管用户看到的错误消息,但智能卡颁发者、供应商或制造商可以使用以下方法之一来解决此错误。
实现智能卡微型驱动程序
我们建议卡颁发者、供应商和制造商实施智能卡微型驱动程序并参与 Windows 徽标计划,以受益于智能卡即插即用、智能卡设备阶段等平台中引入的改进。
为智能卡实现 NULL 驱动程序
如果自定义软件(如 PKCS#11 驱动程序、ActiveX 控件或其他中间件)需要在 Windows 上启用智能卡的使用,并且实现智能卡微型驱动程序或自定义 CSP 不是实用选项,我们建议卡颁发者、供应商或制造商考虑将 NULL 驱动程序提交到Windows 更新。 确保 NULL 驱动程序在Windows 更新上可用的典型过程需要通过 Winqual 成功提交未分类的设备。 如果将来有一个可用于这些卡的微型驱动程序,则可以通过参与 Windows 徽标计划将新驱动程序上传到Windows 更新。 然后,最终用户可以手动下载 NULL 驱动程序,也可以使用可选更新提供。
下面是智能卡的 NULL 驱动程序的示例模板。
;
; Null Driver for Fabrikam Smartcard installation x86 and x64 package.
;
[Version]
Signature="$Windows NT$"
Class=SmartCard
ClassGuid={990A2BD7-E738-46c7-B26F-1CF8FB9F1391}
Provider=%ProviderName%
CatalogFile=delta.cat
DriverVer=4/21/2006,1.0.0.0
[Manufacturer]
%ProviderName%=Minidriver,NTamd64,NTamd64.6.1,NTx86,NTx86.6.1
[Minidriver.NTamd64]
;This driver has no applicability on OS versions earlier than Windows 7
[Minidriver.NTx86]
;This driver has no applicability on OS versions earlier than Windows 7
[Minidriver.NTamd64.6.1]
%CardDeviceName%=Minidriver64_Install,<DEVICE_ID>
;%CardDeviceName%=Minidriver64_Install,<DEVICE_ID2>
;%CardDeviceName%=Minidriver64_Install,<DEVICE_ID3>
;...
[Minidriver.NTx86.6.1]
%CardDeviceName%=Minidriver32_Install,<DEVICE_ID>
;%CardDeviceName%=Minidriver32_Install,<DEVICE_ID2>
;%CardDeviceName%=Minidriver32_Install,<DEVICE_ID3>
;...
;Leave the following sections blank
[DefaultInstall]
[DefaultInstall.ntamd64]
[DefaultInstall.NTx86]
[DefaultInstall.ntamd64.6.1]
[DefaultInstall.NTx86.6.1]
[Minidriver64_Install.NT]
[Minidriver64_61_Install.NT]
[Minidriver32_Install.NT]
[Minidriver32_61_Install.NT]
[Minidriver64_61_Install.NT.Services]
AddService = ,2
[Minidriver32_61_Install.NT.Services]
AddService = ,2
; =================== Generic ==================================
[Strings]
ProviderName ="Microsoft"
CardDeviceName="Fabrikam Generic Smart card"
若要生成示例中DEVICE_ID字符串引用的硬件设备 ID,请按照智能卡微型驱动程序规范中的说明进行操作。
有关如何将 NULL 驱动程序提交到Microsoft的详细信息,请联系Microsoft客户支持服务。
通过托管计算机的组策略禁用智能卡即插即用
此选项仅适用于由管理员管理的计算机的企业部署,以及使用企业中使用的智能卡所需的所有软件都是使用软件管理工具(如 SMS)安装的。
不建议在以下环境中执行此过程,因为它会影响环境中的所有智能卡:
- 面向最终用户(例如在线银行)的商业部署。
- 包括即插即用智能卡和非即插即用智能卡的环境,这些智能卡使用组策略禁用智能卡即插即用。
在最终用户的计算机由组策略等机制管理的企业中,可以禁用智能卡即插即用。
如果部署仅使用非即插即用智能卡解决方案,客户端计算机上的本地管理员可以禁用智能卡即插即用。 禁用智能卡即插即用可防止智能卡驱动程序(也称为智能卡微型驱动程序)下载。 它还会阻止智能卡即插即用提示。
若要在本地组策略中禁用智能卡即插即用,请执行以下步骤:
单击“开始”,在“搜索程序和文件”框中键入 gpedit.msc,然后按 Enter。
在“计算机配置”下的控制台树中,单击“管理模板”。
在详细信息窗格中,双击 Windows 组件,然后双击 智能卡。
右键单击“打开智能卡即插即用服务”,然后单击“编辑”。
单击“已禁用”,然后单击“确定”。
更改最终用户的系统并为特定卡禁用智能卡即插即用
这是建议最少的选项。 仅当卡是旧卡且将来没有实施智能卡微型驱动程序的计划时,才应使用此选项。 此选项要求系统上已安装的现有软件通知 Windows 系统,即使最终用户系统上不存在此类 CSP,系统也安装了自定义 CSP。 Windows 确定系统上已安装自定义 CSP 后,Windows 不会尝试通过智能卡即插即用下载并安装驱动程序。 不会为智能卡设备创建任何设备节点,该节点在设备管理器中可见。 此选项会导致对系统注册表进行以下更改:
子项:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Calais\SmartCards\<Smart card name>
子项注册表项:
ATR=十六进制 DWORD:智能卡的逗号分隔 ATR。
ATRMask= 十六进制 DWORD:要应用于 ATR 的逗号分隔掩码,以屏蔽 ATR 中微不足道的字节。
Crypto Provider=String 值:与智能卡相关的一些字符串。
例如:
子项:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Calais\SmartCards\Fabrikam ATM card
子项注册表项:
- ATR=十六进制 DWORD:3b,dc,13,00,40,3a,49,54,47,5f,4d,53,53,50,5f,56,32
- ATRMask= 十六进制 DWORD: ff、ff、ff、ff、ff、ff、ff
- Crypto Provider=String value: Fabrikam ATM Dummy Provider
对于 x64 位系统,必须在以下子项下进行相同的更改: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Cryptography\Calais\SmartCards
我们建议使用 WinSCard API 来引入系统更改,而不是直接更改系统注册表。 下面是一个示例代码示例,它检测智能卡插入,然后通过创建将卡与非现有提供程序关联的注册表项来禁用特定卡的智能卡即插即用。
Microsoft 提供的编程示例仅用于进行说明,而不提供明示或默示担保。 这包括但不限于适销性或对特定用途的适用性的默示担保。 本文假设您熟悉正在演示的编程语言和用于创建和调试过程的工具。 Microsoft 支持工程师可以帮助解释特定过程的功能。 但是他们不会修改这些示例以提供额外的功能,也不会构建过程以满足您的特定要求。
//==============================================================;
//
// Disable Smart card Plug and Play for specific cards
//
// Abstract:
// This is an example of how to create a new
// Smart Card Database entry when a smart card is inserted
// into the computer.
//
// This source code is only intended as a supplement to existing Microsoft
// documentation.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED. THIS INCLUDES BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (C) Microsoft Corporation. All Rights Reserved.
//==============================================================;
// This code must be compiled with UNICODE support to work correctly
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <winscard.h>
#include <stdio.h>
#include <strsafe.h>
#include <rpc.h>
// Change this prefix to specify what the beginning of the
// introduced card name in the registry will be. This is
// be prepended to a GUID value.
#define CARD_NAME_PREFIX L"MyCustomCard"
// This is the name that will be provided as the CSP for
// the card when introduced to the system. This is provided
// in order to disable Smart Card Plug and Play for this
// card.
#define CARD_CSP L"$DisableSCPnP$"
// This special reader name is used to be notified when
// a reader is added to or removed from the system through
// SCardGetStatusChange.
#define PNP_READER_NAME L"\\\\?PnP?\\Notification"
// Maximum ATR length plus alignment bytes. This value is
// used in the SCARD_READERSTATE structure
#define MAX_ATR_LEN 36
LONG GenerateCardName(
__deref_out LPWSTR *ppwszCardName)
{
LONG lReturn = NO_ERROR;
HRESULT hr = S_OK;
DWORD cchFinalString = 0;
WCHAR wszCardNamePrefix[] = CARD_NAME_PREFIX;
LPWSTR pwszFinalString = NULL;
UUID uuidCardGuid = {0};
RPC_WSTR pwszCardGuid = NULL;
RPC_STATUS rpcStatus = RPC_S_OK;
// Parameter check
if (NULL == ppwszCardName)
{
wprintf(L"Invalid parameter in GenerateCardName.\n");
return ERROR_INVALID_PARAMETER;
}
// Generate GUID
rpcStatus = UuidCreate(&uuidCardGuid);
if (RPC_S_OK != rpcStatus)
{
wprintf(L"Failed to create new GUID with error 0x%x.\n");
lReturn = (DWORD)rpcStatus;
}
else
{
// Convert GUID to string
rpcStatus = UuidToString(&uuidCardGuid, &pwszCardGuid);
if (RPC_S_OK != rpcStatus)
{
wprintf(L"Failed to convert new GUID to string with error 0x%x.\n", rpcStatus);
lReturn = (DWORD)rpcStatus;
}
else
{
// Allocate memory for final string
// Template is <prefix>-<guid>
cchFinalString = (DWORD)(wcslen(wszCardNamePrefix) + 1 + wcslen((LPWSTR)pwszCardGuid) + 1);
pwszFinalString = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, cchFinalString * sizeof(WCHAR));
if (NULL == pwszFinalString)
{
wprintf(L"Out of memory.\n");
lReturn = ERROR_OUTOFMEMORY;
}
else
{
// Create final string
hr = StringCchPrintf(
pwszFinalString,
cchFinalString,
L"%s-%s",
wszCardNamePrefix,
pwszCardGuid);
if (FAILED(hr))
{
wprintf(L"Failed to create card name with error 0x%x.\n", hr);
lReturn = (DWORD)hr;
}
else
{
// Set output params
*ppwszCardName = pwszFinalString;
pwszFinalString = NULL;
}
}
}
}
if (NULL != pwszCardGuid)
{
RpcStringFree(&pwszCardGuid);
}
if (NULL != pwszFinalString)
{
HeapFree(GetProcessHeap(), 0, pwszFinalString);
}
return lReturn;
}
LONG IntroduceCardATR(
__in SCARDCONTEXT hSC,
__in LPBYTE pbAtr,
__in DWORD cbAtr)
{
LONG lReturn = NO_ERROR;
LPWSTR pwszCardName = NULL;
// Parameter checks
if (NULL == hSC || NULL == pbAtr || 0 == cbAtr)
{
wprintf(L"Invalid parameter in IntroduceCardATR.\n");
return ERROR_INVALID_PARAMETER;
}
// Generate a name for the card
lReturn = GenerateCardName(&pwszCardName);
if (NO_ERROR != lReturn)
{
wprintf(L"Failed to generate card name with error 0x%x.\n", lReturn);
}
else
{
// Introduce the card to the system
lReturn = SCardIntroduceCardType(
hSC,
pwszCardName,
NULL,
NULL,
0,
pbAtr,
NULL,
cbAtr);
if (SCARD_S_SUCCESS != lReturn)
{
wprintf(L"Failed to introduce card '%s' to system with error 0x%x.\n", pwszCardName, lReturn);
}
else
{
// Set the provider name
lReturn = SCardSetCardTypeProviderName(
hSC,
pwszCardName,
SCARD_PROVIDER_CSP,
CARD_CSP);
if (SCARD_S_SUCCESS != lReturn)
{
wprintf(L"Failed to set CSP for card '%s' with error 0x%x.\n", pwszCardName, lReturn);
}
else
{
wprintf(L"Card '%s' has been successfully introduced to the system and has had Plug and Play disabled.\n", pwszCardName);
}
}
}
if (NULL != pwszCardName)
{
HeapFree(GetProcessHeap(), 0, pwszCardName);
}
return lReturn;
}
LONG ProcessCard(
__in SCARDCONTEXT hSC,
__in LPSCARD_READERSTATE pRdr)
{
LONG lReturn = NO_ERROR;
DWORD dwActiveProtocol = 0;
DWORD cbAtr = MAX_ATR_LEN;
DWORD dwIndex = 0;
DWORD cchCards = SCARD_AUTOALLOCATE;
LPWSTR pmszCards = NULL;
BYTE rgbAtr[MAX_ATR_LEN] = {0};
SCARDHANDLE hSCard = NULL;
// Parameter checks
if (NULL == hSC || NULL == pRdr)
{
wprintf(L"Invalid parameter in ProcessCard.\n");
return ERROR_INVALID_PARAMETER;
}
// Connect to the card in the provided reader in shared mode
lReturn = SCardConnect(
hSC,
pRdr->szReader,
SCARD_SHARE_SHARED,
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
&hSCard,
&dwActiveProtocol);
if (SCARD_S_SUCCESS != lReturn)
{
wprintf(L"Failed to connect to card in reader '%s' with error 0x%x.\n", pRdr->szReader, lReturn);
}
else
{
wprintf(L"Connected to card in reader '%s'.\n", pRdr->szReader);
/*
* In this spot, put any necessary calls needed to identify that this
* is the type of card you are looking for. Usually this is done via
* SCardTransmit calls. For this example, we will grab the ATR of every
* inserted card.
*/
// Obtain the ATR of the inserted card
lReturn = SCardGetAttrib(
hSCard,
SCARD_ATTR_ATR_STRING,
rgbAtr,
&cbAtr);
if (SCARD_S_SUCCESS != lReturn)
{
wprintf(L"Failed to obtain ATR of card in reader '%s' with error 0x%x.\n", pRdr->szReader, lReturn);
}
else
{
// Output the ATR
wprintf(L"ATR of card in reader '%s':", pRdr->szReader);
for (dwIndex = 0; dwIndex < cbAtr; dwIndex++)
{
wprintf(L" %02x", rgbAtr[dwIndex]);
}
wprintf(L"\n");
// Determine if the ATR is already in the Smart Card Database
lReturn = SCardListCards(
hSC,
rgbAtr,
NULL,
0,
(LPWSTR)&pmszCards,
&cchCards);
if (SCARD_S_SUCCESS != lReturn)
{
wprintf(L"Failed to determine if card in reader '%s' is currently recognized by the system with error 0x%x. Skipping.\n", pRdr->szReader, lReturn);
}
else if (NULL == pmszCards || 0 == *pmszCards)
{
// Card not found. We need to add it.
wprintf(L"Card in reader '%s' is not currently recognized by the system. Adding ATR.\n", pRdr->szReader);
lReturn = IntroduceCardATR(
hSC,
rgbAtr,
cbAtr);
// If an error occurs here, we will continue so we can try the next time
// the card is inserted as well as examine other readers.
}
else
{
wprintf(L"Card in reader '%s' is already known by the system. Not adding ATR.\n", pRdr->szReader);
}
}
}
// Disconnect from the card. We do not need to reset it.
if (NULL != hSCard)
{
SCardDisconnect(hSCard, SCARD_LEAVE_CARD);
}
// Free resources
if (NULL != pmszCards)
{
SCardFreeMemory(hSC, pmszCards);
}
return lReturn;
}
LONG MonitorReaders(
__in SCARDCONTEXT hSC)
{
LPWSTR pwszReaders = NULL;
LPWSTR pwszOldReaders = NULL;
LPWSTR pwszRdr = NULL;
DWORD dwRet = ERROR_SUCCESS;
DWORD cchReaders = SCARD_AUTOALLOCATE;
DWORD dwRdrCount = 0;
DWORD dwOldRdrCount = 0;
DWORD dwIndex = 0;
LONG lReturn = NO_ERROR;
BOOL fDone = FALSE;
SCARD_READERSTATE rgscState[MAXIMUM_SMARTCARD_READERS+1] = {0};
SCARD_READERSTATE rgscOldState[MAXIMUM_SMARTCARD_READERS+1] = {0};
LPSCARD_READERSTATE pRdr = NULL;
// Parameter check
if (NULL == hSC)
{
wprintf(L"Invalid parameter in MonitorReaders.\n");
return ERROR_INVALID_PARAMETER;
}
// One of the entries for monitoring will be to detect new readers
// The first time through the loop will be to detect whether
// the system has any readers.
rgscState[0].szReader = PNP_READER_NAME;
rgscState[0].dwCurrentState = SCARD_STATE_UNAWARE;
dwRdrCount = 1;
while (!fDone)
{
while (!fDone)
{
// Wait for status changes to occur
wprintf(L"Monitoring for changes.\n");
lReturn = SCardGetStatusChange(
hSC,
INFINITE,
rgscState,
dwRdrCount);
switch (lReturn)
{
case SCARD_S_SUCCESS:
// Success
break;
case SCARD_E_CANCELLED:
// Monitoring is being cancelled
wprintf(L"Monitoring cancelled. Exiting.\n");
fDone = TRUE;
break;
default:
// Error occurred
wprintf(L"Error 0x%x occurred while monitoring reader states.\n", lReturn);
fDone = TRUE;
break;
}
if (!fDone)
{
// Examine the status change for each reader, skipping the PnP notification reader
for (dwIndex = 1; dwIndex < dwRdrCount; dwIndex++)
{
pRdr = &rgscState[dwIndex];
// Determine if a card is now present in the reader and
// it can be communicated with.
if ((pRdr->dwCurrentState & SCARD_STATE_EMPTY ||
SCARD_STATE_UNAWARE == pRdr->dwCurrentState) &&
pRdr->dwEventState & SCARD_STATE_PRESENT &&
!(pRdr->dwEventState & SCARD_STATE_MUTE))
{
// A card has been inserted and is available.
// Grab its ATR for addition to the database.
wprintf(L"A card has been inserted into reader '%s'. Grabbing its ATR.\n", pRdr->szReader);
lReturn = ProcessCard(hSC, pRdr);
// If an error occurs here, we will continue so we can try the next time
// the card is inserted as well as examine other readers.
}
// Save off the new state of the reader
pRdr->dwCurrentState = pRdr->dwEventState;
}
// Now see if the number of readers in the system has changed.
// Save its new state as the current state for the next loop.
pRdr = &rgscState[0];
pRdr->dwCurrentState = pRdr->dwEventState;
if (pRdr->dwEventState & SCARD_STATE_CHANGED)
{
wprintf(L"Reader change detected.\n");
break;
}
}
}
if (!fDone)
{
// Clean up previous loop
if (NULL != pwszOldReaders)
{
SCardFreeMemory(hSC, pwszOldReaders);
pwszOldReaders = NULL;
}
pwszReaders = NULL;
cchReaders = SCARD_AUTOALLOCATE;
// Save off PnP notification reader state and and list of readers previously found in the system
memcpy_s(&rgscOldState[0], sizeof(SCARD_READERSTATE), &rgscState[0], sizeof(SCARD_READERSTATE));
memset(rgscState, 0, sizeof(rgscState));
dwOldRdrCount = dwRdrCount;
pwszOldReaders = pwszReaders;
// Obtain a list of all readers in the system
wprintf(L"Building reader list.\n");
lReturn = SCardListReaders(
hSC,
NULL,
(LPWSTR)&pwszReaders,
&cchReaders);
switch (lReturn)
{
case SCARD_S_SUCCESS:
// Success
break;
case SCARD_E_NO_READERS_AVAILABLE:
// No readers in the system. This is OK.
lReturn = SCARD_S_SUCCESS;
break;
default:
// Error occurred
wprintf(L"Failed to obtain list of readers with error 0x%x.\n", lReturn);
fDone = TRUE;
break;
}
// Build the reader list for monitoring - NULL indicates end-of-list
// First entry is the PnP Notification entry.
pRdr = rgscState;
memcpy_s(&rgscState[0], sizeof(SCARD_READERSTATE), &rgscOldState[0], sizeof(SCARD_READERSTATE));
pRdr++;
pwszRdr = pwszReaders;
while ((NULL != pwszRdr) && (0 != *pwszRdr))
{
BOOL fFound = FALSE;
dwRdrCount++;
// Look for an existing reader state from a previous loop
for (dwIndex = 1; dwIndex < dwOldRdrCount; dwIndex++)
{
if ((lstrlen(pwszRdr) == lstrlen(rgscOldState[dwIndex].szReader)) &&
(0 == lstrcmpi(pwszRdr, rgscOldState[dwIndex].szReader)))
{
// Found a match. Copy it.
memcpy_s(pRdr, sizeof(SCARD_READERSTATE), &rgscOldState[dwIndex], sizeof(SCARD_READERSTATE));
fFound = TRUE;
break;
}
}
if (!fFound)
{
// New reader
pRdr->szReader = pwszRdr;
pRdr->dwCurrentState = SCARD_STATE_UNAWARE;
}
// Increment reader indices
pRdr++;
pwszRdr += lstrlen(pwszRdr)+1;
}
}
}
// Clean up resources
if (NULL != pwszReaders)
{
SCardFreeMemory(hSC, pwszReaders);
}
if (NULL != pwszOldReaders)
{
SCardFreeMemory(hSC, pwszOldReaders);
}
return lReturn;
}
LONG __cdecl main(
VOID)
{
DWORD dwRet = ERROR_SUCCESS;
SCARDCONTEXT hSC = NULL;
LONG lReturn = NO_ERROR;
HANDLE hStartedEvent = NULL;
// Get handle to event that will be signaled when the Smart Card Service is available
hStartedEvent = SCardAccessStartedEvent();
// Wait for the Smart Card Service to become available
dwRet = WaitForSingleObject(hStartedEvent, INFINITE);
if (WAIT_OBJECT_0 != dwRet)
{
wprintf(L"Wait for Smart Card Service failed with error 0x%x.\n", dwRet);
lReturn = dwRet;
}
else
{
// Establish a system-level context with the Smart Card Service
lReturn = SCardEstablishContext(
SCARD_SCOPE_SYSTEM,
NULL,
NULL,
&hSC);
if (SCARD_S_SUCCESS != lReturn)
{
wprintf(L"Failed to establish context with the Smart Card Service with error 0x%x.\n", lReturn);
}
else
{
// Begin monitoring the readers in the system
// This routine could be done in a separate thread so it can be cancelled via SCardCancel().
lReturn = MonitorReaders(hSC);
}
}
// Cleanup resources
if (NULL != hSC)
{
SCardReleaseContext(hSC);
}
if (NULL != hStartedEvent)
{
SCardReleaseStartedEvent();
}
wprintf(L"Done.\n");
return lReturn;
}
参考
有关智能卡即插即用问题疑难解答的详细信息,请参阅智能卡故障排除指南。
数据收集
如果需要 Microsoft 支持方面的帮助,建议按照使用 TSS 针对与部署相关的问题收集信息中所述的步骤收集信息。