Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "PlatformLibrary"
     18 #include "utils/Log.h"
     19 
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <unistd.h>
     23 #include <assert.h>
     24 
     25 #include "jni.h"
     26 
     27 
     28 // ----------------------------------------------------------------------------
     29 
     30 /*
     31  * Field/method IDs and class object references.
     32  *
     33  * You should not need to store the JNIEnv pointer in here.  It is
     34  * thread-specific and will be passed back in on every call.
     35  */
     36 static struct {
     37     jclass      platformLibraryClass;
     38     jfieldID    jniInt;
     39     jmethodID   yodel;
     40 } gCachedState;
     41 
     42 // ----------------------------------------------------------------------------
     43 
     44 /*
     45  * Helper function to throw an arbitrary exception.
     46  *
     47  * Takes the exception class name, a format string, and one optional integer
     48  * argument (useful for including an error code, perhaps from errno).
     49  */
     50 static void throwException(JNIEnv* env, const char* ex, const char* fmt,
     51     int data) {
     52 
     53     if (jclass cls = env->FindClass(ex)) {
     54         if (fmt != NULL) {
     55             char msg[1000];
     56             snprintf(msg, sizeof(msg), fmt, data);
     57             env->ThrowNew(cls, msg);
     58         } else {
     59             env->ThrowNew(cls, NULL);
     60         }
     61 
     62         /*
     63          * This is usually not necessary -- local references are released
     64          * automatically when the native code returns to the VM.  It's
     65          * required if the code doesn't actually return, e.g. it's sitting
     66          * in a native event loop.
     67          */
     68         env->DeleteLocalRef(cls);
     69     }
     70 }
     71 
     72 /*
     73  * Trivial sample method.
     74  *
     75  * If "bad" is true, this throws an exception.  Otherwise, this sets the
     76  * "mJniInt" field to 42 and returns 24.
     77  */
     78 static jint PlatformLibrary_getJniInt(JNIEnv* env, jobject thiz, jboolean bad) {
     79     if (bad) {
     80         throwException(env, "java/lang/IllegalStateException",
     81                 "you are bad", 0);
     82         return 0;       /* return value will be ignored */
     83     }
     84     env->SetIntField(thiz, gCachedState.jniInt, 42);
     85     return (jint)24;
     86 }
     87 
     88 /*
     89  * A more complex sample method.
     90  *
     91  * This takes a String as an argument, and returns a new String with
     92  * characters in reverse order.  The new string is passed to another method.
     93  * This demonstrates basic String manipulation functions and method
     94  * invocation.
     95  *
     96  * This method is declared "static", so there's no "this" pointer; instead,
     97  * we get a pointer to the class object.
     98  */
     99 static jstring PlatformLibrary_reverseString(JNIEnv* env, jclass clazz,
    100     jstring str) {
    101 
    102     if (str == NULL) {
    103         throwException(env, "java/lang/NullPointerException", NULL, 0);
    104         return NULL;
    105     }
    106 
    107     /*
    108      * Get a pointer to the string's UTF-16 character data.  The data
    109      * may be a copy or a pointer to the original.  Since String data
    110      * is immutable, we're not allowed to touch it.
    111      */
    112     const jchar* strChars = env->GetStringChars(str, NULL);
    113     if (strChars == NULL) {
    114         /* something went wrong */
    115         ALOGW("Couldn't get string chars\n");
    116         return NULL;
    117     }
    118     jsize strLength = env->GetStringLength(str);
    119 
    120     /*
    121      * Write a progress message to the log.  Log messages are UTF-8, so
    122      * we want to convert the string to show it.
    123      */
    124     const char* printable = env->GetStringUTFChars(str, NULL);
    125     if (printable != NULL) {
    126         ALOGD("Reversing string '%s'\n", printable);
    127         env->ReleaseStringUTFChars(str, printable);
    128     }
    129 
    130     /*
    131      * Copy the characters to temporary storage, reversing as we go.
    132      */
    133     jchar tempChars[strLength];
    134     for (int i = 0; i < strLength; i++) {
    135         tempChars[i] = strChars[strLength -1 -i];
    136     }
    137 
    138     /*
    139      * Release the original String.  That way, if something fails later on,
    140      * we don't have to worry about this leading to a memory leak.
    141      */
    142     env->ReleaseStringChars(str, strChars);
    143     strChars = NULL;            /* this pointer no longer valid */
    144 
    145     /*
    146      * Create a new String with the chars.
    147      */
    148     jstring result = env->NewString(tempChars, strLength);
    149     if (result == NULL) {
    150         ALOGE("NewString failed\n");
    151         return NULL;
    152     }
    153 
    154     /*
    155      * Now let's do something with it.  We already have the methodID for
    156      * "yodel", so we can invoke it directly.  It's in our class, so we
    157      * can use the Class object reference that was passed in.
    158      */
    159     env->CallStaticVoidMethod(clazz, gCachedState.yodel, result);
    160 
    161     return result;
    162 }
    163 
    164 
    165 // ----------------------------------------------------------------------------
    166 
    167 /*
    168  * Array of methods.
    169  *
    170  * Each entry has three fields: the name of the method, the method
    171  * signature, and a pointer to the native implementation.
    172  */
    173 static const JNINativeMethod gMethods[] = {
    174     { "getJniInt",          "(Z)I",
    175                         (void*)PlatformLibrary_getJniInt },
    176     { "reverseString",      "(Ljava/lang/String;)Ljava/lang/String;",
    177                         (void*)PlatformLibrary_reverseString },
    178 };
    179 
    180 /*
    181  * Do some (slow-ish) lookups now and save the results.
    182  *
    183  * Returns 0 on success.
    184  */
    185 static int cacheIds(JNIEnv* env, jclass clazz) {
    186     /*
    187      * Save the class in case we want to use it later.  Because this is a
    188      * reference to the Class object, we need to convert it to a JNI global
    189      * reference.
    190      */
    191     gCachedState.platformLibraryClass = (jclass) env->NewGlobalRef(clazz);
    192     if (clazz == NULL) {
    193         ALOGE("Can't create new global ref\n");
    194         return -1;
    195     }
    196 
    197     /*
    198      * Cache field and method IDs.  IDs are not references, which means we
    199      * don't need to call NewGlobalRef on them.
    200      */
    201     gCachedState.jniInt = env->GetFieldID(clazz, "mJniInt", "I");
    202     if (gCachedState.jniInt == NULL) {
    203         ALOGE("Can't find PlatformLibrary.mJniInt\n");
    204         return -1;
    205     }
    206 
    207     gCachedState.yodel = env->GetStaticMethodID(clazz, "yodel",
    208         "(Ljava/lang/String;)V");
    209     if (gCachedState.yodel == NULL) {
    210         ALOGE("Can't find PlatformLibrary.yodel\n");
    211         return -1;
    212     }
    213 
    214     return 0;
    215 }
    216 
    217 /*
    218  * Explicitly register all methods for our class.
    219  *
    220  * While we're at it, cache some class references and method/field IDs.
    221  *
    222  * Returns 0 on success.
    223  */
    224 static int registerMethods(JNIEnv* env) {
    225     static const char* const kClassName =
    226         "com/example/android/platform_library/PlatformLibrary";
    227     jclass clazz;
    228 
    229     /* look up the class */
    230     clazz = env->FindClass(kClassName);
    231     if (clazz == NULL) {
    232         ALOGE("Can't find class %s\n", kClassName);
    233         return -1;
    234     }
    235 
    236     /* register all the methods */
    237     if (env->RegisterNatives(clazz, gMethods,
    238             sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
    239     {
    240         ALOGE("Failed registering methods for %s\n", kClassName);
    241         return -1;
    242     }
    243 
    244     /* fill out the rest of the ID cache */
    245     return cacheIds(env, clazz);
    246 }
    247 
    248 // ----------------------------------------------------------------------------
    249 
    250 /*
    251  * This is called by the VM when the shared library is first loaded.
    252  */
    253 jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    254     JNIEnv* env = NULL;
    255     jint result = -1;
    256 
    257     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    258         ALOGE("ERROR: GetEnv failed\n");
    259         goto bail;
    260     }
    261     assert(env != NULL);
    262 
    263     if (registerMethods(env) != 0) {
    264         ALOGE("ERROR: PlatformLibrary native registration failed\n");
    265         goto bail;
    266     }
    267 
    268     /* success -- return valid version number */
    269     result = JNI_VERSION_1_4;
    270 
    271 bail:
    272     return result;
    273 }
    274