TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
869 lines
31 KiB
869 lines
31 KiB
#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);
|
|
}
|
|
}
|
|
|