使用 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 上不起作用的主要内容,如果未能解决你的问题,请参考以下文章
NFC Mifare Ultralight 读取/写入使用 ACR122 的普通 Java 桌面应用程序