Move JNIHelp's implementation to C++.

Ouch.

Change-Id: I81c0457a95549f1bef7cc8d9ab23d6ca4475cdb5
diff --git a/Android.mk b/Android.mk
index 088de10..3b0b6dc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -20,13 +20,13 @@
 #
 
 src_files := \
-    JNIHelp.c \
-    Register.c
+    JNIHelp.cpp \
+    Register.cpp
 
 c_includes := \
     $(JNI_H_INCLUDE)
 
-# Any shared/static libs required by libjavacore
+# Any shared/static libs required by libcore
 # need to be mentioned here as well.
 # TODO: fix this requirement
 
diff --git a/JNIHelp.c b/JNIHelp.c
deleted file mode 100644
index d3e5ffb..0000000
--- a/JNIHelp.c
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * JNI helper functions.
- */
-#define LOG_TAG "JNIHelp"
-#include "JNIHelp.h"
-#include "utils/Log.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-/*
- * Register native JNI-callable methods.
- *
- * "className" looks like "java/lang/String".
- */
-int jniRegisterNativeMethods(JNIEnv* env, const char* className,
-    const JNINativeMethod* gMethods, int numMethods)
-{
-    jclass clazz;
-
-    LOGV("Registering %s natives\n", className);
-    clazz = (*env)->FindClass(env, className);
-    if (clazz == NULL) {
-        LOGE("Native registration unable to find class '%s', aborting\n",
-            className);
-        abort();
-    }
-
-    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
-        LOGE("RegisterNatives failed for '%s', aborting\n", className);
-        abort();
-    }
-
-    (*env)->DeleteLocalRef(env, clazz);
-    return 0;
-}
-
-/*
- * Get a human-readable summary of an exception object.  The buffer will
- * be populated with the "binary" class name and, if present, the
- * exception message.
- */
-static char* getExceptionSummary(JNIEnv* env, jthrowable exception) {
-    char* result = NULL;
-
-    /* get the name of the exception's class */
-    jclass exceptionClazz = (*env)->GetObjectClass(env, exception); // can't fail
-    jclass classClazz = (*env)->GetObjectClass(env, exceptionClazz); // java.lang.Class, can't fail
-    jmethodID classGetNameMethod = (*env)->GetMethodID(
-            env, classClazz, "getName", "()Ljava/lang/String;");
-    jstring classNameStr = (*env)->CallObjectMethod(env, exceptionClazz, classGetNameMethod);
-    if (classNameStr != NULL) {
-        /* get printable string */
-        const char* classNameChars = (*env)->GetStringUTFChars(env, classNameStr, NULL);
-        if (classNameChars != NULL) {
-            /* if the exception has a message string, get that */
-            jmethodID throwableGetMessageMethod = (*env)->GetMethodID(
-                    env, exceptionClazz, "getMessage", "()Ljava/lang/String;");
-            jstring messageStr = (*env)->CallObjectMethod(
-                    env, exception, throwableGetMessageMethod);
-
-            if (messageStr == NULL) {
-                result = strdup(classNameChars);
-            } else {
-                const char* messageChars = (*env)->GetStringUTFChars(env, messageStr, NULL);
-                if (messageChars != NULL) {
-                    asprintf(&result, "%s: %s", classNameChars, messageChars);
-                    (*env)->ReleaseStringUTFChars(env, messageStr, messageChars);
-                } else {
-                    (*env)->ExceptionClear(env); // clear OOM
-                    asprintf(&result, "%s: <error getting message>", classNameChars);
-                }
-                (*env)->DeleteLocalRef(env, messageStr);
-            }
-
-            (*env)->ReleaseStringUTFChars(env, classNameStr, classNameChars);
-        }
-        (*env)->DeleteLocalRef(env, classNameStr);
-    }
-    (*env)->DeleteLocalRef(env, classClazz);
-    (*env)->DeleteLocalRef(env, exceptionClazz);
-
-    if (result == NULL) {
-        (*env)->ExceptionClear(env);
-        result = strdup("<error getting class name>");
-    }
-
-    return result;
-}
-
-/*
- * Formats an exception as a string with its stack trace.
- */
-static char* printStackTrace(JNIEnv* env, jthrowable exception) {
-    char* result = NULL;
-
-    jclass stringWriterClazz = (*env)->FindClass(env, "java/io/StringWriter");
-    if (stringWriterClazz != NULL) {
-        jmethodID stringWriterCtor = (*env)->GetMethodID(env, stringWriterClazz, "<init>", "()V");
-        jmethodID stringWriterToStringMethod = (*env)->GetMethodID(env, stringWriterClazz,
-                "toString", "()Ljava/lang/String;");
-
-        jclass printWriterClazz = (*env)->FindClass(env, "java/io/PrintWriter");
-        if (printWriterClazz != NULL) {
-            jmethodID printWriterCtor = (*env)->GetMethodID(env, printWriterClazz,
-                    "<init>", "(Ljava/io/Writer;)V");
-
-            jobject stringWriterObj = (*env)->NewObject(env, stringWriterClazz, stringWriterCtor);
-            if (stringWriterObj != NULL) {
-                jobject printWriterObj = (*env)->NewObject(env, printWriterClazz, printWriterCtor,
-                        stringWriterObj);
-                if (printWriterObj != NULL) {
-                    jclass exceptionClazz = (*env)->GetObjectClass(env, exception); // can't fail
-                    jmethodID printStackTraceMethod = (*env)->GetMethodID(
-                            env, exceptionClazz, "printStackTrace", "(Ljava/io/PrintWriter;)V");
-
-                    (*env)->CallVoidMethod(env, exception, printStackTraceMethod, printWriterObj);
-                    if (! (*env)->ExceptionCheck(env)) {
-                        jstring messageStr = (*env)->CallObjectMethod(
-                                env, stringWriterObj, stringWriterToStringMethod);
-                        if (messageStr != NULL) {
-                            const char* utfChars = (*env)->GetStringUTFChars(env, messageStr, NULL);
-                            if (utfChars != NULL) {
-                                result = strdup(utfChars);
-                                (*env)->ReleaseStringUTFChars(env, messageStr, utfChars);
-                            }
-                            (*env)->DeleteLocalRef(env, messageStr);
-                        }
-                    }
-                    (*env)->DeleteLocalRef(env, exceptionClazz);
-                    (*env)->DeleteLocalRef(env, printWriterObj);
-                }
-                (*env)->DeleteLocalRef(env, stringWriterObj);
-            }
-            (*env)->DeleteLocalRef(env, printWriterClazz);
-        }
-        (*env)->DeleteLocalRef(env, stringWriterClazz);
-    }
-
-    if (result == NULL) {
-        (*env)->ExceptionClear(env);
-        result = getExceptionSummary(env, exception);
-    }
-
-    return result;
-}
-
-/*
- * Throw an exception with the specified class and an optional message.
- *
- * If an exception is currently pending, we log a warning message and
- * clear it.
- *
- * Returns 0 if the specified exception was successfully thrown.  (Some
- * sort of exception will always be pending when this returns.)
- */
-int jniThrowException(JNIEnv* env, const char* className, const char* msg) {
-    jclass exceptionClass;
-
-    if ((*env)->ExceptionCheck(env)) {
-        /* TODO: consider creating the new exception with this as "cause" */
-        jthrowable exception = (*env)->ExceptionOccurred(env);
-        (*env)->ExceptionClear(env);
-
-        if (exception != NULL) {
-            char* text = getExceptionSummary(env, exception);
-            LOGW("Discarding pending exception (%s) to throw %s", text, className);
-            free(text);
-            (*env)->DeleteLocalRef(env, exception);
-        }
-    }
-
-    exceptionClass = (*env)->FindClass(env, className);
-    if (exceptionClass == NULL) {
-        LOGE("Unable to find exception class %s\n", className);
-        /* ClassNotFoundException now pending */
-        return -1;
-    }
-
-    int result = 0;
-    if ((*env)->ThrowNew(env, exceptionClass, msg) != JNI_OK) {
-        LOGE("Failed throwing '%s' '%s'\n", className, msg);
-        /* an exception, most likely OOM, will now be pending */
-        result = -1;
-    }
-
-    (*env)->DeleteLocalRef(env, exceptionClass);
-    return result;
-}
-
-int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, va_list args) {
-    char msgBuf[512];
-    vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
-    return jniThrowException(env, className, msgBuf);
-}
-
-/*
- * Throw a java.lang.NullPointerException, with an optional message.
- */
-int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
-    return jniThrowException(env, "java/lang/NullPointerException", msg);
-}
-
-/*
- * Throw a java.lang.RuntimeException, with an optional message.
- */
-int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
-    return jniThrowException(env, "java/lang/RuntimeException", msg);
-}
-
-/*
- * Throw a java.io.IOException, generating the message from errno.
- */
-int jniThrowIOException(JNIEnv* env, int errnum) {
-    char buffer[80];
-    const char* message = jniStrError(errnum, buffer, sizeof(buffer));
-    return jniThrowException(env, "java/io/IOException", message);
-}
-
-/*
- * Log an exception.
- * If exception is NULL, logs the current exception in the JNI environment, if any.
- */
-void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable exception) {
-    int currentException = 0;
-    if (exception == NULL) {
-        exception = (*env)->ExceptionOccurred(env);
-        if (exception == NULL) {
-            return;
-        }
-
-        (*env)->ExceptionClear(env);
-        currentException = 1;
-    }
-
-    char* buffer = printStackTrace(env, exception);
-    __android_log_write(priority, tag, buffer);
-    free(buffer);
-
-    if (currentException) {
-        (*env)->Throw(env, exception); // rethrow
-        (*env)->DeleteLocalRef(env, exception);
-    }
-}
-
-const char* jniStrError(int errnum, char* buf, size_t buflen) {
-    // note: glibc has a nonstandard strerror_r that returns char* rather
-    // than POSIX's int.
-    // char *strerror_r(int errnum, char *buf, size_t n);
-    char* ret = (char*) strerror_r(errnum, buf, buflen);
-    if (((int)ret) == 0) {
-        //POSIX strerror_r, success
-        return buf;
-    } else if (((int)ret) == -1) {
-        //POSIX strerror_r, failure
-        // (Strictly, POSIX only guarantees a value other than 0. The safest
-        // way to implement this function is to use C++ and overload on the
-        // type of strerror_r to accurately distinguish GNU from POSIX. But
-        // realistic implementations will always return -1.)
-        snprintf(buf, buflen, "errno %d", errnum);
-        return buf;
-    } else {
-        //glibc strerror_r returning a string
-        return ret;
-    }
-}
-
-static struct CachedFields {
-    jclass fileDescriptorClass;
-    jmethodID fileDescriptorCtor;
-    jfieldID descriptorField;
-} gCachedFields;
-
-int registerJniHelp(JNIEnv* env) {
-    gCachedFields.fileDescriptorClass =
-            (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/io/FileDescriptor"));
-    if (gCachedFields.fileDescriptorClass == NULL) {
-        return -1;
-    }
-
-    gCachedFields.fileDescriptorCtor =
-            (*env)->GetMethodID(env, gCachedFields.fileDescriptorClass, "<init>", "()V");
-    if (gCachedFields.fileDescriptorCtor == NULL) {
-        return -1;
-    }
-
-    gCachedFields.descriptorField =
-            (*env)->GetFieldID(env, gCachedFields.fileDescriptorClass, "descriptor", "I");
-    if (gCachedFields.descriptorField == NULL) {
-        return -1;
-    }
-
-    return 0;
-}
-
-/*
- * Create a java.io.FileDescriptor given an integer fd
- */
-jobject jniCreateFileDescriptor(JNIEnv* env, int fd) {
-    jobject fileDescriptor = (*env)->NewObject(env,
-            gCachedFields.fileDescriptorClass, gCachedFields.fileDescriptorCtor);
-    jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
-    return fileDescriptor;
-}
-
-/*
- * Get an int file descriptor from a java.io.FileDescriptor
- */
-int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
-    return (*env)->GetIntField(env, fileDescriptor, gCachedFields.descriptorField);
-}
-
-/*
- * Set the descriptor of a java.io.FileDescriptor
- */
-void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) {
-    (*env)->SetIntField(env, fileDescriptor, gCachedFields.descriptorField, value);
-}
-
-/*
- * DO NOT USE THIS FUNCTION
- *
- * Get a pointer to the elements of a non-movable array.
- *
- * The semantics are similar to GetDirectBufferAddress.  Specifically, the VM
- * guarantees that the array will not move, and the caller must ensure that
- * it does not continue to use the pointer after the object is collected.
- *
- * We currently use an illegal sequence that trips up CheckJNI when
- * the "forcecopy" mode is enabled.  We pass in a magic value to work
- * around the problem.
- *
- * Returns NULL if the array is movable.
- */
-jbyte* jniGetNonMovableArrayElements(JNIEnv* env, jarray arrayObj) {
-#define kNoCopyMagic 0xd5aab57f     /* also in CheckJni.c */
-
-    /*
-     * Normally the "isCopy" parameter is for a return value only, so the
-     * non-CheckJNI VM will ignore whatever we pass in.
-     */
-    uint32_t noCopy = kNoCopyMagic;
-    jbyte *addr = (*env)->GetByteArrayElements(env, arrayObj,
-            (jboolean*)&noCopy);
-
-    /*
-     * The non-CheckJNI implementation only cares about the array object,
-     * so we can replace the element pointer with the magic value.
-     */
-    (*env)->ReleaseByteArrayElements(env, arrayObj, (jbyte*) kNoCopyMagic, 0);
-    return addr;
-}
diff --git a/JNIHelp.cpp b/JNIHelp.cpp
new file mode 100644
index 0000000..79a4042
--- /dev/null
+++ b/JNIHelp.cpp
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "JNIHelp"
+
+#include "JNIHelp.h"
+#include "utils/Log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/**
+ * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
+ */
+template<typename T>
+class scoped_local_ref {
+public:
+    scoped_local_ref(C_JNIEnv* env, T localRef = NULL)
+    : mEnv(env), mLocalRef(localRef)
+    {
+    }
+
+    ~scoped_local_ref() {
+        reset();
+    }
+
+    void reset(T localRef = NULL) {
+        if (mLocalRef != NULL) {
+            (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef);
+            mLocalRef = localRef;
+        }
+    }
+
+    T get() const {
+        return mLocalRef;
+    }
+
+private:
+    C_JNIEnv* mEnv;
+    T mLocalRef;
+
+    // Disallow copy and assignment.
+    scoped_local_ref(const scoped_local_ref&);
+    void operator=(const scoped_local_ref&);
+};
+
+static jclass findClass(C_JNIEnv* env, const char* className) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+    return (*env)->FindClass(e, className);
+}
+
+extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
+    const JNINativeMethod* gMethods, int numMethods)
+{
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+    LOGV("Registering %s natives", className);
+
+    scoped_local_ref<jclass> c(env, findClass(env, className));
+    if (c.get() == NULL) {
+        LOGE("Native registration unable to find class '%s', aborting", className);
+        abort();
+    }
+
+    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
+        LOGE("RegisterNatives failed for '%s', aborting", className);
+        abort();
+    }
+
+    return 0;
+}
+
+/*
+ * Returns a human-readable summary of an exception object.  The buffer will
+ * be populated with the "binary" class name and, if present, the
+ * exception message.
+ */
+static char* getExceptionSummary0(C_JNIEnv* env, jthrowable exception) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+    /* get the name of the exception's class */
+    scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail
+    scoped_local_ref<jclass> classClass(env,
+            (*env)->GetObjectClass(e, exceptionClass.get())); // java.lang.Class, can't fail
+    jmethodID classGetNameMethod =
+            (*env)->GetMethodID(e, classClass.get(), "getName", "()Ljava/lang/String;");
+    scoped_local_ref<jstring> classNameStr(env,
+            (jstring) (*env)->CallObjectMethod(e, exceptionClass.get(), classGetNameMethod));
+    if (classNameStr.get() == NULL) {
+        return NULL;
+    }
+
+    /* get printable string */
+    const char* classNameChars = (*env)->GetStringUTFChars(e, classNameStr.get(), NULL);
+    if (classNameChars == NULL) {
+        return NULL;
+    }
+
+    /* if the exception has a detail message, get that */
+    jmethodID getMessage =
+            (*env)->GetMethodID(e, exceptionClass.get(), "getMessage", "()Ljava/lang/String;");
+    scoped_local_ref<jstring> messageStr(env,
+            (jstring) (*env)->CallObjectMethod(e, exception, getMessage));
+    if (messageStr.get() == NULL) {
+        return strdup(classNameChars);
+    }
+
+    char* result = NULL;
+    const char* messageChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL);
+    if (messageChars != NULL) {
+        asprintf(&result, "%s: %s", classNameChars, messageChars);
+        (*env)->ReleaseStringUTFChars(e, messageStr.get(), messageChars);
+    } else {
+        (*env)->ExceptionClear(e); // clear OOM
+        asprintf(&result, "%s: <error getting message>", classNameChars);
+    }
+
+    (*env)->ReleaseStringUTFChars(e, classNameStr.get(), classNameChars);
+    return result;
+}
+
+static char* getExceptionSummary(C_JNIEnv* env, jthrowable exception) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+    char* result = getExceptionSummary0(env, exception);
+    if (result == NULL) {
+        (*env)->ExceptionClear(e);
+        result = strdup("<error getting class name>");
+    }
+    return result;
+}
+
+/*
+ * Returns an exception (with stack trace) as a string.
+ */
+static char* getStackTrace(C_JNIEnv* env, jthrowable exception) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+    scoped_local_ref<jclass> stringWriterClass(env, findClass(env, "java/io/StringWriter"));
+    if (stringWriterClass.get() == NULL) {
+        return NULL;
+    }
+
+    jmethodID stringWriterCtor = (*env)->GetMethodID(e, stringWriterClass.get(), "<init>", "()V");
+    jmethodID stringWriterToStringMethod =
+            (*env)->GetMethodID(e, stringWriterClass.get(), "toString", "()Ljava/lang/String;");
+
+    scoped_local_ref<jclass> printWriterClass(env, findClass(env, "java/io/PrintWriter"));
+    if (printWriterClass.get() == NULL) {
+        return NULL;
+    }
+
+    jmethodID printWriterCtor =
+            (*env)->GetMethodID(e, printWriterClass.get(), "<init>", "(Ljava/io/Writer;)V");
+
+    scoped_local_ref<jobject> stringWriter(env,
+            (*env)->NewObject(e, stringWriterClass.get(), stringWriterCtor));
+    if (stringWriter.get() == NULL) {
+        return NULL;
+    }
+
+    jobject printWriter =
+            (*env)->NewObject(e, printWriterClass.get(), printWriterCtor, stringWriter.get());
+    if (printWriter == NULL) {
+        return NULL;
+    }
+
+    scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail
+    jmethodID printStackTraceMethod =
+            (*env)->GetMethodID(e, exceptionClass.get(), "printStackTrace", "(Ljava/io/PrintWriter;)V");
+    (*env)->CallVoidMethod(e, exception, printStackTraceMethod, printWriter);
+
+    if ((*env)->ExceptionCheck(e)) {
+        return NULL;
+    }
+
+    scoped_local_ref<jstring> messageStr(env,
+            (jstring) (*env)->CallObjectMethod(e, stringWriter.get(), stringWriterToStringMethod));
+    if (messageStr.get() == NULL) {
+        return NULL;
+    }
+
+    const char* utfChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL);
+    if (utfChars == NULL) {
+        return NULL;
+    }
+
+    char* result = strdup(utfChars);
+    (*env)->ReleaseStringUTFChars(e, messageStr.get(), utfChars);
+    return result;
+}
+
+extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+    if ((*env)->ExceptionCheck(e)) {
+        /* TODO: consider creating the new exception with this as "cause" */
+        scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));
+        (*env)->ExceptionClear(e);
+
+        if (exception.get() != NULL) {
+            char* text = getExceptionSummary(env, exception.get());
+            LOGW("Discarding pending exception (%s) to throw %s", text, className);
+            free(text);
+        }
+    }
+
+    scoped_local_ref<jclass> exceptionClass(env, findClass(env, className));
+    if (exceptionClass.get() == NULL) {
+        LOGE("Unable to find exception class %s", className);
+        /* ClassNotFoundException now pending */
+        return -1;
+    }
+
+    if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) {
+        LOGE("Failed throwing '%s' '%s'", className, msg);
+        /* an exception, most likely OOM, will now be pending */
+        return -1;
+    }
+
+    return 0;
+}
+
+int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args) {
+    char msgBuf[512];
+    vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
+    return jniThrowException(env, className, msgBuf);
+}
+
+int jniThrowNullPointerException(C_JNIEnv* env, const char* msg) {
+    return jniThrowException(env, "java/lang/NullPointerException", msg);
+}
+
+int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) {
+    return jniThrowException(env, "java/lang/RuntimeException", msg);
+}
+
+int jniThrowIOException(C_JNIEnv* env, int errnum) {
+    char buffer[80];
+    const char* message = jniStrError(errnum, buffer, sizeof(buffer));
+    return jniThrowException(env, "java/io/IOException", message);
+}
+
+void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+    scoped_local_ref<jthrowable> currentException(env);
+    if (exception == NULL) {
+        exception = (*env)->ExceptionOccurred(e);
+        if (exception == NULL) {
+            return;
+        }
+
+        (*env)->ExceptionClear(e);
+        currentException.reset(exception);
+    }
+
+    char* buffer = getStackTrace(env, exception);
+    if (buffer == NULL) {
+        (*env)->ExceptionClear(e);
+        buffer = getExceptionSummary(env, exception);
+    }
+
+    __android_log_write(priority, tag, buffer);
+    free(buffer);
+
+    if (currentException.get() != NULL) {
+        (*env)->Throw(e, exception); // rethrow
+    }
+}
+
+const char* jniStrError(int errnum, char* buf, size_t buflen) {
+    // Note: glibc has a nonstandard strerror_r that returns char* rather than POSIX's int.
+    // char *strerror_r(int errnum, char *buf, size_t n);
+    char* ret = (char*) strerror_r(errnum, buf, buflen);
+    if (((int)ret) == 0) {
+        // POSIX strerror_r, success
+        return buf;
+    } else if (((int)ret) == -1) {
+        // POSIX strerror_r, failure
+        // (Strictly, POSIX only guarantees a value other than 0. The safest
+        // way to implement this function is to use C++ and overload on the
+        // type of strerror_r to accurately distinguish GNU from POSIX. But
+        // realistic implementations will always return -1.)
+        snprintf(buf, buflen, "errno %d", errnum);
+        return buf;
+    } else {
+        // glibc strerror_r returning a string
+        return ret;
+    }
+}
+
+static struct CachedFields {
+    jclass fileDescriptorClass;
+    jmethodID fileDescriptorCtor;
+    jfieldID descriptorField;
+} gCachedFields;
+
+int registerJniHelp(JNIEnv* env) {
+    gCachedFields.fileDescriptorClass =
+            reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("java/io/FileDescriptor")));
+    if (gCachedFields.fileDescriptorClass == NULL) {
+        return -1;
+    }
+
+    gCachedFields.fileDescriptorCtor =
+            env->GetMethodID(gCachedFields.fileDescriptorClass, "<init>", "()V");
+    if (gCachedFields.fileDescriptorCtor == NULL) {
+        return -1;
+    }
+
+    gCachedFields.descriptorField =
+            env->GetFieldID(gCachedFields.fileDescriptorClass, "descriptor", "I");
+    if (gCachedFields.descriptorField == NULL) {
+        return -1;
+    }
+
+    return 0;
+}
+
+jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+    jobject fileDescriptor = (*env)->NewObject(e,
+            gCachedFields.fileDescriptorClass, gCachedFields.fileDescriptorCtor);
+    jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
+    return fileDescriptor;
+}
+
+int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+    return (*env)->GetIntField(e, fileDescriptor, gCachedFields.descriptorField);
+}
+
+void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+    (*env)->SetIntField(e, fileDescriptor, gCachedFields.descriptorField, value);
+}
+
+/*
+ * DO NOT USE THIS FUNCTION
+ *
+ * Get a pointer to the elements of a non-movable array.
+ *
+ * The semantics are similar to GetDirectBufferAddress.  Specifically, the VM
+ * guarantees that the array will not move, and the caller must ensure that
+ * it does not continue to use the pointer after the object is collected.
+ *
+ * We currently use an illegal sequence that trips up CheckJNI when
+ * the "forcecopy" mode is enabled.  We pass in a magic value to work
+ * around the problem.
+ *
+ * Returns NULL if the array is movable.
+ */
+#define kNoCopyMagic 0xd5aab57f     /* also in CheckJni.c */
+extern "C" jbyte* jniGetNonMovableArrayElements(C_JNIEnv* env, jarray arrayObj) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+    jbyteArray byteArray = reinterpret_cast<jbyteArray>(arrayObj);
+
+    /*
+     * Normally the "isCopy" parameter is for a return value only, so the
+     * non-CheckJNI VM will ignore whatever we pass in.
+     */
+    uint32_t noCopy = kNoCopyMagic;
+    jbyte* result = (*env)->GetByteArrayElements(e, byteArray, reinterpret_cast<jboolean*>(&noCopy));
+
+    /*
+     * The non-CheckJNI implementation only cares about the array object,
+     * so we can replace the element pointer with the magic value.
+     */
+    (*env)->ReleaseByteArrayElements(e, byteArray, reinterpret_cast<jbyte*>(kNoCopyMagic), 0);
+    return result;
+}
diff --git a/Register.c b/Register.cpp
similarity index 92%
rename from Register.c
rename to Register.cpp
index 5d7d690..b6b1b1f 100644
--- a/Register.c
+++ b/Register.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-/*
- * JNI helper functions.
- */
-
 #include "jni.h"
 
 extern int registerCoreLibrariesJni(JNIEnv* env);
@@ -26,8 +22,7 @@
 /*
  * Register all methods for system classes.
  */
-int jniRegisterSystemMethods(JNIEnv* env)
-{
+int jniRegisterSystemMethods(JNIEnv* env) {
     // JniHelp depends on core library classes such as java.io.FileDescriptor.
     return registerCoreLibrariesJni(env) != -1 && registerJniHelp(env) != -1;
 }
diff --git a/include/nativehelper/JNIHelp.h b/include/nativehelper/JNIHelp.h
index 1b5ff0c..ff9cf86 100644
--- a/include/nativehelper/JNIHelp.h
+++ b/include/nativehelper/JNIHelp.h
@@ -37,17 +37,21 @@
 
 /*
  * Register one or more native methods with a particular class.
+ * "className" looks like "java/lang/String".
  */
-int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
-    const JNINativeMethod* gMethods, int numMethods);
+int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);
 
 /*
  * Throw an exception with the specified class and an optional message.
+ *
  * The "className" argument will be passed directly to FindClass, which
  * takes strings with slashes (e.g. "java/lang/Object").
  *
+ * If an exception is currently pending, we log a warning message and
+ * clear it.
+ *
  * Returns 0 on success, nonzero if something failed (e.g. the exception
- * class couldn't be found).
+ * class couldn't be found, so *an* exception will still be pending).
  *
  * Currently aborts the VM if it can't throw the exception.
  */
@@ -77,17 +81,17 @@
 const char* jniStrError(int errnum, char* buf, size_t buflen);
 
 /*
- * Create a java.io.FileDescriptor given an integer fd
+ * Returns a new java.io.FileDescriptor for the given int fd.
  */
 jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd);
 
 /*
- * Get an int file descriptor from a java.io.FileDescriptor
+ * Returns the int fd from a java.io.FileDescriptor.
  */
 int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor);
 
 /*
- * Set an int file descriptor to a java.io.FileDescriptor
+ * Sets the int fd in a java.io.FileDescriptor.
  */
 void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value);
 
@@ -107,62 +111,52 @@
  * inlines these, even on non-optimized builds.
  */
 #if defined(__cplusplus)
-inline int jniRegisterNativeMethods(JNIEnv* env, const char* className,
-    const JNINativeMethod* gMethods, int numMethods)
-{
-    return jniRegisterNativeMethods(&env->functions, className, gMethods,
-        numMethods);
+inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
+    return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods);
 }
-inline int jniThrowException(JNIEnv* env, const char* className,
-    const char* msg)
-{
+
+inline int jniThrowException(JNIEnv* env, const char* className, const char* msg) {
     return jniThrowException(&env->functions, className, msg);
 }
 
-extern "C" int jniThrowExceptionFmt(C_JNIEnv* env, const char* className,
-        const char* fmt, va_list args);
+extern "C" int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args);
 
 /*
  * Equivalent to jniThrowException but with a printf-like format string and
  * variable-length argument list. This is only available in C++.
  */
-inline int jniThrowExceptionFmt(JNIEnv* env, const char* className,
-        const char* fmt, ...)
-{
+inline int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, ...) {
     va_list args;
     va_start(args, fmt);
     return jniThrowExceptionFmt(&env->functions, className, fmt, args);
     va_end(args);
 }
 
-inline int jniThrowNullPointerException(JNIEnv* env, const char* msg)
-{
+inline int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
     return jniThrowNullPointerException(&env->functions, msg);
 }
-inline int jniThrowRuntimeException(JNIEnv* env, const char* msg)
-{
+
+inline int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
     return jniThrowRuntimeException(&env->functions, msg);
 }
-inline int jniThrowIOException(JNIEnv* env, int errnum)
-{
+
+inline int jniThrowIOException(JNIEnv* env, int errnum) {
     return jniThrowIOException(&env->functions, errnum);
 }
-inline jobject jniCreateFileDescriptor(JNIEnv* env, int fd)
-{
+
+inline jobject jniCreateFileDescriptor(JNIEnv* env, int fd) {
     return jniCreateFileDescriptor(&env->functions, fd);
 }
-inline int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor)
-{
+
+inline int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
     return jniGetFDFromFileDescriptor(&env->functions, fileDescriptor);
 }
-inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor,
-    int value)
-{
+
+inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) {
     jniSetFileDescriptorOfFD(&env->functions, fileDescriptor, value);
 }
-inline void jniLogException(JNIEnv* env, int priority, const char* tag,
-        jthrowable exception = NULL)
-{
+
+inline void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable exception = NULL) {
     jniLogException(&env->functions, priority, tag, exception);
 }
 #endif