- 在.gitignore中添加*.class文件忽略规则 - 新增Hello.java示例类用于JNI测试 - 实现完整的JNI C++桥接代码,包括类型转换和异常处理 - 添加PHP stub文件定义JNI相关函数接口 - 创建main.php演示程序展示JNI功能使用方法 - 配置projectpull/1/head
parent
d387667f1e
commit
8a27a0055b
7 changed files with 1350 additions and 1 deletions
@ -0,0 +1,21 @@ |
|||||||
|
public class Hello { |
||||||
|
private String name; |
||||||
|
private int age; |
||||||
|
|
||||||
|
public Hello(String name, int age) { |
||||||
|
this.name = name; |
||||||
|
this.age = age; |
||||||
|
} |
||||||
|
|
||||||
|
public String greet(String greeting) { |
||||||
|
return greeting + ", I'm " + name + ", " + age + " years old"; |
||||||
|
} |
||||||
|
|
||||||
|
public String getName() { |
||||||
|
return name; |
||||||
|
} |
||||||
|
|
||||||
|
public int getAge() { |
||||||
|
return age; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,263 @@ |
|||||||
|
# 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: <https://github.com/swoole/aot-compiler/tree/main/examples/jni> |
||||||
|
|
||||||
|
## 准备工作 |
||||||
|
|
||||||
|
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 |
||||||
|
<?php |
||||||
|
function jni_init(string $classpath = "."): void {} |
||||||
|
function jni_destroy(): void {} |
||||||
|
function jni_find_class(string $className): mixed {} |
||||||
|
function jni_find_method(mixed $objOrClass, string $methodName): mixed {} |
||||||
|
function jni_find_field(mixed $objOrClass, string $fieldName): mixed {} |
||||||
|
function jni_new_object(mixed $classHandle, array $args = []): mixed {} |
||||||
|
function jni_call(mixed $objOrClass, mixed $method, array $args = []): mixed {} |
||||||
|
function jni_get(mixed $objOrClass, mixed $field): mixed {} |
||||||
|
function jni_set(mixed $objOrClass, mixed $field, mixed $value): void {} |
||||||
|
``` |
||||||
|
|
||||||
|
- 使用`jni_init、jni_destroy` 初始化 `JVM` 和销毁 `JVM` |
||||||
|
- 使用`jni_find_class` 查找 `Java` 类,返回 **JniClass** 句柄 |
||||||
|
- 使用`jni_find_method` 查找 `Java` 方法,返回 **JniMethod** 句柄 |
||||||
|
- 使用`jni_find_field` 查找 `Java` 类属性字段,返回 **JniField** 句柄 |
||||||
|
- 使用`jni_new_object` 创建 `Java` 对象,返回 **JniObject** 句柄 |
||||||
|
- 使用`jni_call` 调用 `Java` 方法,返回方法返回值 |
||||||
|
- 使用`jni_get` 读取 `Java` 类属性字段,返回字段值 |
||||||
|
- 使用`jni_set` 修改 `Java` 类属性字段,返回字段值 |
||||||
|
|
||||||
|
在`jni.cc`代码中,会自定调用`Java`反射`API`获取类、方法、属性的类型, |
||||||
|
并存储起来,在后续的调用中会根据反射信息,实现`PHP`类型与`Java`类型的自动转换。 |
||||||
|
|
||||||
|
### 类型转换 |
||||||
|
- Java `String` → PHP string |
||||||
|
- Java `int/long/short/byte` → PHP int |
||||||
|
- Java `float/double` → PHP float |
||||||
|
- Java `boolean` → PHP bool |
||||||
|
- 其他 `Java` 对象 → `JniObject` 句柄 |
||||||
|
- `void` → `null` |
||||||
|
|
||||||
|
|
||||||
|
| PHP 类型 | 目标 Java 类型 | 转换说明 | |
||||||
|
|----------|---------------|---------| |
||||||
|
| string | `java.lang.String` | 通过 `NewStringUTF` 创建 jstring | |
||||||
|
| string | 其他对象类型 | 自动转换为 jstring(可用于需要 `CharSequence` 等接口的参数) | |
||||||
|
| int | int / long / short / byte | 直接转换为对应整数类型 | |
||||||
|
| int | float / double | 隐式转换为浮点数 | |
||||||
|
| float | float / double | 直接转换 | |
||||||
|
| bool | boolean | 直接转换 | |
||||||
|
| JniObject | 对应的 Java 对象类型 | 提取原始 jobject 传递 | |
||||||
|
|
||||||
|
## 第一步:编写 Java 类 |
||||||
|
|
||||||
|
在项目根目录(或 `classpath` 可访问的位置)创建一个 `Java` 类: |
||||||
|
|
||||||
|
```java |
||||||
|
// Hello.java |
||||||
|
public class Hello { |
||||||
|
private String name; |
||||||
|
private int age; |
||||||
|
|
||||||
|
public Hello(String name, int age) { |
||||||
|
this.name = name; |
||||||
|
this.age = age; |
||||||
|
} |
||||||
|
|
||||||
|
public String greet(String greeting) { |
||||||
|
return greeting + ", I'm " + name + ", " + age + " years old"; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
编译为字节码: |
||||||
|
|
||||||
|
```bash |
||||||
|
javac Hello.java |
||||||
|
``` |
||||||
|
|
||||||
|
除了调用自定义类之外,也可以调用`Java`标准库中的类,如 `java.lang.StringBuilder`、`java.lang.String` 等, |
||||||
|
或者其他第三方类库,如 `com.google.gson.Gson`、`org.apache.commons.lang3.StringUtils` 等,需要使用`maven`等包管理工具 |
||||||
|
引入,并使用 `maven` 构建项目。 |
||||||
|
|
||||||
|
|
||||||
|
## 第二步:配置项目 |
||||||
|
|
||||||
|
创建 `project.yml`,指定 `JNI` 头文件和 `JVM` 库的路径: |
||||||
|
|
||||||
|
```yaml |
||||||
|
name: jni-example |
||||||
|
version: 0.0.1 |
||||||
|
cxxflags: | |
||||||
|
-std=c++17 |
||||||
|
-I/usr/lib/jvm/java-25-openjdk-amd64/include |
||||||
|
-I/usr/lib/jvm/java-25-openjdk-amd64/include/linux |
||||||
|
-Wall |
||||||
|
ldflags: | |
||||||
|
-L/usr/lib/jvm/java-25-openjdk-amd64/lib/server |
||||||
|
-ljvm |
||||||
|
-Wl,-rpath,/usr/lib/jvm/java-25-openjdk-amd64/lib/server |
||||||
|
sources: |
||||||
|
- php-src |
||||||
|
- ./cpp-src |
||||||
|
- main.php |
||||||
|
``` |
||||||
|
|
||||||
|
> **说明**: |
||||||
|
> - `cxxflags` 中 `-I` 指向 `JDK` 的 `JNI` 头文件目录 |
||||||
|
> - `ldflags` 中 `-ljvm` 链接 `JVM` 动态库,`-Wl,-rpath` 确保运行时能找到 `libjvm.so` |
||||||
|
> - `sources` 中 `./cpp-src` 指向 `C++` 实现目录(内含 `jni.cc`),`php-src` 指向 `PHP stub` 文件目录 |
||||||
|
|
||||||
|
## 第三步:编写 PHP 程序 |
||||||
|
|
||||||
|
```php |
||||||
|
<?php |
||||||
|
// main.php |
||||||
|
function main() |
||||||
|
{ |
||||||
|
// 1. 初始化 JVM,传入 classpath |
||||||
|
jni_init("."); |
||||||
|
echo "JVM initialized.\n"; |
||||||
|
|
||||||
|
// -------------------------------------------------- |
||||||
|
// 2. 操作自定义 Hello 类 |
||||||
|
// -------------------------------------------------- |
||||||
|
|
||||||
|
// 查找类 —— 返回 JniClass 句柄 |
||||||
|
$helloClass = jni_find_class("Hello"); |
||||||
|
|
||||||
|
// 查找方法和字段 —— 触发 Java 反射,返回缓存的 JniMethod / JniField 句柄 |
||||||
|
$greet = jni_find_method($helloClass, "greet"); |
||||||
|
$nameField = jni_find_field($helloClass, "name"); |
||||||
|
$ageField = jni_find_field($helloClass, "age"); |
||||||
|
|
||||||
|
// 创建对象 —— 构造器由参数数量自动匹配 |
||||||
|
$hello = jni_new_object($helloClass, ["Swoole", 8]); |
||||||
|
|
||||||
|
// 调用方法 |
||||||
|
$msg = jni_call($hello, $greet, ["你好"]); |
||||||
|
echo $msg . "\n"; // 你好, I'm Swoole, 8 years old |
||||||
|
|
||||||
|
// 读取字段 |
||||||
|
$name = jni_get($hello, $nameField); // "Swoole" |
||||||
|
$age = jni_get($hello, $ageField); // 8 |
||||||
|
|
||||||
|
// 修改字段 |
||||||
|
jni_set($hello, $nameField, "PHP"); |
||||||
|
jni_set($hello, $ageField, 10); |
||||||
|
|
||||||
|
// 再次调用验证 |
||||||
|
echo jni_call($hello, $greet, ["Hi"]) . "\n"; |
||||||
|
// Hi, I'm PHP, 10 years old |
||||||
|
|
||||||
|
// -------------------------------------------------- |
||||||
|
// 3. 操作标准 Java 类 (StringBuilder) |
||||||
|
// -------------------------------------------------- |
||||||
|
|
||||||
|
$sbClass = jni_find_class("java.lang.StringBuilder"); |
||||||
|
$append = jni_find_method($sbClass, "append"); |
||||||
|
$toString = jni_find_method($sbClass, "toString"); |
||||||
|
|
||||||
|
$sb = jni_new_object($sbClass, ["Hello"]); |
||||||
|
jni_call($sb, $append, [" Java"]); |
||||||
|
jni_call($sb, $append, [" JNI"]); |
||||||
|
|
||||||
|
$str = jni_call($sb, $toString, []); |
||||||
|
echo $str . "\n"; // Hello Java JNI |
||||||
|
|
||||||
|
// 4. 销毁 JVM |
||||||
|
jni_destroy(); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## 第四步:编译运行 |
||||||
|
|
||||||
|
编译项目: |
||||||
|
|
||||||
|
```bash |
||||||
|
swoole_compiler examples/jni/project.yml |
||||||
|
``` |
||||||
|
|
||||||
|
运行编译产物: |
||||||
|
|
||||||
|
```bash |
||||||
|
cd jni_example |
||||||
|
LD_LIBRARY_PATH=/opt/php-8.4/lib:/path/to/phpx/lib:/usr/lib/jvm/java-25-openjdk-amd64/lib/server ./jni_example |
||||||
|
``` |
||||||
|
|
||||||
|
> 请注意务必将`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` 编译的程序默认为单线程模型) |
||||||
@ -0,0 +1,869 @@ |
|||||||
|
#include <phpx.h> |
||||||
|
#include <jni.h> |
||||||
|
#include <string> |
||||||
|
#include <cstring> |
||||||
|
#include <unordered_map> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
using namespace php; |
||||||
|
|
||||||
|
static JavaVM *jvm = nullptr; |
||||||
|
static JNIEnv *env = nullptr; |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// JNI helper: check and rethrow JNI exceptions as PHP errors
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static void check_jni_exception() { |
||||||
|
if (env->ExceptionCheck()) { |
||||||
|
env->ExceptionDescribe(); |
||||||
|
env->ExceptionClear(); |
||||||
|
throwError("JNI exception occurred"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Cached reflection helpers (java.lang.reflect.* method IDs)
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static jmethodID s_Class_getName = nullptr; |
||||||
|
static jmethodID s_Class_getDeclaredConstructors = nullptr; |
||||||
|
static jmethodID s_Class_getMethods = nullptr; |
||||||
|
static jmethodID s_Class_getDeclaredFields = nullptr; |
||||||
|
static jmethodID s_Constructor_getParameterTypes = nullptr; |
||||||
|
static jmethodID s_Method_getName = nullptr; |
||||||
|
static jmethodID s_Method_getParameterTypes = nullptr; |
||||||
|
static jmethodID s_Method_getReturnType = nullptr; |
||||||
|
static jmethodID s_Field_getName = nullptr; |
||||||
|
static jmethodID s_Field_getType = nullptr; |
||||||
|
static jmethodID s_Field_getModifiers = nullptr; |
||||||
|
static bool s_reflection_inited = false; |
||||||
|
|
||||||
|
static void init_reflection() { |
||||||
|
if (s_reflection_inited) return; |
||||||
|
|
||||||
|
jclass classCls = env->FindClass("java/lang/Class"); |
||||||
|
s_Class_getName = env->GetMethodID(classCls, "getName", "()Ljava/lang/String;"); |
||||||
|
s_Class_getDeclaredConstructors = env->GetMethodID(classCls, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;"); |
||||||
|
s_Class_getMethods = env->GetMethodID(classCls, "getMethods", "()[Ljava/lang/reflect/Method;"); |
||||||
|
s_Class_getDeclaredFields = env->GetMethodID(classCls, "getDeclaredFields", "()[Ljava/lang/reflect/Field;"); |
||||||
|
env->DeleteLocalRef(classCls); |
||||||
|
|
||||||
|
jclass ctorCls = env->FindClass("java/lang/reflect/Constructor"); |
||||||
|
s_Constructor_getParameterTypes = env->GetMethodID(ctorCls, "getParameterTypes", "()[Ljava/lang/Class;"); |
||||||
|
env->DeleteLocalRef(ctorCls); |
||||||
|
|
||||||
|
jclass methodCls = env->FindClass("java/lang/reflect/Method"); |
||||||
|
s_Method_getName = env->GetMethodID(methodCls, "getName", "()Ljava/lang/String;"); |
||||||
|
s_Method_getParameterTypes = env->GetMethodID(methodCls, "getParameterTypes", "()[Ljava/lang/Class;"); |
||||||
|
s_Method_getReturnType = env->GetMethodID(methodCls, "getReturnType", "()Ljava/lang/Class;"); |
||||||
|
env->DeleteLocalRef(methodCls); |
||||||
|
|
||||||
|
jclass fieldCls = env->FindClass("java/lang/reflect/Field"); |
||||||
|
s_Field_getName = env->GetMethodID(fieldCls, "getName", "()Ljava/lang/String;"); |
||||||
|
s_Field_getType = env->GetMethodID(fieldCls, "getType", "()Ljava/lang/Class;"); |
||||||
|
s_Field_getModifiers = env->GetMethodID(fieldCls, "getModifiers", "()I"); |
||||||
|
env->DeleteLocalRef(fieldCls); |
||||||
|
|
||||||
|
s_reflection_inited = true; |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Convert a java.lang.Class object to a JNI type signature string
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static std::string class_to_jni_sig(jclass typeClass) { |
||||||
|
jstring nameStr = (jstring) env->CallObjectMethod(typeClass, s_Class_getName); |
||||||
|
const char *name = env->GetStringUTFChars(nameStr, nullptr); |
||||||
|
std::string result(name); |
||||||
|
env->ReleaseStringUTFChars(nameStr, name); |
||||||
|
env->DeleteLocalRef(nameStr); |
||||||
|
|
||||||
|
// Primitives
|
||||||
|
if (result == "int") return "I"; |
||||||
|
if (result == "long") return "J"; |
||||||
|
if (result == "float") return "F"; |
||||||
|
if (result == "double") return "D"; |
||||||
|
if (result == "boolean") return "Z"; |
||||||
|
if (result == "byte") return "B"; |
||||||
|
if (result == "char") return "C"; |
||||||
|
if (result == "short") return "S"; |
||||||
|
if (result == "void") return "V"; |
||||||
|
|
||||||
|
// Array types: Class.getName() returns "[Lfoo.Bar;" etc.
|
||||||
|
// Convert dots to slashes for object types inside arrays
|
||||||
|
for (auto &ch : result) { |
||||||
|
if (ch == '.') ch = '/'; |
||||||
|
} |
||||||
|
// Non-array object type: prepend L and append ;
|
||||||
|
if (result[0] != '[') { |
||||||
|
result = "L" + result + ";"; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Count JNI type tags in an arg_tags string
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static int count_tags(const std::string &arg_tags) { |
||||||
|
int n = 0; |
||||||
|
const char *p = arg_tags.c_str(); |
||||||
|
while (*p) { |
||||||
|
if (*p == 'L') { |
||||||
|
const char *semi = strchr(p, ';'); |
||||||
|
if (!semi) return -1; |
||||||
|
p = semi + 1; |
||||||
|
} else if (*p == '[') { |
||||||
|
while (*p == '[') p++; |
||||||
|
if (*p == 'L') { |
||||||
|
const char *semi = strchr(p, ';'); |
||||||
|
if (!semi) return -1; |
||||||
|
p = semi + 1; |
||||||
|
} else { |
||||||
|
p++; |
||||||
|
} |
||||||
|
} else { |
||||||
|
p++; |
||||||
|
} |
||||||
|
n++; |
||||||
|
} |
||||||
|
return n; |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Step through JNI signature, optionally writing tag to out.
|
||||||
|
// If out is nullptr, just advances past the tag.
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static const char *next_tag(const char *p, char *out = nullptr) { |
||||||
|
if (*p == 'L') { |
||||||
|
const char *semi = strchr(p, ';'); |
||||||
|
if (!semi) throwError("Invalid JNI object signature"); |
||||||
|
size_t n = semi - p + 1; |
||||||
|
if (out) { |
||||||
|
memcpy(out, p, n); |
||||||
|
out[n] = '\0'; |
||||||
|
} |
||||||
|
return semi + 1; |
||||||
|
} else if (*p == '[') { |
||||||
|
const char *q = p; |
||||||
|
while (*q == '[') q++; |
||||||
|
if (*q == 'L') { |
||||||
|
const char *semi = strchr(q, ';'); |
||||||
|
if (!semi) throwError("Invalid JNI array signature"); |
||||||
|
size_t n = semi - p + 1; |
||||||
|
if (out) { |
||||||
|
memcpy(out, p, n); |
||||||
|
out[n] = '\0'; |
||||||
|
} |
||||||
|
return semi + 1; |
||||||
|
} else { |
||||||
|
if (out) { |
||||||
|
out[0] = *p; |
||||||
|
out[1] = '\0'; |
||||||
|
} |
||||||
|
return p + 1; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (out) { |
||||||
|
out[0] = *p; |
||||||
|
out[1] = '\0'; |
||||||
|
} |
||||||
|
return p + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Data structures for cached reflection info
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
struct MethodInfo { |
||||||
|
jmethodID method_id = nullptr; |
||||||
|
std::string arg_tags; // e.g., "Ljava/lang/String;I"
|
||||||
|
std::string return_sig; // e.g., "Ljava/lang/String;"
|
||||||
|
}; |
||||||
|
|
||||||
|
struct FieldInfo { |
||||||
|
jfieldID field_id = nullptr; |
||||||
|
std::string sig; // e.g., "Ljava/lang/String;" or "I"
|
||||||
|
bool is_static = false; |
||||||
|
}; |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Box types: wrap JNI handles and cached reflection data
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Box type identifiers (stored in Box::type_info)
|
||||||
|
enum : uint32_t { |
||||||
|
BOX_JNI_CLASS = 1, |
||||||
|
BOX_JNI_METHOD = 2, |
||||||
|
BOX_JNI_FIELD = 3, |
||||||
|
BOX_JNI_OBJECT = 4, |
||||||
|
}; |
||||||
|
|
||||||
|
class JniClass; |
||||||
|
|
||||||
|
class JniClass : public Box { |
||||||
|
public: |
||||||
|
jclass cls; |
||||||
|
std::unordered_map<std::string, std::vector<MethodInfo>> methods; |
||||||
|
std::unordered_map<std::string, FieldInfo> fields; |
||||||
|
std::vector<MethodInfo> constructors; |
||||||
|
bool reflected = false; |
||||||
|
|
||||||
|
explicit JniClass(jclass c) : cls(c) { type_info = BOX_JNI_CLASS; } |
||||||
|
~JniClass() override { |
||||||
|
if (cls && env) env->DeleteGlobalRef(cls); |
||||||
|
} |
||||||
|
|
||||||
|
void reflect(); |
||||||
|
const std::vector<MethodInfo> *find_method(const std::string &name); |
||||||
|
const FieldInfo *find_field(const std::string &name); |
||||||
|
const MethodInfo *find_constructor(int nargs); |
||||||
|
}; |
||||||
|
|
||||||
|
class JniMethod : public Box { |
||||||
|
public: |
||||||
|
std::vector<MethodInfo> overloads; |
||||||
|
|
||||||
|
JniMethod() { type_info = BOX_JNI_METHOD; } |
||||||
|
|
||||||
|
// Score how well a PHP arg matches a JNI parameter type tag
|
||||||
|
static int score_arg_match(const Variant &arg, const char *tag_start) { |
||||||
|
char tag[256]; |
||||||
|
next_tag(tag_start, tag); |
||||||
|
switch (tag[0]) { |
||||||
|
case 'L': |
||||||
|
if (strcmp(tag, "Ljava/lang/String;") == 0 && arg.isString()) return 10; |
||||||
|
if (arg.isString()) return 5; // auto-convert string → jstring
|
||||||
|
if (arg.isResource()) return 3; // JniObject
|
||||||
|
return 1; |
||||||
|
case 'I': case 'J': case 'S': case 'B': |
||||||
|
if (arg.isInt()) return 10; |
||||||
|
if (arg.isFloat()) return 3; |
||||||
|
return 1; |
||||||
|
case 'F': case 'D': |
||||||
|
if (arg.isFloat()) return 10; |
||||||
|
if (arg.isInt()) return 5; |
||||||
|
return 1; |
||||||
|
case 'Z': |
||||||
|
if (arg.isBool()) return 10; |
||||||
|
return 1; |
||||||
|
default: |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const MethodInfo *find_by_arg_count(int nargs, Array &args) const { |
||||||
|
const MethodInfo *best = nullptr; |
||||||
|
int best_score = -1; |
||||||
|
for (auto &m : overloads) { |
||||||
|
if (count_tags(m.arg_tags) != nargs) continue; |
||||||
|
int score = 0; |
||||||
|
const char *p = m.arg_tags.c_str(); |
||||||
|
for (int i = 0; i < nargs; i++) { |
||||||
|
score += score_arg_match(args[i], p); |
||||||
|
p = next_tag(p); |
||||||
|
} |
||||||
|
if (score > best_score) { |
||||||
|
best_score = score; |
||||||
|
best = &m; |
||||||
|
} |
||||||
|
} |
||||||
|
return best; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class JniField : public Box { |
||||||
|
public: |
||||||
|
FieldInfo info; |
||||||
|
|
||||||
|
JniField() { type_info = BOX_JNI_FIELD; } |
||||||
|
}; |
||||||
|
|
||||||
|
class JniObject : public Box { |
||||||
|
public: |
||||||
|
jobject obj; |
||||||
|
explicit JniObject(jobject o) : obj(o) { type_info = BOX_JNI_OBJECT; } |
||||||
|
~JniObject() override { |
||||||
|
if (obj && env) env->DeleteGlobalRef(obj); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Lazy reflection: populate methods/fields/constructors via Java reflection
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
void JniClass::reflect() { |
||||||
|
if (reflected) return; |
||||||
|
init_reflection(); |
||||||
|
|
||||||
|
// --- Constructors via getDeclaredConstructors() ---
|
||||||
|
jobjectArray ctors = (jobjectArray) env->CallObjectMethod(cls, s_Class_getDeclaredConstructors); |
||||||
|
jsize nctors = env->GetArrayLength(ctors); |
||||||
|
for (jsize i = 0; i < nctors; i++) { |
||||||
|
jobject ctor = env->GetObjectArrayElement(ctors, i); |
||||||
|
jobjectArray paramTypes = (jobjectArray) env->CallObjectMethod(ctor, s_Constructor_getParameterTypes); |
||||||
|
jsize nparams = env->GetArrayLength(paramTypes); |
||||||
|
|
||||||
|
std::string argTags; |
||||||
|
for (jsize j = 0; j < nparams; j++) { |
||||||
|
jclass paramClass = (jclass) env->GetObjectArrayElement(paramTypes, j); |
||||||
|
argTags += class_to_jni_sig(paramClass); |
||||||
|
env->DeleteLocalRef(paramClass); |
||||||
|
} |
||||||
|
|
||||||
|
std::string fullSig = "(" + argTags + ")V"; |
||||||
|
jmethodID methodId = env->GetMethodID(cls, "<init>", fullSig.c_str()); |
||||||
|
constructors.push_back({methodId, argTags, "V"}); |
||||||
|
|
||||||
|
env->DeleteLocalRef(paramTypes); |
||||||
|
env->DeleteLocalRef(ctor); |
||||||
|
} |
||||||
|
env->DeleteLocalRef(ctors); |
||||||
|
|
||||||
|
// If no explicit constructors, add default no-arg constructor
|
||||||
|
if (nctors == 0) { |
||||||
|
jmethodID methodId = env->GetMethodID(cls, "<init>", "()V"); |
||||||
|
if (!env->ExceptionCheck()) { |
||||||
|
constructors.push_back({methodId, "", "V"}); |
||||||
|
} else { |
||||||
|
env->ExceptionClear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// --- Methods via getMethods() ---
|
||||||
|
jobjectArray methods_arr = (jobjectArray) env->CallObjectMethod(cls, s_Class_getMethods); |
||||||
|
jsize nmethods = env->GetArrayLength(methods_arr); |
||||||
|
for (jsize i = 0; i < nmethods; i++) { |
||||||
|
jobject method = env->GetObjectArrayElement(methods_arr, i); |
||||||
|
jstring nameStr = (jstring) env->CallObjectMethod(method, s_Method_getName); |
||||||
|
const char *nameChars = env->GetStringUTFChars(nameStr, nullptr); |
||||||
|
std::string methodName(nameChars); |
||||||
|
env->ReleaseStringUTFChars(nameStr, nameChars); |
||||||
|
env->DeleteLocalRef(nameStr); |
||||||
|
|
||||||
|
// Parameter types
|
||||||
|
jobjectArray paramTypes = (jobjectArray) env->CallObjectMethod(method, s_Method_getParameterTypes); |
||||||
|
jsize nparams = env->GetArrayLength(paramTypes); |
||||||
|
|
||||||
|
std::string argTags; |
||||||
|
for (jsize j = 0; j < nparams; j++) { |
||||||
|
jclass paramClass = (jclass) env->GetObjectArrayElement(paramTypes, j); |
||||||
|
argTags += class_to_jni_sig(paramClass); |
||||||
|
env->DeleteLocalRef(paramClass); |
||||||
|
} |
||||||
|
|
||||||
|
// Return type
|
||||||
|
jclass returnClass = (jclass) env->CallObjectMethod(method, s_Method_getReturnType); |
||||||
|
std::string returnSig = class_to_jni_sig(returnClass); |
||||||
|
env->DeleteLocalRef(returnClass); |
||||||
|
|
||||||
|
std::string fullSig = "(" + argTags + ")" + returnSig; |
||||||
|
jmethodID methodId = env->GetMethodID(cls, methodName.c_str(), fullSig.c_str()); |
||||||
|
|
||||||
|
methods[methodName].push_back({methodId, argTags, returnSig}); |
||||||
|
|
||||||
|
env->DeleteLocalRef(paramTypes); |
||||||
|
env->DeleteLocalRef(method); |
||||||
|
} |
||||||
|
env->DeleteLocalRef(methods_arr); |
||||||
|
|
||||||
|
// --- Fields via getDeclaredFields() ---
|
||||||
|
jobjectArray fields_arr = (jobjectArray) env->CallObjectMethod(cls, s_Class_getDeclaredFields); |
||||||
|
jsize nfields = env->GetArrayLength(fields_arr); |
||||||
|
for (jsize i = 0; i < nfields; i++) { |
||||||
|
jobject field = env->GetObjectArrayElement(fields_arr, i); |
||||||
|
jstring nameStr = (jstring) env->CallObjectMethod(field, s_Field_getName); |
||||||
|
const char *nameChars = env->GetStringUTFChars(nameStr, nullptr); |
||||||
|
std::string fieldName(nameChars); |
||||||
|
env->ReleaseStringUTFChars(nameStr, nameChars); |
||||||
|
env->DeleteLocalRef(nameStr); |
||||||
|
|
||||||
|
jclass typeClass = (jclass) env->CallObjectMethod(field, s_Field_getType); |
||||||
|
std::string sig = class_to_jni_sig(typeClass); |
||||||
|
env->DeleteLocalRef(typeClass); |
||||||
|
|
||||||
|
jint mods = env->CallIntMethod(field, s_Field_getModifiers); |
||||||
|
bool isStatic = (mods & 0x0008) != 0; // java.lang.reflect.Modifier.STATIC = 8
|
||||||
|
|
||||||
|
jfieldID fieldId; |
||||||
|
if (isStatic) { |
||||||
|
fieldId = env->GetStaticFieldID(cls, fieldName.c_str(), sig.c_str()); |
||||||
|
} else { |
||||||
|
fieldId = env->GetFieldID(cls, fieldName.c_str(), sig.c_str()); |
||||||
|
} |
||||||
|
fields[fieldName] = {fieldId, sig, isStatic}; |
||||||
|
|
||||||
|
env->DeleteLocalRef(field); |
||||||
|
} |
||||||
|
env->DeleteLocalRef(fields_arr); |
||||||
|
|
||||||
|
reflected = true; |
||||||
|
} |
||||||
|
|
||||||
|
const std::vector<MethodInfo> *JniClass::find_method(const std::string &name) { |
||||||
|
reflect(); |
||||||
|
auto it = methods.find(name); |
||||||
|
return (it != methods.end()) ? &it->second : nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
const FieldInfo *JniClass::find_field(const std::string &name) { |
||||||
|
reflect(); |
||||||
|
auto it = fields.find(name); |
||||||
|
return (it != fields.end()) ? &it->second : nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
const MethodInfo *JniClass::find_constructor(int nargs) { |
||||||
|
reflect(); |
||||||
|
for (auto &c : constructors) { |
||||||
|
if (count_tags(c.arg_tags) == nargs) return &c; |
||||||
|
} |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Safe, non-throwing Box extraction with type checking via Box::getTypeInfo()
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static Box *get_box(const Variant &v) { |
||||||
|
if (!v.isResource()) return nullptr; |
||||||
|
auto *mut = const_cast<Variant *>(&v); |
||||||
|
auto res = Z_RES_P(mut->unwrap_ptr()); |
||||||
|
if (res->type != getBoxResourceId()) return nullptr; |
||||||
|
return static_cast<Box *>(res->ptr); |
||||||
|
} |
||||||
|
|
||||||
|
template <uint32_t Type> |
||||||
|
static inline bool is_box_type(const Variant &v) { |
||||||
|
auto *box = get_box(v); |
||||||
|
return box && box->getTypeInfo() == Type; |
||||||
|
} |
||||||
|
|
||||||
|
static inline JniClass *to_jni_class(const Variant &v) { |
||||||
|
auto *box = get_box(v); |
||||||
|
return (box && box->getTypeInfo() == BOX_JNI_CLASS) ? static_cast<JniClass *>(box) : nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
static inline JniMethod *to_jni_method(const Variant &v) { |
||||||
|
auto *box = get_box(v); |
||||||
|
return (box && box->getTypeInfo() == BOX_JNI_METHOD) ? static_cast<JniMethod *>(box) : nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
static inline JniField *to_jni_field(const Variant &v) { |
||||||
|
auto *box = get_box(v); |
||||||
|
return (box && box->getTypeInfo() == BOX_JNI_FIELD) ? static_cast<JniField *>(box) : nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
static inline JniObject *to_jni_object(const Variant &v) { |
||||||
|
auto *box = get_box(v); |
||||||
|
return (box && box->getTypeInfo() == BOX_JNI_OBJECT) ? static_cast<JniObject *>(box) : nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Extract jclass from a Variant (JniClass or JniObject)
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static jclass resolve_class(const Variant &v) { |
||||||
|
if (auto *jc = to_jni_class(v)) { |
||||||
|
return jc->cls; |
||||||
|
} |
||||||
|
if (auto *jo = to_jni_object(v)) { |
||||||
|
return env->GetObjectClass(jo->obj); |
||||||
|
} |
||||||
|
throwError("Argument must be a JniClass or JniObject"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
// Get the JniClass* from a Variant (JniClass directly, or from JniObject's class)
|
||||||
|
static JniClass *resolve_jni_class(const Variant &v) { |
||||||
|
if (auto *jc = to_jni_class(v)) { |
||||||
|
return jc; |
||||||
|
} |
||||||
|
throwError("Argument must be a JniClass handle"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
static jobject resolve_object(const Variant &v) { |
||||||
|
if (auto *jo = to_jni_object(v)) { |
||||||
|
return jo->obj; |
||||||
|
} |
||||||
|
throwError("Argument must be a JniObject"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
static bool is_jni_class(const Variant &v) { |
||||||
|
return is_box_type<BOX_JNI_CLASS>(v); |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Marshal: Variant → jvalue (by single-char JNI type tag)
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static jvalue marshal_arg(const Variant &arg, char tag) { |
||||||
|
jvalue jv{}; |
||||||
|
switch (tag) { |
||||||
|
case 'Z': jv.z = (jboolean) arg.toBool(); break; |
||||||
|
case 'B': jv.b = (jbyte) arg.toInt(); break; |
||||||
|
case 'C': jv.c = (jchar) arg.toInt(); break; |
||||||
|
case 'S': jv.s = (jshort) arg.toInt(); break; |
||||||
|
case 'I': jv.i = (jint) arg.toInt(); break; |
||||||
|
case 'J': jv.j = (jlong) arg.toInt(); break; |
||||||
|
case 'F': jv.f = (jfloat) arg.toFloat(); break; |
||||||
|
case 'D': jv.d = (jdouble) arg.toFloat(); break; |
||||||
|
case 'L': |
||||||
|
if (auto *jo = to_jni_object(arg)) { |
||||||
|
jv.l = jo->obj; |
||||||
|
} else { |
||||||
|
jv.l = env->NewStringUTF(arg.toCString()); |
||||||
|
} |
||||||
|
break; |
||||||
|
case '[': |
||||||
|
if (auto *jo = to_jni_object(arg)) { |
||||||
|
jv.l = jo->obj; |
||||||
|
} else { |
||||||
|
throwError("Array argument must be a JniObject"); |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
throwError("Unknown JNI tag: %c", tag); |
||||||
|
} |
||||||
|
return jv; |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Marshal: JNI return → Variant (by full type signature string)
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
static Variant marshal_return(jvalue jv, const std::string &return_sig) { |
||||||
|
const char *sig = return_sig.c_str(); |
||||||
|
switch (sig[0]) { |
||||||
|
case 'V': return nullptr; |
||||||
|
case 'Z': return Variant((bool) jv.z); |
||||||
|
case 'B': return Variant((Int) jv.b); |
||||||
|
case 'C': return Variant((Int) jv.c); |
||||||
|
case 'S': return Variant((Int) jv.s); |
||||||
|
case 'I': return Variant((Int) jv.i); |
||||||
|
case 'J': return Variant((Int) jv.j); |
||||||
|
case 'F': return Variant((double) jv.f); |
||||||
|
case 'D': return Variant(jv.d); |
||||||
|
case 'L': |
||||||
|
case '[': { |
||||||
|
if (!jv.l) return nullptr; |
||||||
|
jclass stringClass = env->FindClass("java/lang/String"); |
||||||
|
if (env->IsInstanceOf((jobject) jv.l, stringClass)) { |
||||||
|
auto *jstr = (jstring) jv.l; |
||||||
|
const char *chars = env->GetStringUTFChars(jstr, nullptr); |
||||||
|
String ret(chars); |
||||||
|
env->ReleaseStringUTFChars(jstr, chars); |
||||||
|
env->DeleteLocalRef(stringClass); |
||||||
|
return ret; |
||||||
|
} |
||||||
|
env->DeleteLocalRef(stringClass); |
||||||
|
jobject global = env->NewGlobalRef((jobject) jv.l); |
||||||
|
return {new JniObject(global)}; |
||||||
|
} |
||||||
|
default: |
||||||
|
throwError("Unknown JNI return type: %s", sig); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// PHP-callable functions
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
void php_jni_init(String classpath) { |
||||||
|
if (jvm) return; |
||||||
|
|
||||||
|
JavaVMInitArgs vm_args; |
||||||
|
JavaVMOption options[2]; |
||||||
|
std::string cp = "-Djava.class.path=" + std::string(classpath.data(), classpath.length()); |
||||||
|
options[0].optionString = cp.data(); |
||||||
|
options[1].optionString = const_cast<char *>("-Xcheck:jni"); |
||||||
|
|
||||||
|
vm_args.version = JNI_VERSION_10; |
||||||
|
vm_args.nOptions = 1; |
||||||
|
vm_args.options = options; |
||||||
|
vm_args.ignoreUnrecognized = JNI_TRUE; |
||||||
|
|
||||||
|
jint rc = JNI_CreateJavaVM(&jvm, (void **) &env, &vm_args); |
||||||
|
if (rc != JNI_OK) { |
||||||
|
throwError("Failed to create JVM, error code: %d", rc); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void php_jni_destroy() { |
||||||
|
if (jvm) { |
||||||
|
jvm->DestroyJavaVM(); |
||||||
|
jvm = nullptr; |
||||||
|
env = nullptr; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* jni_find_class(string $className): mixed |
||||||
|
* Returns a JniClass handle. Reflection is lazy, triggered on first use. |
||||||
|
*/ |
||||||
|
var php_jni_find_class(String className) { |
||||||
|
if (!jvm) throwError("JNI not initialized, call jni_init() first"); |
||||||
|
|
||||||
|
std::string cn(className.data(), className.length()); |
||||||
|
for (auto &ch : cn) { |
||||||
|
if (ch == '.') ch = '/'; |
||||||
|
} |
||||||
|
|
||||||
|
jclass cls = env->FindClass(cn.c_str()); |
||||||
|
check_jni_exception(); |
||||||
|
if (!cls) { |
||||||
|
throwError("Class not found: %s", className.data()); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
jclass globalCls = (jclass) env->NewGlobalRef(cls); |
||||||
|
env->DeleteLocalRef(cls); |
||||||
|
return {new JniClass(globalCls)}; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* jni_find_method(mixed $objOrClass, string $methodName): mixed |
||||||
|
* Returns a JniMethod handle containing all overloads of the named method. |
||||||
|
*/ |
||||||
|
var php_jni_find_method(var objOrClass, String methodName) { |
||||||
|
auto *jc = resolve_jni_class(objOrClass); |
||||||
|
auto *overloads = jc->find_method(std::string(methodName.data(), methodName.length())); |
||||||
|
if (!overloads || overloads->empty()) { |
||||||
|
throwError("Method not found: %s", methodName.data()); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
auto *box = new JniMethod(); |
||||||
|
box->overloads = *overloads; |
||||||
|
return {box}; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* jni_find_field(mixed $objOrClass, string $fieldName): mixed |
||||||
|
* Returns a JniField handle with cached type and field ID. |
||||||
|
*/ |
||||||
|
var php_jni_find_field(var objOrClass, String fieldName) { |
||||||
|
auto *jc = resolve_jni_class(objOrClass); |
||||||
|
auto *info = jc->find_field(std::string(fieldName.data(), fieldName.length())); |
||||||
|
if (!info) { |
||||||
|
throwError("Field not found: %s", fieldName.data()); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
auto *box = new JniField(); |
||||||
|
box->info = *info; |
||||||
|
return {box}; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* jni_new_object(mixed $classHandle, array $args = []): mixed |
||||||
|
* Creates a new Java object. Constructor is found automatically by arg count. |
||||||
|
*/ |
||||||
|
var php_jni_new_object(var classHandle, Array args) { |
||||||
|
auto *jc = to_jni_class(classHandle); |
||||||
|
if (!jc) throwError("First argument must be a JniClass handle"); |
||||||
|
|
||||||
|
int nargs = args.count(); |
||||||
|
auto *ctorInfo = jc->find_constructor(nargs); |
||||||
|
if (!ctorInfo) { |
||||||
|
throwError("No constructor found with %d argument(s)", nargs); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
|
||||||
|
// Marshal args using cached signature
|
||||||
|
std::vector<jvalue> jvals(nargs); |
||||||
|
const char *p = ctorInfo->arg_tags.c_str(); |
||||||
|
for (int i = 0; i < nargs; i++) { |
||||||
|
char tag[256]; |
||||||
|
p = next_tag(p, tag); |
||||||
|
jvals[i] = marshal_arg(args[i], tag[0]); |
||||||
|
} |
||||||
|
|
||||||
|
jobject obj = env->NewObjectA(jc->cls, ctorInfo->method_id, jvals.data()); |
||||||
|
check_jni_exception(); |
||||||
|
if (!obj) { |
||||||
|
throwError("Failed to create object"); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
jobject globalObj = env->NewGlobalRef(obj); |
||||||
|
env->DeleteLocalRef(obj); |
||||||
|
return {new JniObject(globalObj)}; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* jni_call(mixed $objOrClass, mixed $method, array $args = []): mixed |
||||||
|
* Call an instance or static method. $method is a JniMethod handle. |
||||||
|
*/ |
||||||
|
var php_jni_call(var objOrClass, var method, Array args) { |
||||||
|
jclass cls = resolve_class(objOrClass); |
||||||
|
jobject obj = is_jni_class(objOrClass) ? nullptr : resolve_object(objOrClass); |
||||||
|
|
||||||
|
auto *jm = to_jni_method(method); |
||||||
|
if (!jm) throwError("Second argument must be a JniMethod handle"); |
||||||
|
|
||||||
|
int nargs = args.count(); |
||||||
|
auto *methodInfo = jm->find_by_arg_count(nargs, args); |
||||||
|
if (!methodInfo) { |
||||||
|
throwError("No matching overload of method with %d argument(s)", nargs); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
|
||||||
|
// Marshal args
|
||||||
|
std::vector<jvalue> jvals(nargs); |
||||||
|
const char *p = methodInfo->arg_tags.c_str(); |
||||||
|
for (int i = 0; i < nargs; i++) { |
||||||
|
char tag[256]; |
||||||
|
p = next_tag(p, tag); |
||||||
|
jvals[i] = marshal_arg(args[i], tag[0]); |
||||||
|
} |
||||||
|
|
||||||
|
bool isStatic = (obj == nullptr); |
||||||
|
jvalue ret{}; |
||||||
|
const char *retSig = methodInfo->return_sig.c_str(); |
||||||
|
|
||||||
|
switch (retSig[0]) { |
||||||
|
case 'V': |
||||||
|
if (isStatic) { |
||||||
|
env->CallStaticVoidMethodA(cls, methodInfo->method_id, jvals.data()); |
||||||
|
} else { |
||||||
|
env->CallVoidMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
} |
||||||
|
check_jni_exception(); |
||||||
|
if (!isStatic) env->DeleteLocalRef(cls); |
||||||
|
return nullptr; |
||||||
|
case 'Z': |
||||||
|
ret.z = isStatic ? env->CallStaticBooleanMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallBooleanMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'B': |
||||||
|
ret.b = isStatic ? env->CallStaticByteMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallByteMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'C': |
||||||
|
ret.c = isStatic ? env->CallStaticCharMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallCharMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'S': |
||||||
|
ret.s = isStatic ? env->CallStaticShortMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallShortMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'I': |
||||||
|
ret.i = isStatic ? env->CallStaticIntMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallIntMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'J': |
||||||
|
ret.j = isStatic ? env->CallStaticLongMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallLongMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'F': |
||||||
|
ret.f = isStatic ? env->CallStaticFloatMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallFloatMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'D': |
||||||
|
ret.d = isStatic ? env->CallStaticDoubleMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallDoubleMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
case 'L': |
||||||
|
case '[': |
||||||
|
ret.l = isStatic ? env->CallStaticObjectMethodA(cls, methodInfo->method_id, jvals.data()) |
||||||
|
: env->CallObjectMethodA(obj, methodInfo->method_id, jvals.data()); |
||||||
|
break; |
||||||
|
default: |
||||||
|
if (!isStatic) env->DeleteLocalRef(cls); |
||||||
|
throwError("Unknown JNI return type: %s", retSig); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
check_jni_exception(); |
||||||
|
|
||||||
|
if (!isStatic) { |
||||||
|
env->DeleteLocalRef(cls); |
||||||
|
} |
||||||
|
return marshal_return(ret, methodInfo->return_sig); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* jni_get(mixed $objOrClass, mixed $field): mixed |
||||||
|
* Read an instance or static field. $field is a JniField handle. |
||||||
|
*/ |
||||||
|
var php_jni_get(var objOrClass, var field) { |
||||||
|
jclass cls = resolve_class(objOrClass); |
||||||
|
jobject obj = is_jni_class(objOrClass) ? nullptr : resolve_object(objOrClass); |
||||||
|
|
||||||
|
auto *jf = to_jni_field(field); |
||||||
|
if (!jf) throwError("Field argument must be a JniField handle"); |
||||||
|
|
||||||
|
jvalue jv{}; |
||||||
|
const char *s = jf->info.sig.c_str(); |
||||||
|
|
||||||
|
if (jf->info.is_static) { |
||||||
|
switch (s[0]) { |
||||||
|
case 'Z': jv.z = env->GetStaticBooleanField(cls, jf->info.field_id); break; |
||||||
|
case 'B': jv.b = env->GetStaticByteField(cls, jf->info.field_id); break; |
||||||
|
case 'C': jv.c = env->GetStaticCharField(cls, jf->info.field_id); break; |
||||||
|
case 'S': jv.s = env->GetStaticShortField(cls, jf->info.field_id); break; |
||||||
|
case 'I': jv.i = env->GetStaticIntField(cls, jf->info.field_id); break; |
||||||
|
case 'J': jv.j = env->GetStaticLongField(cls, jf->info.field_id); break; |
||||||
|
case 'F': jv.f = env->GetStaticFloatField(cls, jf->info.field_id); break; |
||||||
|
case 'D': jv.d = env->GetStaticDoubleField(cls, jf->info.field_id); break; |
||||||
|
case 'L': case '[': jv.l = env->GetStaticObjectField(cls, jf->info.field_id); break; |
||||||
|
default: throwError("Unknown field type: %s", s); |
||||||
|
} |
||||||
|
} else { |
||||||
|
switch (s[0]) { |
||||||
|
case 'Z': jv.z = env->GetBooleanField(obj, jf->info.field_id); break; |
||||||
|
case 'B': jv.b = env->GetByteField(obj, jf->info.field_id); break; |
||||||
|
case 'C': jv.c = env->GetCharField(obj, jf->info.field_id); break; |
||||||
|
case 'S': jv.s = env->GetShortField(obj, jf->info.field_id); break; |
||||||
|
case 'I': jv.i = env->GetIntField(obj, jf->info.field_id); break; |
||||||
|
case 'J': jv.j = env->GetLongField(obj, jf->info.field_id); break; |
||||||
|
case 'F': jv.f = env->GetFloatField(obj, jf->info.field_id); break; |
||||||
|
case 'D': jv.d = env->GetDoubleField(obj, jf->info.field_id); break; |
||||||
|
case 'L': case '[': jv.l = env->GetObjectField(obj, jf->info.field_id); break; |
||||||
|
default: throwError("Unknown field type: %s", s); |
||||||
|
} |
||||||
|
} |
||||||
|
check_jni_exception(); |
||||||
|
|
||||||
|
if (!is_jni_class(objOrClass)) { |
||||||
|
env->DeleteLocalRef(cls); |
||||||
|
} |
||||||
|
return marshal_return(jv, jf->info.sig); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* jni_set(mixed $objOrClass, mixed $field, mixed $value): void |
||||||
|
* Write an instance or static field. $field is a JniField handle. |
||||||
|
*/ |
||||||
|
void php_jni_set(var objOrClass, var field, var value) { |
||||||
|
jclass cls = resolve_class(objOrClass); |
||||||
|
jobject obj = is_jni_class(objOrClass) ? nullptr : resolve_object(objOrClass); |
||||||
|
|
||||||
|
auto *jf = to_jni_field(field); |
||||||
|
if (!jf) throwError("Field argument must be a JniField handle"); |
||||||
|
|
||||||
|
jvalue jv = marshal_arg(value, jf->info.sig[0]); |
||||||
|
const char *s = jf->info.sig.c_str(); |
||||||
|
|
||||||
|
if (jf->info.is_static) { |
||||||
|
switch (s[0]) { |
||||||
|
case 'Z': env->SetStaticBooleanField(cls, jf->info.field_id, jv.z); break; |
||||||
|
case 'B': env->SetStaticByteField(cls, jf->info.field_id, jv.b); break; |
||||||
|
case 'C': env->SetStaticCharField(cls, jf->info.field_id, jv.c); break; |
||||||
|
case 'S': env->SetStaticShortField(cls, jf->info.field_id, jv.s); break; |
||||||
|
case 'I': env->SetStaticIntField(cls, jf->info.field_id, jv.i); break; |
||||||
|
case 'J': env->SetStaticLongField(cls, jf->info.field_id, jv.j); break; |
||||||
|
case 'F': env->SetStaticFloatField(cls, jf->info.field_id, jv.f); break; |
||||||
|
case 'D': env->SetStaticDoubleField(cls, jf->info.field_id, jv.d); break; |
||||||
|
case 'L': case '[': env->SetStaticObjectField(cls, jf->info.field_id, (jobject) jv.l); break; |
||||||
|
default: throwError("Unknown field type: %s", s); |
||||||
|
} |
||||||
|
} else { |
||||||
|
switch (s[0]) { |
||||||
|
case 'Z': env->SetBooleanField(obj, jf->info.field_id, jv.z); break; |
||||||
|
case 'B': env->SetByteField(obj, jf->info.field_id, jv.b); break; |
||||||
|
case 'C': env->SetCharField(obj, jf->info.field_id, jv.c); break; |
||||||
|
case 'S': env->SetShortField(obj, jf->info.field_id, jv.s); break; |
||||||
|
case 'I': env->SetIntField(obj, jf->info.field_id, jv.i); break; |
||||||
|
case 'J': env->SetLongField(obj, jf->info.field_id, jv.j); break; |
||||||
|
case 'F': env->SetFloatField(obj, jf->info.field_id, jv.f); break; |
||||||
|
case 'D': env->SetDoubleField(obj, jf->info.field_id, jv.d); break; |
||||||
|
case 'L': case '[': env->SetObjectField(obj, jf->info.field_id, (jobject) jv.l); break; |
||||||
|
default: throwError("Unknown field type: %s", s); |
||||||
|
} |
||||||
|
} |
||||||
|
check_jni_exception(); |
||||||
|
|
||||||
|
if (!is_jni_class(objOrClass)) { |
||||||
|
env->DeleteLocalRef(cls); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,76 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
// 1. 初始化 JVM |
||||||
|
echo "=== Step 1: Initialize JVM ===\n"; |
||||||
|
jni_init("."); |
||||||
|
echo "JVM initialized.\n\n"; |
||||||
|
|
||||||
|
// --------------------------------------------------------------- |
||||||
|
// 2. 动态创建自定义 Hello 对象、调用方法、读写属性 |
||||||
|
// --------------------------------------------------------------- |
||||||
|
echo "=== Step 2: Dynamic Java Object (Hello) ===\n"; |
||||||
|
|
||||||
|
// 查找类 |
||||||
|
$helloClass = jni_find_class("Hello"); |
||||||
|
echo "Found class: Hello\n"; |
||||||
|
|
||||||
|
// 获取方法、属性(反射 + 缓存为 Box 类型) |
||||||
|
$greet = jni_find_method($helloClass, "greet"); |
||||||
|
$nameField = jni_find_field($helloClass, "name"); |
||||||
|
$ageField = jni_find_field($helloClass, "age"); |
||||||
|
|
||||||
|
// 创建对象: Hello(String name, int age) |
||||||
|
$hello = jni_new_object($helloClass, ["Swoole", 8]); |
||||||
|
echo "Created Hello object.\n"; |
||||||
|
|
||||||
|
// 调用方法: String greet(String greeting) |
||||||
|
$msg = jni_call($hello, $greet, ["你好"]); |
||||||
|
echo "greet() → " . $msg . "\n"; |
||||||
|
|
||||||
|
// 读取属性 |
||||||
|
$name = jni_get($hello, $nameField); |
||||||
|
$age = jni_get($hello, $ageField); |
||||||
|
echo "name = " . $name . ", age = " . $age . "\n"; |
||||||
|
|
||||||
|
// 修改属性 |
||||||
|
jni_set($hello, $nameField, "PHP"); |
||||||
|
jni_set($hello, $ageField, 10); |
||||||
|
$name2 = jni_get($hello, $nameField); |
||||||
|
$age2 = jni_get($hello, $ageField); |
||||||
|
echo "After set: name = " . $name2 . ", age = " . $age2 . "\n"; |
||||||
|
echo "greet() → " . jni_call($hello, $greet, ["Hi"]) . "\n\n"; |
||||||
|
|
||||||
|
// --------------------------------------------------------------- |
||||||
|
// 3. 使用标准 Java 类 (StringBuilder) |
||||||
|
// --------------------------------------------------------------- |
||||||
|
echo "=== Step 3: Standard Java Class (StringBuilder) ===\n"; |
||||||
|
|
||||||
|
$sbClass = jni_find_class("java.lang.StringBuilder"); |
||||||
|
echo "Found class: java.lang.StringBuilder\n"; |
||||||
|
|
||||||
|
// 获取方法 |
||||||
|
$append = jni_find_method($sbClass, "append"); |
||||||
|
$toString = jni_find_method($sbClass, "toString"); |
||||||
|
|
||||||
|
// StringBuilder sb = new StringBuilder("Hello"); |
||||||
|
$sb = jni_new_object($sbClass, ["Hello"]); |
||||||
|
echo "Created StringBuilder.\n"; |
||||||
|
|
||||||
|
// sb.append(" Java") |
||||||
|
$sb2 = jni_call($sb, $append, [" Java"]); |
||||||
|
// sb.append(" JNI") |
||||||
|
jni_call($sb, $append, [" JNI"]); |
||||||
|
|
||||||
|
// sb.toString() |
||||||
|
$str = jni_call($sb, $toString, []); |
||||||
|
echo "toString() → " . $str . "\n\n"; |
||||||
|
|
||||||
|
// --------------------------------------------------------------- |
||||||
|
// 4. 销毁 JVM |
||||||
|
// --------------------------------------------------------------- |
||||||
|
echo "=== Step 4: Destroy JVM ===\n"; |
||||||
|
jni_destroy(); |
||||||
|
echo "JVM destroyed.\n"; |
||||||
|
} |
||||||
@ -0,0 +1,104 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/** |
||||||
|
* Initialize the JVM with a classpath. |
||||||
|
* Must be called once before any other jni_* functions. |
||||||
|
*/ |
||||||
|
function jni_init(string $classpath = "."): void |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Destroy the JVM and release all resources. |
||||||
|
*/ |
||||||
|
function jni_destroy(): void |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find a Java class by name (e.g. "Hello" or "java.lang.StringBuilder"). |
||||||
|
* Returns a JniClass handle. |
||||||
|
*/ |
||||||
|
function jni_find_class(string $className): mixed |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find a method by name. Returns a JniMethod handle that caches |
||||||
|
* the jmethodID and type signatures. Handles method overloading; |
||||||
|
* the correct overload is selected at call time by argument count. |
||||||
|
* |
||||||
|
* @param mixed $objOrClass JniClass handle (or JniObject to get its class) |
||||||
|
* @param string $methodName Java method name |
||||||
|
* @return mixed JniMethod handle |
||||||
|
*/ |
||||||
|
function jni_find_method(mixed $objOrClass, string $methodName): mixed |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find a field by name. Returns a JniField handle that caches |
||||||
|
* the jfieldID and type signature. |
||||||
|
* |
||||||
|
* @param mixed $objOrClass JniClass handle (or JniObject to get its class) |
||||||
|
* @param string $fieldName Java field name |
||||||
|
* @return mixed JniField handle |
||||||
|
*/ |
||||||
|
function jni_find_field(mixed $objOrClass, string $fieldName): mixed |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new Java object. The constructor is found automatically |
||||||
|
* by matching argument count. |
||||||
|
* |
||||||
|
* @param mixed $classHandle A JniClass handle from jni_find_class() |
||||||
|
* @param array $args Constructor arguments |
||||||
|
* @return mixed JniObject handle |
||||||
|
*/ |
||||||
|
function jni_new_object(mixed $classHandle, array $args = []): mixed |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Call a Java method (instance or static). |
||||||
|
* |
||||||
|
* @param mixed $objOrClass JniObject (instance method) or JniClass (static method) |
||||||
|
* @param mixed $method JniMethod handle from jni_find_method() |
||||||
|
* @param array $args Method arguments |
||||||
|
* @return mixed Return value (string/int/bool/JniObject, or null for void) |
||||||
|
*/ |
||||||
|
function jni_call(mixed $objOrClass, mixed $method, array $args = []): mixed |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read a Java field (instance or static). |
||||||
|
* |
||||||
|
* @param mixed $objOrClass JniObject (instance field) or JniClass (static field) |
||||||
|
* @param mixed $field JniField handle from jni_find_field() |
||||||
|
* @return mixed Field value |
||||||
|
*/ |
||||||
|
function jni_get(mixed $objOrClass, mixed $field): mixed |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Write a Java field (instance or static). |
||||||
|
* |
||||||
|
* @param mixed $objOrClass JniObject (instance field) or JniClass (static field) |
||||||
|
* @param mixed $field JniField handle from jni_find_field() |
||||||
|
* @param mixed $value New value to assign |
||||||
|
*/ |
||||||
|
function jni_set(mixed $objOrClass, mixed $field, mixed $value): void |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
name: jni-example |
||||||
|
version: 0.0.1 |
||||||
|
cxxflags: | |
||||||
|
-std=c++17 |
||||||
|
-I/usr/lib/jvm/java-25-openjdk-amd64/include |
||||||
|
-I/usr/lib/jvm/java-25-openjdk-amd64/include/linux |
||||||
|
-Wall |
||||||
|
ldflags: | |
||||||
|
-L/usr/lib/jvm/java-25-openjdk-amd64/lib/server |
||||||
|
-ljvm |
||||||
|
-Wl,-rpath,/usr/lib/jvm/java-25-openjdk-amd64/lib/server |
||||||
|
sources: |
||||||
|
- php-src |
||||||
|
- ./cpp-src |
||||||
|
- main.php |
||||||
Loading…
Reference in new issue