|
| 1 | +#include <dlfcn.h> |
| 2 | + |
| 3 | +#include "djni.h" |
| 4 | + |
| 5 | +static JNI_GetDefaultJavaVMInitArgs_fnptr JNI_GetDefaultJavaVMInitArgs_fn; |
| 6 | +static JNI_CreateJavaVM_fnptr JNI_CreateJavaVM_fn; |
| 7 | +static JNI_GetCreatedJavaVMs_fnptr JNI_GetCreatedJavaVMs_fn; |
| 8 | + |
| 9 | +static void *jni_dl = 0; |
| 10 | + |
| 11 | +static const char *last_error = 0; |
| 12 | + |
| 13 | +static int load_sym(const char *sym, void **ptr) { |
| 14 | + void *v = dlsym(jni_dl, sym); |
| 15 | + if (!v) { |
| 16 | + last_error = dlerror(); |
| 17 | + dlclose(jni_dl); |
| 18 | + jni_dl = 0; |
| 19 | + return -1; |
| 20 | + } |
| 21 | + *ptr = v; |
| 22 | + return 0; |
| 23 | +} |
| 24 | + |
| 25 | +/* path: path to libjvm |
| 26 | + returns: 0 = success, -1 = already loaded, -2 = dlopen error, -3 symbols not found */ |
| 27 | +int djni_load(const char *path) { |
| 28 | + last_error = 0; |
| 29 | + if (jni_dl) return -1; |
| 30 | + jni_dl = dlopen(path, RTLD_LOCAL | RTLD_NOW); |
| 31 | + if (!jni_dl) { |
| 32 | + last_error = dlerror(); |
| 33 | + return -2; |
| 34 | + } |
| 35 | + if (load_sym("JNI_GetDefaultJavaVMInitArgs", (void**) &JNI_GetDefaultJavaVMInitArgs_fn) || |
| 36 | + load_sym("JNI_CreateJavaVM", (void**) &JNI_CreateJavaVM_fn) || |
| 37 | + load_sym("JNI_GetCreatedJavaVMs", (void**) &JNI_GetCreatedJavaVMs_fn)) { |
| 38 | + JNI_GetDefaultJavaVMInitArgs_fn = 0; |
| 39 | + JNI_CreateJavaVM_fn = 0; |
| 40 | + JNI_GetCreatedJavaVMs_fn = 0; |
| 41 | + return -3; |
| 42 | + } |
| 43 | + return 0; |
| 44 | +} |
| 45 | + |
| 46 | +/* returns: 0 = success, -1 = noting loaded, -2 = unload failed */ |
| 47 | +int djni_unload(void) { |
| 48 | + if (jni_dl) { |
| 49 | + if (dlclose(jni_dl)) |
| 50 | + return -2; |
| 51 | + jni_dl = 0; |
| 52 | + return 0; |
| 53 | + } |
| 54 | + return -1; |
| 55 | +} |
| 56 | + |
| 57 | +/* The following are the "normal" JNI API calls which are routed to libjvm JNI API. |
| 58 | + If no JNI was loaded, they return -99 to distinguish it from JNI error codes. */ |
| 59 | + |
| 60 | +jint JNI_GetDefaultJavaVMInitArgs(void *args) { |
| 61 | + return JNI_GetDefaultJavaVMInitArgs_fn ? JNI_GetDefaultJavaVMInitArgs_fn(args) : -99; |
| 62 | +} |
| 63 | + |
| 64 | +jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args) { |
| 65 | + return JNI_CreateJavaVM_fn ? JNI_CreateJavaVM_fn(pvm, penv, args) : -99; |
| 66 | +} |
| 67 | + |
| 68 | +jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) { |
| 69 | + return JNI_GetCreatedJavaVMs_fn ? JNI_GetCreatedJavaVMs_fn(vmBuf, bufLen, nVMs) : -99; |
| 70 | +} |
| 71 | + |
| 72 | +/* define DJNI_MAIN_TEST to test the load + create + print version + destroy + unload cycle |
| 73 | + it will create an executable - just compile djni.c alone for this (not used in rJava) */ |
| 74 | +#ifdef DJNI_MAIN_TEST |
| 75 | + |
| 76 | +#include <stdio.h> |
| 77 | +#include <stdlib.h> |
| 78 | +#include <string.h> |
| 79 | + |
| 80 | +/* calls java.lang.System.getProperty("java.version") and returns the result or NULL |
| 81 | + if something went wrong. Any non-NULL returns must be free()d */ |
| 82 | +static char *java_version(JNIEnv *eenv) { |
| 83 | + char *jver = 0; |
| 84 | + jclass sys = (*eenv)->FindClass(eenv, "java/lang/System"); |
| 85 | + if (sys) { |
| 86 | + jmethodID mid = (*eenv)->GetStaticMethodID(eenv, sys, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); |
| 87 | + if (mid) { |
| 88 | + jstring jvs = (*eenv)->NewStringUTF(eenv, "java.version"); |
| 89 | + if (jvs) { |
| 90 | + jobject jres = (*eenv)->CallStaticObjectMethod(eenv, sys, mid, jvs); |
| 91 | + (*eenv)->DeleteLocalRef(eenv, jvs); |
| 92 | + if (jres) { |
| 93 | + const char *str = (*eenv)->GetStringUTFChars(eenv, (jstring) jres, NULL); |
| 94 | + if (str) { |
| 95 | + jver = strdup(str); |
| 96 | + (*eenv)->ReleaseStringUTFChars(eenv, (jstring) jres, str); |
| 97 | + } |
| 98 | + (*eenv)->DeleteLocalRef(eenv, jres); |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + (*eenv)->DeleteLocalRef(eenv, sys); |
| 103 | + } |
| 104 | + return jver; |
| 105 | +} |
| 106 | + |
| 107 | +/* create a JVM, call java_version(), destroy the JVM */ |
| 108 | +static int run_jvm_test(void) { |
| 109 | + JavaVM *jvm; |
| 110 | + JNIEnv *eenv; |
| 111 | + JavaVMOption vm_options[4]; |
| 112 | + JavaVMInitArgs vm_args; |
| 113 | + vm_args.version = JNI_VERSION_1_2; |
| 114 | + if(JNI_GetDefaultJavaVMInitArgs(&vm_args) != JNI_OK) { |
| 115 | + fprintf(stderr, "JNI 1.2 or higher is required\n"); |
| 116 | + return -1; |
| 117 | + } |
| 118 | + vm_args.ignoreUnrecognized = JNI_TRUE; |
| 119 | + vm_args.options = vm_options; |
| 120 | + vm_args.nOptions = 0; |
| 121 | + jint res = JNI_CreateJavaVM(&jvm,(void **)&eenv, &vm_args); |
| 122 | + if (res != 0) { |
| 123 | + fprintf(stderr, "Cannot create Java virtual machine (JNI_CreateJavaVM returned %d)\n", (int) res); |
| 124 | + return -1; |
| 125 | + } |
| 126 | + if (!eenv) { |
| 127 | + fprintf(stderr, "Cannot obtain JVM environment"); |
| 128 | + return -1; |
| 129 | + } |
| 130 | + printf("JVM initialized, JNI version: %x\n", (*eenv)->GetVersion(eenv)); |
| 131 | + { |
| 132 | + char *jver = java_version(eenv); |
| 133 | + if (jver) { |
| 134 | + printf("Java version: %s\n", jver); |
| 135 | + free(jver); |
| 136 | + } |
| 137 | + } |
| 138 | + /* this shouldn't be even needed, but was last-ditch effort - makes no difference */ |
| 139 | + if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) |
| 140 | + fprintf(stderr, "FWIW: cannot detach thread.\n"); |
| 141 | + /* destroy the VM */ |
| 142 | + if ((*jvm)->DestroyJavaVM(jvm) != JNI_OK) { |
| 143 | + fprintf(stderr, "ERROR: cannot destroy VM\n"); |
| 144 | + return -1; |
| 145 | + } |
| 146 | + printf("JVM destroyed.\n"); |
| 147 | + return 0; |
| 148 | +} |
| 149 | + |
| 150 | +int test_jvm(const char *path) { |
| 151 | + int e = djni_load(path); |
| 152 | + if (e) { |
| 153 | + printf("Cannot load: %d (%s)\n", e, last_error ? last_error : "not a dl error"); |
| 154 | + return 1; |
| 155 | + } |
| 156 | + printf("JVM Loaded!\n"); |
| 157 | + if (!run_jvm_test()) { |
| 158 | + if (djni_unload()) |
| 159 | + fprintf(stderr, "ERROR: cannot unload JNI (%s)\n", last_error ? last_error : "no information"); |
| 160 | + else |
| 161 | + printf("JNI unloaded.\n"); |
| 162 | + } |
| 163 | + return 0; |
| 164 | +} |
| 165 | + |
| 166 | +/* for debugging on macOS - prints all dynamic loads */ |
| 167 | +#ifdef __APPLE__ |
| 168 | +#include <mach-o/dyld.h> |
| 169 | + |
| 170 | +static void add_image(const struct mach_header* mh, intptr_t vmaddr_slide) { |
| 171 | + Dl_info DlInfo; |
| 172 | + dladdr(mh, &DlInfo); |
| 173 | + printf("LOADED: %s\n", DlInfo.dli_fname); |
| 174 | +} |
| 175 | + |
| 176 | +static void rm_image(const struct mach_header* mh, intptr_t vmaddr_slide) { |
| 177 | + Dl_info DlInfo; |
| 178 | + dladdr(mh, &DlInfo); |
| 179 | + printf("REMOVE: %s\n", DlInfo.dli_fname); |
| 180 | +} |
| 181 | +#endif |
| 182 | + |
| 183 | +int main(int ac, char **av) { |
| 184 | +#ifdef __APPLE__ |
| 185 | + _dyld_register_func_for_add_image(add_image); |
| 186 | + _dyld_register_func_for_remove_image(rm_image); |
| 187 | +#endif |
| 188 | + |
| 189 | + test_jvm("/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/lib/server/libjvm.dylib"); |
| 190 | +#ifdef SAME_VM /* option 1: try the same VM twice (will load but fail to create VM) */ |
| 191 | + test_jvm("/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/lib/server/libjvm.dylib"); |
| 192 | +#else /* option 2: will even fail to load */ |
| 193 | + test_jvm("/Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home/lib/server/libjvm.dylib"); |
| 194 | +#endif |
| 195 | + return 0; |
| 196 | +} |
| 197 | + |
| 198 | +#endif |
0 commit comments