有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java智能卡终端移除:SCARD_E_无服务卡例外

我正在开发一个Java应用程序,它使用smartcardio来处理智能卡。 必须能够让其中一个卸下USB读卡器,然后再次插入,而无需再次启动小程序

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

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

我不知道它在说什么“服务”。但我认为这是在我调用TerminalFactory.getDefault()以获得TerminalFactory单例时在我的线程中启动的。我认为这个单身者可能有办法管理底层服务,而这正是被打破的

有人知道如何在Windows8上管理终端断开与smartcardio的连接吗


共 (3) 个答案

  1. # 1 楼答案

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

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

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

  2. # 2 楼答案

    我找到了一种方法,但它使用的是反射代码。我宁愿找到一种更干净的方法,但似乎没有管理智能卡上下文的官方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);
        }
        // ...
    
  3. # 3 楼答案

    这篇文章已经很老了,但它对我解决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));
            }