# Swoole AOT 编译器编程语言互调用机制实例:PHP 调用 JNI 接口实现调用任意 Java 类方法 ## 概述 `Swoole AOT` 编译器是一个`PHP`的静态编译器,可以将`PHP` 项目或代码直接编译为二进制可执行文件。 `AOT`编译器基于`ABI`模式实现了`PHP`与`C/C++`的互操作性,这使得`PHP`获得了与其他编程语言的直接调用能力,而不需要借助`FFI`或者编写`PHP`扩展。 本文将介绍如何使用`Java`的`JNI`接口,实现在 `PHP` 代码中直接调用 `Java` 类库 —— 创建 `Java` 对象、调用方法、读写字段,就像在 `Java` 代码中操作一样自然。 1. 本实例程序的代码全部由 `DeepSeek-4-Pro` 生成,耗时约为`50分钟` 2. GitHub: ## 准备工作 1. **JDK**(示例使用 `OpenJDK 25`,路径 `/usr/lib/jvm/java-25-openjdk-amd64`) 2. **PHP 8.2+** 及 **Swoole-Compiler 0.2.0** 3. **编译命令**: ```bash swoole_compiler examples/jni/project.yml ``` ## 核心接口 参见:`php-src/jni.stub.php`文件 ```php **说明**: > - `cxxflags` 中 `-I` 指向 `JDK` 的 `JNI` 头文件目录 > - `ldflags` 中 `-ljvm` 链接 `JVM` 动态库,`-Wl,-rpath` 确保运行时能找到 `libjvm.so` > - `sources` 中 `./cpp-src` 指向 `C++` 实现目录(内含 `jni.cc`),`php-src` 指向 `PHP stub` 文件目录 ## 第三步:编写 PHP 程序 ```php 请注意务必将`Hello.class`文件放在与`jni_example`相同的目录下。 执行结果: ```bash swoole@swoole-26:~/workspace/aot/compiler$ ./jni_example === Step 1: Initialize JVM === JVM initialized. === Step 2: Dynamic Java Object (Hello) === Found class: Hello Created Hello object. greet() → 你好, I'm Swoole, 8 years old name = Swoole, age = 8 After set: name = PHP, age = 10 greet() → Hi, I'm PHP, 10 years old === Step 3: Standard Java Class (StringBuilder) === Found class: java.lang.StringBuilder Created StringBuilder. toString() → Hello Java JNI === Step 4: Destroy JVM === JVM destroyed. ``` ## 内部原理 ### 反射与缓存 当首次对某个类调用 `jni_find_method` 或 `jni_find_field` 时,C++ 层会触发**惰性反射(lazy reflection)**: 1. 通过 JNI 调用 `Class.getDeclaredConstructors()` 获取所有构造器 2. 通过 JNI 调用 `Class.getMethods()` 获取所有 public 方法(含继承的) 3. 通过 JNI 调用 `Class.getDeclaredFields()` 获取所有字段(含 private) 4. 对每个方法/字段,获取其参数类型、返回类型,转换为 JNI 签名 5. 调用 `GetMethodID` / `GetFieldID` 获取 JNI ID 6. 全部信息缓存在 JniClass 内部的 C++ map 中 后续对同类的方法/字段查找直接命中缓存,无需再调 `JNI`。 ### Box 类型体系 ``` Box (phpx 基类) ├─ JniClass — 包装 jclass 全局引用 + 反射缓存 ├─ JniMethod — 包装 jmethodID + 所有重载的签名信息 ├─ JniField — 包装 jfieldID + 字段类型签名 + 静态/实例标记 └─ JniObject — 包装 jobject 全局引用 ``` 每个 `Box` 子类在构造时设置 `type_info` 字段,使得 `C++` 层无需 `RTTI` 即可安全地区分不同句柄类型。 ## 限制与注意事项 1. **JVM 单实例**:一个进程只能创建一个 `JVM` 实例,`jni_init` 不可重复调用 2. **classpath**:运行目录需能访问到 `.class` 文件(通过 `jni_init` 的参数指定) 3. **无序字段**:`Java` 反射不保证方法/字段的返回顺序,方法重载匹配不依赖顺序 4. **资源释放**:`JniClass` / `JniObject` 的全局引用在 `PHPX Box` 析构时自动释放(`DeleteGlobalRef`),JVM 销毁时一并清理 5. **线程安全**:当前实现未考虑多线程场景(`AOT` 编译的程序默认为单线程模型)