使用 JAVA 智能卡 API 读取 NFC 标签在 MAC OS 上不起作用

Posted

技术标签:

【中文标题】使用 JAVA 智能卡 API 读取 NFC 标签在 MAC OS 上不起作用【英文标题】:Reading NFC Tag using JAVA Smart Card API not working on MAC OS 【发布时间】:2013-11-19 12:25:48 【问题描述】:

我正在开发一个应用程序来从 NFC 读取器 (ACR122U-A9) 设备读取 NFC 标签 UID。 我使用 JAVA 和 javax.smartcardio API 来检测 NFC Reader 和 Reading NFC Tag。

应用程序的功能是在 NFC 阅读器设备与 PC 连接或断开连接时显示通知。然后,如果设备已连接并且出现 NFC 标签,则显示出现 NFC 标签的通知。 我试图找到基于事件的 api 来实现上述功能,但我找不到,所以我对 NFC 读取器设备和 NFC 标签使用了 Java 计时器和轮询。

以下是我用于轮询 NFC 设备和标签的示例 JAVA 代码。

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.TerminalFactory;

/**
 *
 * @author sa
 */
public class NFC_Test 

    /**
     * @param args the command line arguments
     */
    static Timer timer;

    public static void main(String[] args) 

        try 


            timer = new Timer();  //At this line a new Thread will be created

            timer.scheduleAtFixedRate(new NFC_Test.MyTask(), 0, 1000);


         catch (Exception ex) 
            Logger.getLogger(NFC_Test.class.getName()).log(Level.SEVERE, null, ex);
        
    

    static class MyTask extends TimerTask 

        public void run() 


    ///////////////////This fix applied after reading thread at http://***.com/a/16987873/1411888
            try 
                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);
                
             catch (Exception ex) 
            
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

            TerminalFactory factory = null;
            List<CardTerminal> terminals = null;
            try 
                factory = TerminalFactory.getDefault();

                terminals = factory.terminals().list();
             catch (Exception ex)  //
                Logger.getLogger(NFC_Test.class.getName()).log(Level.SEVERE,null, ex);
            

            if (factory != null && factory.terminals() != null && terminals
                    != null && terminals.size() > 0) 
                try 
                    CardTerminal terminal = terminals.get(0);

                    if (terminal != null) 

                        System.out.println(terminal);
                        if (terminal.isCardPresent()) 
                            System.out.println("Card");
                         else 
                            System.out.println("No Card");
                        

                     else 
                        System.out.println("No terminal");
                    

                    terminal = null;
                 catch (Exception e) 
                    Logger.getLogger(NFC_Test.class.getName()).log(Level.SEVERE,null, e);
                
                factory = null;

                terminals = null;

                Runtime.getRuntime().gc();

             else 
                System.out.println("No terminal");
            

        
    

上面的代码在 Windows 操作系统中运行良好,但是当我在 MAC 操作系统上运行它时,应用程序完美地运行了 5-10 秒,但随后它突然崩溃并出现以下内存错误。

java(921,0x10b0c3000) malloc: *** mmap(size=140350941302784) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
Java Result: 139

我在互联网上搜索并找不到有关上述内存错误的任何信息。此外,我还包含了内存管理代码,以便在计时器中使用该对象时通过为其分配 NULL 值来释放该对象。

我用http://ludovicrousseau.blogspot.com/2010/06/pcsc-sample-in-java.html做参考

【问题讨论】:

“完美”在 Windows 上?我敢打赌它最终会爆炸。在这里,您在没有文档的情况下调用私有函数,并且您对发生了可怕的事情感到惊讶? 我调用的是哪个私有函数?我在使用反射阅读***.com/a/16987873/1411888 的线程后应用的修复。 @bmargulies 能否回复一下上面代码中调用了哪个私有函数。 【参考方案1】:

我相信这是我在尝试使用 OS X 上的 64 位 Java 上的 libj2pcsc.dylib 查找错误时遇到的错误之一。另请参阅 smartcardio thread on discussions.apple.com 和我的 email to security-dev。基本上,问题在于DWORD* 在 OS X 上应该是一个指向 32 位数字的指针,但 Sun 的库假定它是一个指向 64 位数字的指针。然后它取消引用该值并尝试分配该大小的缓冲区,该缓冲区可以包含高 32 位中的垃圾。在source of pcsc.c 中查看Java_sun_security_smartcardio_PCSC_SCardListReaders

可能的解决方法:

在调用Terminals.list()(间歇性崩溃)时要非常保守,不要相信Terminal.isCardPresent()Terminals.waitForChange(long)CardTerminal.waitForCard(boolean, long) 的结果。我的同事意识到他可以使用反射调用TerminalImpl.SCardGetStatusChange(long, long, int[], String[]) 以获得正确的结果。这是我们过去常做的。很痛苦! 修复 libj2pcsc.dylib 的头文件并重新编译 OpenJDK。这就是我们现在在我公司所做的事情。 切换到javax.smartcardio 的不同实现。我知道两个:我自己的jnasmartcardio 和intarsys/smartcard-io。不过,我还没有在 NFC 卡上尝试过我自己的库,但我欢迎任何错误报告和补丁。

【讨论】:

Thanx Yonran,你能给我举个例子吗“我的同事意识到他可以调用 TerminalImpl.SCardGetStatusChange(long, long, int[], String[]) 使用反射来获取正确的结果。这是我们过去常做的。”解决方法,因为我需要知道如何使用反射来调用它。我还将使用替代库尝试第三种解决方法。 @SwapnilTandel,其实第一个是错误的选择。不要继续使用损坏的默认 javax.smartcardio 实现。它很丑。此外,Terminals.list() 仍会间歇性崩溃。 我还在示例程序中尝试了 intarsys/smartcard-io,它在 Windows 和 MAC 的初始测试中运行良好,所以我正在将此库升级到我的程序中,看看会发生什么

以上是关于使用 JAVA 智能卡 API 读取 NFC 标签在 MAC OS 上不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Android系统级应用连续读取NFC标签实现

acr122U NFC 标签读取

acr122U NFC标签读取

NFC Mifare Ultralight 读取/写入使用 ACR122 的普通 Java 桌面应用程序

如何在靠近 NFC(智能卡)时停止我的 Android NFC 应用程序循环

Android API Guides---NFC Basics