资讯动态

XE10 Android java2op jar2pas 与 JNIBridge 实战排错

发布时间:2026/10/28 3:44:58 来源:尧图企业网站定制
简介Java2OP for XE10 面向需要在 Delphi 10.2 环境中调用 Java 类库的开发者围绕 jar 到 pas 的桥接需求提供从 Java 字节码元数据到 Delphi 单元文件的转换工具。压缩包共 8 个文件约 16.4MB包含可执行主程序、两个 jar 依赖、inc 模板、xml 配置以及 txt 说明和 log 日志等分别承担运行、转换规则支撑、参数配置与过程记录等用途便于快速搭建转换环境。已有 260 人学习下载。借助包内工具可将 jar 中的类、接口和方法声明转换为可引入 Delphi 工程的 PAS 文件同时结合说明与日志检查转换结果适合需要复用 Java 生态、实现跨语言调用或研究 jar2pas、class2pas 流程的中高级开发者参考。1. 接手一个 XE10 的 Android 工程只有一支 jar 没有源码工程是 RAD Studio XE10内部版本号 17.0要在 Android 端接一支第三方 jar没有源码也没有调用说明。bin目录里躺着java2op.exe看着就是现成的 jar2pas 工具我在命令行敲下第一条转换命令窗口闪一下退出只留一行 JVM 相关的报错。折腾半天才确定问题不在参数而在本机装的是 64 位 JDK——32 位的转换工具加载不了那份 jvm.dll。这个场景在 Delphi 做 Android 时太常见了系统 API 之外的东西大多要靠 java2op 把 .class 或 .jar 转成 pas也就是 class2pas 那条路转出来的单元并进工程运行时靠 JNIBridge 调 Java。转不干净轻则编译一片 Undeclared identifier重则编译过、真机一调就崩日志只留一行 NoClassDefFoundError。下面记录的是我在 XE10 上从装 JDK、定位工具、跑转换到真机验证的完整走法。2. 环境清单XE10 的 java2op.exe 在哪、JDK 该装几位动手前先把三件事固定下来工具在哪、Java 环境几位、工作目录在哪。这三件事没固定后面每一次报错都会变成玄学同一个命令在别人的机器上能跑在你这儿就是不行最后只能靠猜。2.1 三个版本目录对照17.0 / 18.0 / 19.0 里的 java2op.exejava2op.exe是随 RAD Studio 一起装进bin目录的不需要单独下载也不需要找什么安装包。它与 IDE 版本严格绑定版本号写在路径里这也是很多人第一次找不到它的原因——网上抄来的路径和自己机器上的对不上。产品版本内部版本号相对位置XE10 Seattle17.0安装根目录\Studio\17.0\bin\java2op.exe10.1 Berlin18.0安装根目录\Studio\18.0\bin\java2op.exe10.2 Tokyo19.0安装根目录\Studio\19.0\bin\java2op.exe10.3 Rio20.0安装根目录\Studio\20.0\bin\java2op.exe安装根目录按自己机器替换默认在系统盘的程序目录下。确认位置最直接的办法是开一个资源管理器窗口搜java2op.exe比翻文档快。顺带把javac、javap、jar三个 Java 工具也确认一遍后面核对签名全靠它们。提示不要把java2op.exe单独拷到别的目录去用。它依赖同目录下的运行时组件单独拷出来常见的结果是启动即退出、连帮助都打不出来。版本差异要提前有数17.0 上的参数集比后来的 19.0、20.0 少一些网上的教程很多基于新版本写参数直接照抄会报Invalid option。最稳的做法是先在命令行里裸跑一次java2op.exe它会把自己的帮助打出来以这份帮助为准别信博客。2.2 JDK 位数决定 jvm.dll 能不能被加载这是整套环境里最容易翻车的一环。java2op.exe是 32 位进程启动时要去加载 JVM 的动态库jvm.dll位数对不上就直接失败。机器上装的是 64 位 JDK我当初就是在这里卡了整整一个下午因为报错信息里只提 JVM完全没提位数。常见做法是单独准备一份 32 位 JDK 8只给转换工具用不去动系统里那套 64 位的。装完之后有两个目录必须进 PATHJDK32\bin提供 java、javac、javap和JDK32\jre\bin\clientjvm.dll就在这儿。顺序上要放在最前面避免被系统里另一套 Java 抢先命中。rem 检查当前窗口实际命中的是哪一份 Java where java where javac where jvm.dllwhere是按 PATH 顺序输出的第一行才是真正生效的那一份。三条命令里如果jvm.dll找不到说明jre\bin\client没进 PATH如果找到的是 64 位 JDK 目录下的那份说明 PATH 顺序反了。这两个判断比读报错快得多。还有两个细节值得顺手确认JAVA_HOME指向的必须是 JDK 根目录而不是 JRE 目录路径里不要有中文和空格中间带空格的目录名在拼参数时非常容易被截断。2.3 一份只对当前窗口生效的启动脚本我不建议为了这个工具去改系统环境变量。改完之后别的项目、别的命令行窗口都会受影响过两个月自己都忘了改过什么这类“环境漂移”是最难查的一类问题。写一个.bat用setlocal圈住作用域窗口关掉即失效相当于给自己留了后悔药。echo off setlocal rem 32 位 JDK专供 java2op 使用 set JDK32C:\Java\jdk1.8.0-32bit rem RAD Studio XE10 的 bin 目录 set RADBINC:\RAD\Studio\17.0\bin rem 把 JDK 和自己的 bin 放到 PATH 最前面优先级最高 set PATH%JDK32%\bin;%JDK32%\jre\bin\client;%RADBIN%;%PATH% set JAVA_HOME%JDK32% rem 三行自检位数、版本、工具是否可见 java -version where jvm.dll java2op.exe endlocal逻辑很直白setlocal保证所有改动只在当前窗口有效PATH前面两段解决 JVM 加载最后一段解决java2op.exe直接可调用不带参数跑一次java2op.exe会把帮助打出来顺便验证它确实能被启动。java -version的输出里如果出现 64-Bit 字样说明 PATH 还是被别处覆盖了回头查顺序。以后每次要转换双击这个 bat在它开出来的窗口里操作不要在别的 cmd 里操作。血泪经验同一个命令在两个窗口里结果不同九成是环境变量不一样。3. jar2pas 实操java2op 命令行参数与第一次成功转换环境固定之后才轮到参数。这一段的目标很明确用一条命令把一支 jar 变成一份能编译的.pas并且知道每个参数改动了什么。3.1 三种输入模式-classes、-jar、-list 怎么选java2op有三种喂输入的方式选错了要么转不全要么转出一堆用不上的类。-classes直点名适合只调一两个类的情况类名写全限定名带包名多个类之间用逗号分隔。优点是产物干净缺点是要手工把整条继承链上的父类都点出来漏一个父类生成结果里的父接口就会退化成JObject。-jar直接给 jar 文件路径工具会把 jar 里能识别的类全部转出来适合“不知道要用哪些类”或者“要用一大片”的情况。产物体积大但不会漏。-list读一个文本清单文件一行一个类名适合类数量多到命令行塞不下的场景。清单文件用 UTF-8 无 BOM 保存带 BOM 时第一行类名会莫名识别失败。我一般的取舍是先用-jar全量转一次看日志里哪些类报了 not found再决定要不要缩到-classes。全量转一次的成本很低但漏转一个类带来的排查成本很高。3.2 输出侧参数-unit、-out、-headerUnit、-includeConflict输入解决“转什么”输出参数解决“转成什么样、放哪儿”。-unit指定生成单元的名字。建议统一加Android.JNI.前缀和 IDE 自带的桥接单元保持同一套命名习惯工程里一眼能看出来这是生成物而不是手写代码。-out输出目录。不要和源码目录混在一起单独建一个java2pas\out重新生成时整个目录删掉重建避免旧文件残留导致编译到过期版本。-headerUnit生成的单元要 uses 的头单元默认是Androidapi.JNIBridge。工程里如果有自定义的桥接头这里要跟着改否则编译期找不到TJavaGenericImport。-includeConflict默认情况下工具会跳过那些 RTL 里已经有桥接的类避免重复定义。加上这个开关就强制生成。除非确实要做覆盖否则不要加加了很容易撞出一堆重复签名。-skip跳过指定类BuildConfig、R这类纯资源类建议直接跳过转出来也调不了。参数名以本地帮助为准17.0 上能用的组合比新版本少敲错时工具会打一行Invalid option并退出不会给你任何猜测空间。3.3 跑通第一支 jar命令、日志与生成的 .pas 长什么样下面这条是我在 XE10 上跑通的最小命令。习惯上会把 android.jar 一起塞进-jar让工具能解析android.*和java.*的父类否则生成的接口父类全是JObject方法表是空的。java2op.exe ^ -jar .\libs\probe.jar;AndroidSDK\platforms\android-22\android.jar ^ -unit Android.JNI.Probe ^ -out .\java2pas\out ^ -headerUnit Androidapi.JNIBridge ^ -skip com.example.probe.BuildConfig ^ java2op.log 21几个点单独说明。多个 jar 之间用分号分隔整段用引号包住——不带引号时路径里的空格会把参数切成两半工具只会认前半段然后默默产出一个空单元这个坑后面还会单独说。android.jar的 api 级别按你本地platforms目录里实际存在的那个填别照抄。末尾把标准输出和错误都重定向到日志是因为部分版本的提示只写到 stderr不重定向的话窗口一关就没了。跑通之后java2pas\out下会出现一份Android.JNI.Probe.pas结构大致长这样unit Android.JNI.Probe; interface uses Androidapi.JNIBridge, Androidapi.JNI.JavaTypes; type JProbe interface; // 前向声明供下面引用 JProbeClass interface(JObjectClass) [{生成的 GUID不要手抄}] {class} function greet(who: JString): JString; cdecl; // static 方法带 {class} {class} function version: Integer; cdecl; end; JProbe interface(JObject) [{生成的 GUID不要手抄}] // 实例方法在这里 end; TJProbe class(TJavaGenericImportJProbeClass, JProbe) end; implementation end.看结构就明白了静态方法声明在JXxxClass接口里并带{class}标记调用时走TJProbe.JavaClass.方法名不需要创建对象实例方法声明在JXxx接口里得先有 Java 侧的对象引用才能调。两个 GUID 由工具生成每次重生成都可能变所以千万不要手工改这份文件——改了下次重新生成就全丢正确做法是把它当纯产物改逻辑放到外层的包装单元里。4. class2pas 之后把生成单元放进工程并编译通过生成文件只是半成品真正麻烦的是让它和工程里的其它单元和平共处。4.1 单元命名、Uses 顺序与重复 JavaSignature生成单元丢进工程目录后先在 Project Options 的 Unit scope names 里确认有Androidapi否则uses Androidapi.JNIBridge这种写法在部分配置下解析不到。单元名带点Android.JNI.Probe是可以的Delphi 会当成普通标识符处理。引用顺序上Androidapi.JNIBridge和Androidapi.JNI.JavaTypes放在生成单元之前生成单元放在业务单元之前。顺序错了最常见的表现是编译报E2003 Undeclared identifier指向的却是生成文件里明明写着的类型。重复签名是另一个高频问题两个生成单元如果为同一个 Java 类各生成了一份接口编译期可能过得去运行期注册桥接时就出事。表现是调用时拿到的对象类型对不上或者直接抛异常。解决办法不是改生成文件而是在转换阶段就掐掉用-skip排除或者用-edl把已有 Delphi 单元映射进去让工具知道这个类已经有主了。4.2 依赖类缺失父类、内部类、R 类怎么补生成出来的接口如果父接口是JObject而不是你预期的那个类基本可以断定父类没被解析到。原因通常是父类在另一个 jar 里或者父类是android.*下的东西而android.jar没喂进去。补的办法就是把依赖 jar 一并放进-jar列表或者用-classes显式把父类点出来。内部类类名带$的那些要看用途只是编译期引用的话跳过没关系如果是回调接口比如各种Listener那就必须转出来否则事件没法接。R 类和BuildConfig一律跳过它们只在 Java 侧有意义。4.3 jar 随应用发布Libraries 节点与 Deployment 两条路这一步是纯运行期问题编译期完全看不出来。工程里引用了生成的 pas只代表 Delphi 侧认识了这个类Java 侧的字节码还得跟着应用一起发到设备上。常见做法是在 Project Manager 里展开 Android 目标找到 Libraries 节点右键 Add 把 jar 加进去构建时会自动处理打包。工程里没有这个入口的版本退一步用 Deployment 面板手工添加注意远程路径要和包加载逻辑对得上。漏掉这一步的典型现象是编译零错误真机上第一次调用就抛java.lang.NoClassDefFoundError日志里只有类名看不出是打包问题。我一般的习惯是先在设备上确认包里的文件清单再回头怀疑代码。5. 排查笔记java2op 从命令行到真机的五个翻车点下面五条都是我自己踩过的按“现象 → 原因 → 解决”记遇到同类问题时可以对着现象直接定位。5.1 环境与加载类问题现象命令行窗口一闪就退或者只留一行与 JVM 加载失败相关的报错连帮助都打不出来。 原因java2op.exe是 32 位进程PATH 里命中的是 64 位 JDK或者jre\bin\client根本没进 PATHjvm.dll加载不到。 解决装一份 32 位 JDK 8 专用把JDK32\bin和JDK32\jre\bin\client放到 PATH 最前面用where jvm.dll确认命中的是 32 位那份再重开窗口跑一次。改完 PATH 一定重开窗口旧窗口不会自动刷新。现象转换跑完了但输出目录里只有一个 0 字节的.pas日志干干净净。 原因-jar后面的路径含空格或中文且没加引号参数被截断工具拿到的是一个不存在的路径于是什么都没转。 解决整段路径加双引号多个 jar 用分号分隔后再整体加引号工作目录尽量放在无空格、无中文的路径下把命令写进 bat 文件而不是直接粘到 cmd 里避免续行符被吃掉。5.2 转换产物问题现象生成的.pas编译报E2003 Undeclared identifier指向JString、TJavaGenericImport这类基础类型。 原因转换时没带-headerUnit或者工程 Unit scope names 里缺Androidapi也可能是工程里同时存在两份不同时期生成的同名单元。 解决转换命令补上-headerUnit Androidapi.JNIBridgeProject Options 里检查 Unit scope names把输出目录整个清空重新生成一次确认工程引用的是最新那份。现象生成结果里全是a.a.a、a.b.c这种类名调方法时提示找不到。 原因jar 被混淆过原始类名和方法名已经不可读。 解决让提供方出一份保留类名的构建或者直接用javap -p -s -classpath xxx.jar a.a.a反查真实签名按签名在包装单元里手写调用同时把混淆规则里的 keep 配置要过来否则每次对方发版都要重来一遍。5.3 编译与运行期问题现象编译全过真机一调就抛java.lang.NoClassDefFoundError异常信息里只有类名。 原因jar 只加进了工程引用没进 Libraries 节点或 Deployment 面板打包时没被带进去。 解决在 Project Manager 的 Android 目标下把 jar 加到 Libraries或手工在 Deployment 里补一条改完做一次 Clean 再构建增量构建有时不会重新打包。现象调用TJXxx.JavaClass的瞬间崩溃栈里看不出有用信息。 原因类名大小写写错Java 侧严格区分大小写包名与生成文件里的JavaSignature不一致或者调用发生在非主线程导致 JNI 环境没挂上。 解决用javap核对全限定名与签名逐个字符比调用代码放回主线程执行确认生成单元的JavaSignature和 jar 里的实际包名一致。翻车最多的还是大小写看着一样其实不一样。6. 装完怎么验证用一个自写 jar 打通 JNI 调用链验证不要拿真实业务库去试坑太多出问题分不清是环境还是库本身的毛病。我一般自己写一个只有静态方法的小类把链路单独跑一遍。三步走先用javac -source 1.7 -target 1.7编译高版本字节码在部分工具链上解析会出问题jar cf probe.jar com打包再用第 3 章那条命令转成 pas最后在窗体上放个按钮调用并显示返回值。uses Androidapi.Helpers, Androidapi.JNIBridge, Androidapi.JNI.JavaTypes, Android.JNI.Probe; // java2op 生成的那份单元 procedure TFormMain.Button1Click(Sender: TObject); var LRet: JString; begin // 静态方法挂在 class 引用上不需要 Create 实例 LRet : TJProbe.JavaClass.greet(StringToJString(delphi)); Memo1.Lines.Add(JStringToString(LRet)); end;StringToJString和JStringToString来自Androidapi.Helpers负责 Delphi 字符串和 Java 字符串之间的转换方向别写反传参用前者取返回值用后者。静态方法不需要构造对象这也是先用静态方法验证的原因——少一层对象生命周期出问题只可能是环境和打包。对照下面四项看结果哪一项不符就回哪一章验证点期望结果不符时先查转换命令日志无 Errorpas 非 0 字节JDK 位数、PATH、路径引号编译无 Undeclared identifierUses 顺序、Unit scope names真机调用Memo 里出现预期字符串jar 是否进包、类名大小写logcat无 NoClassDefFoundErrorLibraries 节点、dex 转换日志真机上还可以顺手用adb logcat盯一下异常栈Java 侧的异常在 Delphi 里往往只呈现成一句笼统的报错原始堆栈只有 logcat 里有。从那以后我每接一支新 jar都强制先跑这个只有静态方法的小类链路通了再动真库。看着多花二十分钟实际省下的是后面分不清是工具、是打包还是对方库有问题的几个小时。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑