ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Delphi XE10中使用java2op转换Java Jar包为Pascal的方法与踩坑实践

Delphi XE10中使用java2op转换Java Jar包为Pascal的方法与踩坑实践 简介面向 Delphi 开发者的 Java2OP for XE10 是一套实用的 jar 转 pas 工具专门解决在 Delphi 工程中直接复用 Java 类库的跨语言集成问题。它读取 jar 包内的 .class 字节码将类、接口与方法元数据解析成 Delphi 可调用的 PAS 单元并针对 XE10.2 版本做了适配适合需要同时利用两大生态的中高级开发者。资源包总大小约 16.4MB共含 8 个文件除 Java2OP.exe 主程序外还提供 bootclasses.jar、doclava.jar 等依赖文件以及 default.inc 模板、XML 配置和 txt 使用说明可辅助完成参数配置与错误排查。已有 255 人学习。使用这套工具你可以把既有的 Java 库引入 Delphi IDE省去重写业务逻辑的重复劳动借助附带的模板和配置还能自定义数据类型映射、处理异常情况让跨语言调用更稳妥。适合正在 XE10 环境下尝试 Java 互操作的开发团队或个人参考。 聊一个Delphi开发里特别冷门、但只要有移动端项目就绕不开的工具java2op而且是在XE10这个版本下的用法。先讲个场景。你用Delphi XE10写Android客户端结果第三方支付、推送、地图、扫码人家只给了Java的SDK一个jar包扔过来文档全是Java示例。你总不能为了一个SDK就去重写整个App也不可能手写一堆JNI去对接。这时候就得靠java2op把jar包里的Java类自动翻译成Delphi能识别的Pascal单元生成之后直接uses然后像调用Delphi原生类一样去操作Java对象。我最早听说这工具的时候也愣了半天这名字起得实在太“内部工具”了但实际用下来它是Delphi做Android原生集成最顶用的一个桥头堡。这篇就把我在XE10里用java2op转换第三方jar包、在Delphi工程里调用Java API的完整过程写一遍包括环境准备、生成步骤、生成的代码怎么看、常见错误怎么排查。尤其是最后那部分坑基本是官方文档不会写的老手也是踩了无数次才摸出来的。1. java2op到底是什么它能帮你做什么1.1 要理解它先理解Delphi和Java之间怎么“通话”Delphi的Android编译器是把Pascal代码直接编译成ARM或者x86的机器指令跑在Android设备上它和Java代码不在一套运行时里。想让Delphi调用Java里的方法有几种路子一种是通过JNIJava Native Interface手写c代码再通过Delphi的JNI Bridge封装另一种就是现在大家常用的方式——用Delphi自带的Androidapi.JNI.*单元把你需要调用的Java类、接口、方法全部用Pascal的interface重新声明一遍然后由Delphi的JNI桥在运行时动态去找Java层的类和方法。问题就在这Android SDK里的类官方已经给你生成好了Androidapi.JNI.Java.*这一大堆Pascal声明但第三方jar包里的类没有人为你声明你得自己写。假如一个SDK有几百个类、几千个方法手写Pascal接口直写到怀疑人生而且Java方法签名、返回值类型、异常声明写错一个就崩溃调试起来特别痛苦。java2op就是干这个的它自动解析jar包里的字节码读取每个类的字段、方法、返回类型、参数类型然后生成对等的Delphi Pascal声明文件。1.2 工作流程其实就三步java2op不是个常驻服务也不是编译器插件它就是个命令行工具。你给它一个jar文件它吐出一个.pas文件。流程大概是这样java2op读取jar包内的.class文件解析里面的类结构和方法签名。根据解析结果生成对应的Pascal接口例如Java里的public class Calculator会变成JCalculator interface(JObject)。你把这个.pas文件加进工程通过TJCalculator这个Delphi包装类来调用Java类的构造函数、静态方法和实例方法。这里的核心是第二步。Java的类、方法在Pascal里通常对应成接口和两个类一个是接口本身JXXX另一个是实现了该接口的包装类TJXXXTJXXX.Create会调用Java对象的构造函数并且持有JNI的local reference或global reference。1.3 比手写JNI省心在哪我是早年手写过JNI的人那滋味太酸爽了。你需要在c层注册函数、管理JNIEnv指针、手动转换jstring和Pascal字符串、处理jobject引用释放。一旦Java层方法签名有变动整个c代码要重新改。java2op生成方案最舒服的地方在于它把JNI的繁琐细节全部封装掉了Delphi层写起来就像一个普通类。比如调用Java的静态方法生成的代码大概是TJCalculator.JavaClass.add(10, 20)这种爽不爽非常爽。2. XE10下使用java2op的环境准备2.1 确认工具路径和版本装完Delphi XE10之后java2op默认在C:\Program Files (x86)\Embarcadero\Studio\17.0\bin目录下。XE10对应的版本号是17.0也就是Seattle。如果你的安装路径变了直接在文件管理器里搜java2op.exe就能找到。这里有个容易忽略的点java2op.exe是个32位命令行程序依赖Java环境。系统里没配置JDK或者只有JRE都不行java2op内部要用到jar命令来解压和分析文件。我建议直接装JDK 1.8不要用太高版本某些老版本java2op对JDK 9以上的模块化结构支持得不好。2.2 验证环境是否可用打开命令行切到bin目录直接敲java2op -h。正常情况下会打印出一堆参数说明。如果提示找不到java命令或者报java.lang.NoClassDefFoundError那就说明JDK环境有问题先把JAVA_HOME环境变量配好并把%JAVA_HOME%\bin加进PATH。我在新电脑上第一次跑的时候就栽在这上面Delphi装好了但是机器上只有JRE没有JDKjava2op直接秒退。后来装好JDK才正常。2.3 准备jar包此外你的jar包必须用JDK能解析的标准格式。有些第三方SDK会把jar包加密、混淆或者做成Android专用的AAR包AAR里面的classes.jar才能转换AAR本质是zip直接把里面的classes.jar抽出来用。混淆过的jar包也能转类名可能变成a、b、c生成的Pascal单元可读性差但不影响调用。如果jar包还有依赖的其他jar必须一起参与转换或者把依赖先合并进来否则类引用解析会出错。3. 用java2op生成Pascal桥接单元的完整实操3.1 基本命令行先举个最简单的例子。假设有一个demo.jar里面就一个类package com.example.util; public class Calculator { public static int add(int a, int b) { return a b; } public String getVersion() { return 1.0; } }打开命令行执行java2op -jar demo.jar -o Androidapi.JNI.Demo.pas -unit Androidapi.JNI.Demo -lib demo解释一下参数-jar后面跟要解析的jar包路径。-o输出文件名。-unit生成的Pascal单元名不写的话默认和输出文件同名。-lib这个参数很关键它会告诉生成代码运行时加载的动态库名字是什么。对于Android平台这个通常填jar包名但实际调用时类加载依赖的是Java虚拟机的classpath而不是动态库所以更多是用于生成固定的库名常量。跑完以后目录下会多一个Androidapi.JNI.Demo.pas。用Delphi打开看一眼会发现生成好的接口声明。3.2 生成的代码结构长什么样生成的文件核心结构是这样的简化后unit Androidapi.JNI.Demo; interface uses Androidapi.JNIBridge, Androidapi.JNI.JavaTypes; type JCalculator interface(JObject) [{一些GUID}] function getVersion: JString; cdecl; end; TJCalculator class(TJavaGenericImportJCalculator, JCalculator) end; implementation end.注意几个细节。第一静态方法add去哪了你没看错java2op生成的JCalculator接口里通常不会包含静态方法它只声明实例方法。静态方法会通过TJCalculator.JavaClass来访问在Delphi那边你需要自己加一个包装方法或者直接使用TJCalculator.JavaClass.add(10,20)。这算是个“反直觉”点我第一次找静态方法找了半天。第二返回值String在Pascal里变成了JString这是JNI桥接的约定Delphi里的String不能直接和Java的String互相转换要用JStringToString和StringToJString来转。第三接口的GUID是自动生成的用来在Delphi的interface机制里做类型识别不同版本的jar包生成的GUID不同如果jar更新了重新生成的pas文件要整体替换不能只改方法签名。3.3 在Delphi工程里调用生成的单元调用方式相当直接uses Androidapi.JNI.Demo, Androidapi.Helpers; procedure TForm1.Button1Click(Sender: TObject); var Calc: JCalculator; Ver: string; begin Calc : TJCalculator.Create; try Ver : JStringToString(Calc.getVersion); ShowMessage(Ver); finally Calc : nil; end; // 调用静态方法 ShowMessage(IntToStr(TJCalculator.JavaClass.add(3, 5))); end;这里有几个使用要点TJCalculator.Create不是创建Delphi对象而是通过JNI去new一个Java对象并把引用返回给Delphi侧的接口变量。它可能在底层创建一个GlobalRef也可能用LocalRef取决于JNI桥的当前模式。所以用完要释放引用置nil不要长期持有大量Java对象不释放否则会爆引用表。JStringToString和StringToJString这两个函数是Delphi提供的JNI字符串互转工具几乎所有涉及Java字符串的场景都会用到最好记熟。静态方法调用是通过JavaClass属性完成的。JavaClass返回的是Java类的JNI引用再调用其中的静态方法。它的本质是在运行时查找类ID如果JVM里没有这个类会在这一步抛异常。常见的java.lang.ClassNotFoundException就是在这条链路上出的。3.4 参数繁杂时怎么处理jar包里类特别多的情况下生成的单文件会非常庞大有时候甚至几千行。但Delphi的编译器能扛住只是IDE打开会有点卡。我可以接受。如果你特别在意可读性可以拆开转换一个jar生成多个unit但要确保单元之间的引用关系正确这个难度比较大我一般不折腾。4. 高频报错与避坑指南老手经验4.1 生成的Pascal代码编译不过这是最常见的情况尤其是在XE10早期版本里java2op生成的代码有时候会引用到不存在的JNI单元类型或者出现重复的命名空间。我遇到过几种典型报错E2003 Undeclared identifier: JObject说明你没有在uses里引用Androidapi.JNI.JavaTypes。生成的unit默认会uses它但如果你的Delphi库路径有问题或者IDE没能索引到Androidapi目录就会报这个。E2010 Incompatible types: JString and string类型没互转把string直接赋值给JString了要用StringToJString。E2251 Ambiguous overloaded call to method CreateJava类有多个构造函数生成的TJXXX也会重载多个Create参数类型不匹配就会触发这问题仔细核对参数。上面这些基本靠看编译器的错误行号就能定位不算难。4.2 运行时找不到类或方法编译能过一跑到TJCalculator.Create就崩或者静态方法调用时报错。这通常有几种可能性jar包没有真正部署到Android设备上。Java类必须存在于App的classpath里。Delphi的Android工程要把jar放到Library节点下并且在Deployment里设置路径为classes.dex所需的加载路径。很多人只把jar加到了工程里没有添加到Deployment导致运行时类找不到。混淆过的方法名和字节码不一致。java2op是基于字节码解析的如果jar在发布时被混淆而你在转换时拿到的又是混淆后的版本那问题不大。但如果你拿到的是混淆前和混淆后混着来方法名就对不上。静态方法没找到。Delphi调用静态方法走的是JavaClass找类再GetStaticMethodID找方法。如果Java方法签名声明是private或者package private的外部类没法调用JNI会抛NoSuchMethodError。第三方SDK偶尔会把本该public的方法搞成非公开这时候只能靠反射调用java2op生成不了需要自己写JNI助手。4.3 泛型接口的处理Java的泛型在字节码里会被擦除但有时候类的签名里会带泛型信息java2op识别不了的时候会生成奇怪的接口比如JListJString这种是生成不出来的。我的建议是遇到泛型相关的地方生成完后手动改Pascal源码把泛型涉及的方法改成具体的JObject类型然后在调用处自己做类型转换。这是比较糙但有效的方式。如果你要处理的SDK用了大量泛型先做好心理准备。4.4 依赖jar包的解析很多SDK不是孤立的它依赖Android SDK里的类依赖其他第三方库。java2op在解析类引用时如果找不到被引用的类可能会在生成的代码里留下一个未定义的类型或者直接跳过某些方法。解决办法是用-classpath参数把依赖jar一起传给java2op这样它解析符号时不会抓瞎java2op -jar demo.jar -classpath android.jar;support.jar;otherlib.jar -o Androidapi.JNI.Demo.pas要注意android.jar需要从你的Android SDK目录里找路径通常在platforms\android-XX\android.jar。4.5 在XE10这个版本上还有几个特性要留心XE10跟后来的版本比Java2OP的生成逻辑相对老。比如对Java 8新增的default方法接口默认方法支持不太行生成的代码可能会缺失。对某些注解类型Annotation无法正确生成。生成的接口中toString、equals这些Object基类方法有时候会重复声明和父接口冲突编译会报“duplicate method”需要手动删掉。我每次转换完都会先编译一遍把重复方法或者不支持的类型标记出来再逐一手修。这个手动修正的量一般不会太大比手写全量JNI省太多。5. 生成特定类、自定义映射等进阶玩法5.1 只想转换某几个类怎么办如果jar包里有几十个类你只需要其中几个可以用-classes参数java2op -jar demo.jar -classes com.example.util.Calculator,com.example.util.OrderManager -o Androidapi.JNI.Demo.pas这个参数用逗号分隔类名java2op只解析这些类不会生成全部代码减少编译冲突和文件体积。但要注意如果这些类内部引用了jar里其他类作为参数或返回类型那些类也要参与转换否则就会生成引用缺失的代码。5.2 自定义映射文件java2op支持通过XML或properties形式的映射文件来定制生成规则包括改名、忽略某些方法、强行指定某类对应JObject等。比如mapping class namecom.example.util.Calculator method namegetVersion ignoretrue / /class /mapping使用方式是在命令行加-map mapping.xml。这个功能对于处理命名冲突、屏蔽SDK的废弃方法特别有用。很多第三方SDK里带了内部测试用的方法不想暴露给业务层干脆在映射文件里忽略掉生成的Delphi接口干净不少。5.3 配合Androidapi.JNI.JavaTypes做类型映射Java里的ArrayList会生成JArrayListHashMap生成JHashMap这些类型在Delphi里调用时返回值确实方便但要那个转到Delphi自己的泛型列表时就得手动遍历。我一般的做法是写一个辅助函数把Java的JIterator转换成Delphi的TListT。这个部分不算难但是在大型SDK对接时它决定了你的代码写起来是优雅还是痛苦。5.4 在XE10下有没有必要换Newer版本的Delphi如果你有条件升级到更高版本工具本身没什么差别核心API是稳定的。但新版本对Android新版本的支持更好对64位支持也更好。如果项目卡在XE10上也不是不能用就是遇到Android 13/14的新权限、新API时需要自己扩展JNI桥接的代码。这类扩展不难很多时候就是在生成的接口里补几个方法声明而已。6. 总结一点个人体会按我的经验java2op本身不难用真正难的是理解它生成的代码和JNI运行时的对应关系。你不需要是个Java专家但得懂一点Java对象的生命周期、JNI引用管理、字符串映射。如果你能把这几点搞清楚大部分第三方jar包都能顺利转过来。最后提一个习惯每次把一个新SDK集成进来我会写一个最小的demo工程只调用对方一个方法通了之后再扩展。一旦出问题排查范围就被缩得很小。这个思路在java2op转换后尤其管用——先确认转换结果没问题再确认运行时环境没问题否则一旦和业务逻辑混在一起查错查到天亮都未必能定位。本文还有配套的精品资源点击获取
返回列表