/* * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #ifndef __JNIUTILITIES_H #define __JNIUTILITIES_H #include "jni.h" #include "jni_util.h" #import /******** LOGGING SUPPORT *********/ #define LOG_NULL(dst_var, name) \ if (dst_var == NULL) { \ NSLog(@"Bad JNI lookup %s\n", name); \ NSLog(@"%@",[NSThread callStackSymbols]); \ if ([NSThread isMainThread] == NO) { \ if ((*env)->ExceptionOccurred(env) == NULL) { \ JNU_ThrowInternalError(env, "Bad JNI Lookup"); \ } \ } else { \ if ((*env)->ExceptionOccurred(env) != NULL) { \ (*env)->ExceptionDescribe(env); \ } \ } \ [NSException raise:NSGenericException format:@"JNI Lookup Exception"]; \ } /******** GET CLASS SUPPORT *********/ #define GET_CLASS(dst_var, cls) \ if (dst_var == NULL) { \ dst_var = (*env)->FindClass(env, cls); \ if (dst_var != NULL) dst_var = (*env)->NewGlobalRef(env, dst_var); \ } \ LOG_NULL(dst_var, cls); \ CHECK_NULL(dst_var); #define DECLARE_CLASS(dst_var, cls) \ static jclass dst_var = NULL; \ GET_CLASS(dst_var, cls); #define GET_CLASS_RETURN(dst_var, cls, ret) \ if (dst_var == NULL) { \ dst_var = (*env)->FindClass(env, cls); \ if (dst_var != NULL) dst_var = (*env)->NewGlobalRef(env, dst_var); \ } \ LOG_NULL(dst_var, cls); \ CHECK_NULL_RETURN(dst_var, ret); #define DECLARE_CLASS_RETURN(dst_var, cls, ret) \ static jclass dst_var = NULL; \ GET_CLASS_RETURN(dst_var, cls, ret); /******** GET METHOD SUPPORT *********/ #define GET_METHOD(dst_var, cls, name, signature) \ if (dst_var == NULL) { \ dst_var = (*env)->GetMethodID(env, cls, name, signature); \ } \ LOG_NULL(dst_var, name); \ CHECK_NULL(dst_var); #define DECLARE_METHOD(dst_var, cls, name, signature) \ static jmethodID dst_var = NULL; \ GET_METHOD(dst_var, cls, name, signature); #define GET_METHOD_RETURN(dst_var, cls, name, signature, ret) \ if (dst_var == NULL) { \ dst_var = (*env)->GetMethodID(env, cls, name, signature); \ } \ LOG_NULL(dst_var, name); \ CHECK_NULL_RETURN(dst_var, ret); #define DECLARE_METHOD_RETURN(dst_var, cls, name, signature, ret) \ static jmethodID dst_var = NULL; \ GET_METHOD_RETURN(dst_var, cls, name, signature, ret); #define GET_STATIC_METHOD(dst_var, cls, name, signature) \ if (dst_var == NULL) { \ dst_var = (*env)->GetStaticMethodID(env, cls, name, signature); \ } \ LOG_NULL(dst_var, name); \ CHECK_NULL(dst_var); #define DECLARE_STATIC_METHOD(dst_var, cls, name, signature) \ static jmethodID dst_var = NULL; \ GET_STATIC_METHOD(dst_var, cls, name, signature); #define GET_STATIC_METHOD_RETURN(dst_var, cls, name, signature, ret) \ if (dst_var == NULL) { \ dst_var = (*env)->GetStaticMethodID(env, cls, name, signature); \ } \ LOG_NULL(dst_var, name); \ CHECK_NULL_RETURN(dst_var, ret); #define DECLARE_STATIC_METHOD_RETURN(dst_var, cls, name, signature, ret) \ static jmethodID dst_var = NULL; \ GET_STATIC_METHOD_RETURN(dst_var, cls, name, signature, ret); /******** GET FIELD SUPPORT *********/ #define GET_FIELD(dst_var, cls, name, signature) \ if (dst_var == NULL) { \ dst_var = (*env)->GetFieldID(env, cls, name, signature); \ } \ LOG_NULL(dst_var, name); \ CHECK_NULL(dst_var); #define DECLARE_FIELD(dst_var, cls, name, signature) \ static jfieldID dst_var = NULL; \ GET_FIELD(dst_var, cls, name, signature); #define GET_FIELD_RETURN(dst_var, cls, name, signature, ret) \ if (dst_var == NULL) { \ dst_var = (*env)->GetFieldID(env, cls, name, signature); \ } \ LOG_NULL(dst_var, name); \ CHECK_NULL_RETURN(dst_var, ret); #define DECLARE_FIELD_RETURN(dst_var, cls, name, signature, ret) \ static jfieldID dst_var = NULL; \ GET_FIELD_RETURN(dst_var, cls, name, signature, ret); #define GET_STATIC_FIELD_RETURN(dst_var, cls, name, signature, ret) \ if (dst_var == NULL) { \ dst_var = (*env)->GetStaticFieldID(env, cls, name, signature); \ } \ LOG_NULL(dst_var, name); \ CHECK_NULL_RETURN(dst_var, ret); #define DECLARE_STATIC_FIELD_RETURN(dst_var, cls, name, signature, ret) \ static jfieldID dst_var = NULL; \ GET_STATIC_FIELD_RETURN(dst_var, cls, name, signature, ret); /********* EXCEPTION_HANDLING *********/ /* * Some explanation to set context of the bigger picture. * Before returning to Java from JNI, NSExceptions are caught - so long as * the body of the native method is wrapped in the ENTER/EXIT macros. * So if we want to directly return to Java from some nested Objective-C * function when detecting a Java exception, we just need to raise an * NSException. Then clear that right before returning to Java, * leaving the Java exception to be seen back in Java-land. * * But if the current thread is the Appkit thread we might as well clear * the Java Exception right now since there's nothing to receive it. * In such a case control will propagate back to the run loop which might * terminate the application. One drawback of that is that the location of * termination does not show where the NSException originated. * And for whatever reason, something swallows that exception. * So as a debugging aid, when on the AppKit thread we can provide a * way (via an env. var.) to log the location. * Additionally provide a similar way to prevent the NSException being * raised and instead just clear the Java Exception. * Together these provide alternate behaviours for more debugging info * or maybe a way for the app to continue running depending on the exact * nature of the problem that has been detected and how survivable it is. */ #define CHECK_EXCEPTION() \ if ((*env)->ExceptionOccurred(env) != NULL) { \ if ([NSThread isMainThread] == YES) { \ if (getenv("JNU_APPKIT_TRACE")) { \ (*env)->ExceptionDescribe(env); \ NSLog(@"%@",[NSThread callStackSymbols]); \ } else { \ (*env)->ExceptionClear(env); \ } \ } \ if (getenv("JNU_NO_COCOA_EXCEPTION") == NULL) { \ [NSException raise:NSGenericException format:@"Java Exception"]; \ } else { \ (*env)->ExceptionClear(env); \ } \ }; #define CHECK_EXCEPTION_NULL_RETURN(x, y) \ CHECK_EXCEPTION(); \ if ((x) == NULL) { \ return y; \ }; /* Create a pool and initiate a try block to catch any exception */ #define JNI_COCOA_ENTER(env) \ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \ @try { /* Don't allow NSExceptions to escape to Java. * If there is a Java exception that has been thrown that should escape. * And ensure we drain the auto-release pool. */ #define JNI_COCOA_EXIT(env) \ } \ @catch (NSException *e) { \ NSLog(@"%@", [e callStackSymbols]); \ } \ @finally { \ [pool drain]; \ }; /* Same as above but adds a clean up action. * Requires that whatever is being cleaned up is in scope. */ #define JNI_COCOA_EXIT_WITH_ACTION(env, action) \ } \ @catch (NSException *e) { \ { action; }; \ NSLog(@"%@", [e callStackSymbols]); \ } \ @finally { \ [pool drain]; \ }; /******** STRING CONVERSION SUPPORT *********/ JNIEXPORT NSString* JavaStringToNSString(JNIEnv *env, jstring jstr); JNIEXPORT jstring NSStringToJavaString(JNIEnv* env, NSString *str); JNIEXPORT NSString* NormalizedPathNSStringFromJavaString(JNIEnv *env, jstring pathStr); JNIEXPORT jstring NormalizedPathJavaStringFromNSString(JNIEnv* env, NSString *str); #endif /* __JNIUTILITIES_H */