- 在.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