资讯动态

JNA实战:Java调用读卡器DLL实现Mifare卡读写

发布时间:2026/9/2 2:42:23 来源:尧图企业网站定制
简介面向需要在Java工程中集成智能卡或射频识别RFID读卡能力的开发者这套精简示例演示了如何通过Java本地接口JNI调用明华RD读卡器官方驱动Mwic_32.dll。明华RD系列读卡器常用于读取智能卡、身份证等射频识别设备Mwic_32动态链接库为上层提供设备初始化、读卡、控制命令等底层函数Java程序需要先声明本地方法再借助System.loadLibrary加载动态库才能完成调用。包内共6个文件两个Java源码展示了与memory、fuction相关的本地方法接口定义和调用逻辑三个class文件是编译后产物方便直接运行或对照学习一个txt开发包说明则整理了驱动配置、动态库路径设置及常见错误处理要点对梳理JNI调用流程很有帮助。通过阅读代码可以掌握声明native方法、加载动态库、传递字节数组参数等关键步骤也能理解调用约定不匹配、库文件缺失等典型异常的处理思路为后续封装成独立模块打好基础。整个rar压缩包仅4KB小巧但结构完整。已有469人学习下载适合具备基础Java语法、需要为Mwic_32.dll编写Java调用的开发人员参考也可作为二次开发或排障的起点。1. 项目背景为什么Java要碰读卡器的DLL最近在做一个会员卡管理系统后端用 Java前端负责发卡和查询页面。卡机设备是明华 RD 系列读卡器厂家 SDK 的核心就是 Mwic_32.dll。这个 DLL 只有 C/C 接口文档和示例Java 要操作读卡器第一关就是解决“Java 怎么调用 DLL”的问题。Mifare 卡就是市面上最常见的 IC 卡的扇区读写、密钥认证、UID 读取全封装在这套 API 里所以只要能把 DLL 的函数调通后面所有业务就有了硬件基础。我在网上翻了很久大多数帖子停留在“可以用 JNA”这句话真正把环境坑点、函数映射、调用顺序讲明白的不多。所以这篇内容适合正在做同类设备对接的 Java 开发也适合第一次接触 JNA 调 DLL 的朋友。里面所有代码都是项目里实际跑通的不是从文档里抄一遍就发出来。我会把关键“为什么”也讲清楚不然换个型号、换个 DLL 版本你大概率还要再踩一遍我的坑。1.1 技术选型JNI、JNative、JNA 怎么选Java 调 Windows DLL 无非三条路JNI、JNative、JNA。JNI 是 Java 官方的原生接口但用起来最折腾。你得先用 javac 生成头文件再用 C/C 写一个中间层把这个中间层编译成另一个 DLLJava 通过它去调 Mwic_32.dll。等于自己维护一套 C 桥接代码编译环境要装跨平台要烧脑对业务项目来说成本太高。JNative 是老牌方案把底层细节包掉了一部分但用的人少维护早就停了在高版本 JDK 上容易出兼容问题。JNA 是 JNI 的“动态代理版”你只需要定义一个 Java 接口方法名和 DLL 导出函数名一一对应JNA 运行时自动生成桥接代码。对“只要调通 API、不追求底层控制”的项目来说这是最省事的。我把三个方案对比放在一起你心里大概就有数方案使用成本是否写 C 代码稳定性适合场景JNI高必须高需要极致性能或深度定制封装JNative中否中多年未更新老项目维护JNA低否高社区活跃业务系统对接成熟 DLL我最终选了 JNA后面所有内容也基于 JNA。选型理由说到底就一条公司的目标是做业务不是为读卡器写驱动。1.2 为什么要关注 DLL 位数和 JVM 位数Mwic_32.dll 从名字就能看出来它是 32 位动态库。Windows 上 32 位进程只能加载 32 位 DLL64 位进程只能加载 64 位 DLL这个规则非常硬。也就是说你用 64 位 JDK 启动 Java 程序哪怕 JNA 依赖写对了Native.load 一样会失败直接抛 UnsatisfiedLinkError。这个坑太常见了很多人第一反应是“代码哪里写错了”实际是 JDK 位数不对。所以环境准备的第一件事不是写代码而是确认 JVM 是 32 位的。你可以命令行执行 java -version输出里如果带 64-Bit就得装一个 32 位 JDK。我这边用 1.8 的 32 位版本跑了几个月很稳。另外开发机和生产机的位数最容易不一致发布前一定检查两边的 JVM。2. 项目准备依赖、硬件和运行环境2.1 Maven 依赖与基础工程配置要用 JNA先在 pom.xml 里加依赖。我用的是 5.13.0跑了一年多没出现问题后续版本接口也兼容。dependency groupIdnet.java.dev.jna/groupId artifactIdjna/artifactId version5.13.0/version /dependency顺便提醒一句如果项目编译时出现“源发行版 17 需要目标发行版 17”的报错别慌这不是业务代码问题是 Maven 编译器插件默认用的 JDK 版本和项目设置不一致。两个版本必须统一可以在 pom 里固定properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties如果你团队统一用 JDK 17那 source 和 target 都写 17关键是两个不能一边高一边低否则每次编译都会跳出这种提示。这类问题跟读卡器没关系但经常在准备环境阶段和 DLL 加载问题混在一起容易让人误判。2.2 硬件连接与端口确认明华 RD 读卡器有 RS232 串口版也有 USB 版。USB 版插上以后系统通常识别成一个虚拟串口设备管理器里能看到 COMx。写代码之前先打开设备管理器在“端口COM 和 LPT”下看当前是哪个 COM 口然后把厂商 Demo 跑一遍确认这个串口下读卡、写卡都正常再开始做 Java 调试。这一步看着多余但能把“硬件问题”和“代码问题”切开。我有一次折腾到半夜MF_Init 一直初始化失败后来发现读卡器根本没被识别成 COM 口而是被驱动装成了别的设备类别。先拿 Demo 验证能省掉后面一大半排查时间。默认波特率按厂商文档一般用 9600。如果你改动过读卡器设置这里也要跟着改否则会出现初始化成功但发指令没响应的情况。2.3 DLL 放置位置与最小加载测试Mwic_32.dll 到底放哪这是个容易忽略的问题。JNA 的 Native.load 会按这个顺序找jna.library.path 属性指定路径、系统 PATH 环境变量、当前目录。所以我习惯在程序启动时显式指定System.setProperty(jna.library.path, D:/rdreader/libs);如果不指定就把 DLL 放到系统 PATH 包含的目录里或者项目根目录。但生产环境不要依赖系统 PATH显式指定最稳妥。我建议正式功能之前先写一个最小加载测试只调用 MF_Init确认 DLL 能被正确加载并执行。加载失败时除了位数问题还可能是系统缺 Microsoft Visual C Redistributable 运行库顺手装上再试。这个点厂商文档一般不写但线上环境踩到过。3. 核心函数解析Mwic_32.dll 常用 API3.1 一张表看明白常用函数明华 Mwic_32.dll 函数风格很统一绝大多数返回 0 表示成功非 0 表示失败。下面是我项目里实际用到的函数参数以我手头这个 DLL 版本为准函数作用关键参数说明MF_Init初始化读卡器并打开串口port串口号baud波特率MF_Close关闭读卡器释放串口无MF_Request寻卡查卡是否在感应区ctrl控制字mode0x52 常用于包含休眠卡MF_Anticoll防冲突拿到卡 UIDsnr 是 4 字节缓冲区返回卡序列号MF_Select选中一张卡后续操作针它snr 必须传刚才拿到的 UIDMF_Halt让卡休眠常用于一卡一密流程中复位MF_LoadKey把密钥加载到读卡器内部密钥区sec 传扇区号key 是 6 字节密钥MF_Authentication用密钥对指定扇区认证mode0x60 验 A 密钥0x61 验 Bsec 传扇区号MF_Read读一个数据块block 传块号data 是 16 字节缓冲区MF_Write写一个数据块block 传块号data 是 16 字节数据这里要特别说明 sector 和 block 的关系。Mifare S50 卡有 16 个扇区每个扇区 4 个块块号从 0 到 63。块号等于 sector * 4 加上块内偏移0~3。认证是针对扇区级别的读数据是以块为单位的这两个概念不能混。我一开始以为认证传块号读卡器返回成功但读出来全是 00折腾半天才发现认证参数得传扇区号数据读写才传块号。3.2 JNA 接口定义把 C 函数翻译成 JavaJNA 的定义方式很直接建一个接口继承 Library方法名和 DLL 导出函数保持一致Native.load 加载动态库。注意加载名不写 .dll 后缀。import com.sun.jna.Library; import com.sun.jna.Native; public interface Mwic32 extends Library { Mwic32 INSTANCE Native.load(Mwic_32, Mwic32.class); int MF_Init(int port, int baud); int MF_Close(); int MF_Request(int ctrl, int mode, byte[] tagType); int MF_Anticoll(int ctrl, byte[] snr); int MF_Select(int ctrl, byte[] snr); int MF_Halt(int ctrl); int MF_LoadKey(int ctrl, int mode, int sec, byte[] key); int MF_Authentication(int ctrl, int mode, int sec); int MF_Read(int ctrl, int block, byte[] data); int MF_Write(int ctrl, int block, byte[] data); }这里有个关键点C 函数里的 unsigned char* 指针参数在 Java 里对应 byte[]不能用 String 或 int[]。JNA 会把数组直接映射成底层内存缓冲区DLL 直接读写这块内存所以数据才能正确返回。另外函数返回值必须用 int不要用 boolean因为错误码值很多不是只有 0 和 1。我见过有人把密钥参数声明成 String认证一直失败原因就是 JNA 把 Java String 转成了 C 的 char*UTF-8 编码和字节数全对不上。密钥、UID、数据块这些二进制内容统一用 byte[] 是最安全的做法。4. 实操从初始化到读写卡完整跑通4.1 初始化读卡器我封装了一个 CardService第一步是初始化。端口号不要猜直接遍历 0、1、2 去试哪个返回 0 就说明读卡器在哪个口。部分 DLL 版本里 port 0 代表自动探测但显式端口更可控private Mwic32 initReader() { int baud 9600; int ret -1; for (int p 0; p 4; p) { ret Mwic32.INSTANCE.MF_Init(p, baud); if (ret 0) { System.out.println(读卡器初始化成功端口 p); return Mwic32.INSTANCE; } } throw new IllegalStateException(读卡器初始化失败请检查串口连接和驱动); }初始化失败时除了端口问题还要看是不是有别的程序占用了串口。Windows 的串口是独占的厂商 Demo 没退出Java 这边就打不开这是最容易被忽略的“假故障”。实测下来关掉 Demo 后立刻就好了。4.2 寻卡、防冲突和选卡三步拿到卡片身份卡片放到感应区后流程是固定的MF_Request 寻卡MF_Anticoll 防冲突拿 UIDMF_Select 选中卡片。用生活例子理解寻卡是问“有没有人”防冲突是“人多了先排好队一个一个来”选卡是“叫的就是你别动”。byte[] tagType new byte[2]; int ret Mwic32.INSTANCE.MF_Request(0, 0x52, tagType); if (ret ! 0) { throw new IllegalStateException(寻卡失败请确认卡片已放在感应区); } byte[] snr new byte[4]; ret Mwic32.INSTANCE.MF_Anticoll(0, snr); if (ret ! 0) { throw new IllegalStateException(防冲突失败错误码 ret); } ret Mwic32.INSTANCE.MF_Select(0, snr); if (ret ! 0) { throw new IllegalStateException(选卡失败错误码 ret); } String uidHex bytesToHex(snr); System.out.println(当前卡片 UID: uidHex);MF_Anticoll 返回的 snr 就是卡 UID正好 4 字节。很多业务系统的“卡号”用的就是这个 UID如果你想直接拿 UID 当业务主键到这一步就够了不一定要进扇区读写。打印 UID 时必须用 0xFF 转成无符号再看否则负数补位会显示成一长串 F看着像 bug实际是 Java byte 有符号导致的展示问题。4.3 密钥认证不认证就读取只会得到 00拿到卡还不能马上读数据得先通过密钥认证。Mifare 卡出厂密钥默认是 6 个 0xFF很多测试卡没有改过密钥用默认值能通的概率很高。byte[] key new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; int sector 1; int ret Mwic32.INSTANCE.MF_LoadKey(0, 0x60, sector, key); if (ret ! 0) { throw new IllegalStateException(加载密钥失败错误码 ret); } ret Mwic32.INSTANCE.MF_Authentication(0, 0x60, sector); if (ret ! 0) { throw new IllegalStateException(扇区认证失败错误码 ret); }注意 MF_Authentication 的第三参在部分 DLL 版本里也可以是块号我手上的版本传扇区号。如果你照着写总是认证失败把这里改成 sector * 4该扇区第一个块的块号再试。这种版本差异厂商文档基本不标只能靠实际试。所以建议留一个测试台专门验证参数差异。4.4 读写数据块并释放资源认证成功后就能对扇区内的块做读写。以扇区 1 为例块号是 4、5、6、7其中 7 是控制块虽然能读但不要乱写控制字写坏了整张卡就报废。安全做法是只写 4、5、6 三个数据块。int block sector * 4; // 扇区1 - 块4 byte[] data new byte[16]; ret Mwic32.INSTANCE.MF_Read(0, block, data); if (ret ! 0) { throw new IllegalStateException(读块失败错误码 ret); }写数据的重点是一个块固定 16 字节不够要补 0多了塞不下byte[] writeData new byte[16]; byte[] content HELLO_RD.getBytes(StandardCharsets.UTF_8); System.arraycopy(content, 0, writeData, 0, Math.min(content.length, 16)); ret Mwic32.INSTANCE.MF_Write(0, block, writeData); if (ret ! 0) { throw new IllegalStateException(写块失败错误码 ret); }每次操作结束尤其是一次性发多张卡时建议先 MF_Halt 让卡休眠再 MF_Close 释放串口。不要小看这一步不 Halt 直接让下一张卡上来经常出现“上一张卡的操作结果串到下一张卡”的诡异现象实际就是卡片状态机没有复位。这个坑在批量发卡时几乎必然遇到。5. 常见问题与排查技巧速查表5.1 现象、可能原因与解决办法现象可能原因解决办法Native.load 抛 UnsatisfiedLinkErrorJVM 是 64 位DLL 是 32 位换 32 位 JDK 启动项目MF_Init 返回非 0串口被占用或端口号不对关闭厂商 Demo遍历 0~3 测试端口能寻卡但认证失败密钥错误认证模式不对参数版本差异确认密钥检查 0x60/0x61试传块号读取返回全 00未认证或认证扇区不匹配先认证再读检查扇区号连续发卡时读卡错乱没有 MF_Halt 复位卡片状态每张卡流程结束调 MF_Halt程序加载 DLL 慢或失败系统缺 VC 运行库安装 Microsoft Visual C Redistributable这张表是多次踩坑后整理的基本覆盖了设备对接 90% 的初期问题。你可以直接拿去当项目验收前的检查清单。5.2 Java 环境那些“插曲”做这套对接时还会遇到一类和读卡器无关的 Java 环境问题这类问题也经常被误判成读卡器代码的问题。比如“mvn、pnpm、git 等命令无法识别为 cmdlet、函数、脚本文件或可运行程序的名称”这是因为这些可执行文件所在的目录没加到 PATH 环境变量跟项目本身没关系Windows 下把对应 bin 目录加进 PATH重开终端就好。再一个是 Lombok 在高版本 JDK 下报“you arent using a compiler supported by lombok”这是 Lombok 版本太旧升级到 1.18.30 以上就解决。还有“java: OutOfMemoryError: Insufficient memory”如果发卡量大JVM 堆直接调大比如本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价