智能卡终端删除:SCARD_E_NO_SERVICE CardException

我正在开发一个使用smartcardio来处理智能卡的Java应用程序。 必须有一个可以移除其USB读卡器,然后再次插入它而无需再次启动小程序。

我正在使用terminals()waitForChange()方法来检测终端更改,它在Linux,MacOS和Win7上运行正常。

但是在Windows 8(仅限Windows 8)上,删除最后一个终端后,这些方法抛出SCARD_E_NO_SERVICE CardException ,并且不再检测到任何更改。

我不确定它所说的“服务”是什么。 但是当我调用TerminalFactory.getDefault()来创建一个TerminalFactory单例时,我认为这是在我的线程中启动的。 而且我认为这个单例可能有办法管理底层服务,这就是被打破的。

有没有人知道如何在Windows 8上使用smartcardio管理终端断开连接?

这篇文章很老了,但是对我来说修复Windows 8中描述的问题非常有用。

来自JR Utily的解决方案没有完全发挥作用:如果读取器未插入然后再次插入,则CardTerminal实例上存在错误。

所以我添加了一些代码来清除终端列表,如下面的代码所示。

  Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals"); Field contextId = pcscterminal.getDeclaredField("contextId"); contextId.setAccessible(true); if(contextId.getLong(pcscterminal) != 0L) { // First get a new context value Class pcsc = Class.forName("sun.security.smartcardio.PCSC"); Method SCardEstablishContext = pcsc.getDeclaredMethod( "SCardEstablishContext", new Class[] {Integer.TYPE } ); SCardEstablishContext.setAccessible(true); Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER"); SCARD_SCOPE_USER.setAccessible(true); long newId = ((Long)SCardEstablishContext.invoke(pcsc, new Object[] { SCARD_SCOPE_USER.getInt(pcsc) } )); contextId.setLong(pcscterminal, newId); // Then clear the terminals in cache TerminalFactory factory = TerminalFactory.getDefault(); CardTerminals terminals = factory.terminals(); Field fieldTerminals = pcscterminal.getDeclaredField("terminals"); fieldTerminals.setAccessible(true); Class classMap = Class.forName("java.util.Map"); Method clearMap = classMap.getDeclaredMethod("clear"); clearMap.invoke(fieldTerminals.get(terminals)); } 

我找到了一种方法,但它使用了reflection代码。 我宁愿找到一个更干净的方法,但似乎没有管理智能卡上下文的官方API。 所有课程都是私人的。

sun.security.smartcardio.PCSCTerminalshttp://www.docjar.com/html/api/sun/security/smartcardio/PCSCTerminals.java.html )的initContext()方法可防止新线程获取新的上下文后第一个被初始化:调用该方法,但上下文被视为单例并且不会重新初始化。

使用java.lang.reflect在其周围的所有内容中传递private ,可以强制创建新上下文并将其新id存储为“官方” contextId 。 这应该在实现新的TerminalFactory之前完成。

  // ... Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals"); Field contextId = pcscterminal.getDeclaredField("contextId"); contextId.setAccessible(true); if(contextId.getLong(pcscterminal) != 0L) { Class pcsc = Class.forName("sun.security.smartcardio.PCSC"); Method SCardEstablishContext = pcsc.getDeclaredMethod( "SCardEstablishContext", new Class[] {Integer.TYPE } ); SCardEstablishContext.setAccessible(true); Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER"); SCARD_SCOPE_USER.setAccessible(true); long newId = ((Long)SCardEstablishContext.invoke(pcsc, new Object[] { Integer.valueOf(SCARD_SCOPE_USER.getInt(pcsc)) } )).longValue(); contextId.setLong(pcscterminal, newId); } // ... 

(这只是一个评论,但我没有足够的代表发表评论。)

它所指的服务是Windows智能卡服务,也称为智能卡资源管理器。 如果打开服务 MMC控制台,您将看到它,启动类型设置为手动(触发启动) 。 在Windows 8中,此服务仅在智能卡读卡器连接到系统时更改为运行(以节省资源),并且在删除最后一个读卡器时服务自动停止。 停止服务会使任何未完成的句柄无效。

本机Windows解决方案是调用SCardAccessStartedEvent并使用它返回的句柄等待服务启动,然后再使用SCardEstablishContext连接到资源管理器。