#include #include #include #include #include #include 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> methods; std::unordered_map fields; std::vector 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 *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 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, "", 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, "", "()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 *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(&v); auto res = Z_RES_P(mut->unwrap_ptr()); if (res->type != getBoxResourceId()) return nullptr; return static_cast(res->ptr); } template 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(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(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(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(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(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("-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 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 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); } }