static void CL_CALLBACK nativeKernelCallback(void *args) {
    JNIEnv *env = attachCurrentThread();
    jobject user_func = (jobject)(intptr_t)*(jlong *)args;
    jsize num_mem_objects = *(jsize *)((char *)args + 8);
    jobjectArray memobjs = NULL;
    jobject buffer;
    jsize i;

	if ( env != NULL && !(*env)->ExceptionOccurred(env) && nativeKernelCallbackJ != NULL ) {
        if ( num_mem_objects > 0 ) {
            memobjs = (*env)->NewObjectArray(env, num_mem_objects, (*env)->FindClass(env, "java/nio/ByteBuffer"), NULL);
            for ( i = 0; i < num_mem_objects; i++ ) {
                buffer = (*env)->NewDirectByteBuffer(env,
                    // Pointer to cl_mem buffer
                    (void *)((char *)args + (12 + 4 + (i * (4 + sizeof(void *))))),
                    // cl_mem buffer size
                    *((jint *)((char *)args + (12 + (i * (4 + sizeof(void *))))))
                );
                (*env)->SetObjectArrayElement(env, memobjs, i, buffer);
            }
        }

        (*env)->CallVoidMethod(env, user_func, nativeKernelCallbackJ, memobjs);

        if ( num_mem_objects > 0 )
            (*env)->DeleteLocalRef(env, memobjs);
        (*env)->DeleteGlobalRef(env, user_func);
    }

    detachCurrentThread();
}
static void CL_CALLBACK CLContextCallbackFunction(
	const char *errinfo,
	const void *private_info,
	size_t cb,
	void *user_data
) {
	jobject callback;

	JNIEnv *env = getThreadEnv();
	jboolean async = env == NULL;
	if ( async ) {
        env = attachCurrentThread();
        if ( env == NULL )
            return;
	}

	// user_data is a weak global reference
	callback = (*env)->NewLocalRef(env, (jweak)user_data);
	if ( callback != NULL ) {
	    (*env)->CallVoidMethod(env, callback, CLContextCallbackMethod,
	        (jlong)(intptr_t)errinfo,
	        (jlong)(intptr_t)private_info,
	        (jlong)cb
	    );
	    (*env)->DeleteLocalRef(env, callback);
    }

	if ( async )
        detachCurrentThread();
}
static void CL_CALLBACK printfCallback(cl_context context, cl_uint printf_data_len, char *printf_data_ptr, void *user_data) {
    JNIEnv *env = attachCurrentThread();

	if ( env != NULL && !(*env)->ExceptionOccurred(env) && printfCallbackJ != NULL ) {
        (*env)->CallVoidMethod(env, (jobject)user_data, printfCallbackJ,
            NewStringNativeWithLength(env, printf_data_ptr, printf_data_len)
        );
    }

    detachCurrentThread();
}
static void CL_CALLBACK memObjectDestructorCallback(cl_mem memobj, void *user_data) {
    JNIEnv *env = attachCurrentThread();

	if ( env != NULL && !(*env)->ExceptionOccurred(env) && memObjectDestructorCallbackJ != NULL ) {
        (*env)->CallVoidMethod(env, (jobject)user_data, memObjectDestructorCallbackJ,
            (jlong)(intptr_t)memobj
        );
        (*env)->DeleteGlobalRef(env, (jobject)user_data);
    }

    detachCurrentThread();
}
static void CL_CALLBACK programCallback(cl_program program, void *user_data) {
    JNIEnv *env = attachCurrentThread();

	if ( env != NULL && !(*env)->ExceptionOccurred(env) && programCallbackJ != NULL ) {
        (*env)->CallVoidMethod(env, (jobject)user_data, programCallbackJ,
            (jlong)(intptr_t)program
        );
        (*env)->DeleteGlobalRef(env, (jobject)user_data);
    }

    detachCurrentThread();
}
static void CL_CALLBACK eventCallback(cl_event event, cl_int event_command_exec_status, void *user_data) {
    JNIEnv *env = attachCurrentThread();

	if ( env != NULL && !(*env)->ExceptionOccurred(env) && eventCallbackJ != NULL ) {
        (*env)->CallVoidMethod(env, (jobject)user_data, eventCallbackJ,
            (jlong)(intptr_t)event,
            event_command_exec_status
        );
        (*env)->DeleteGlobalRef(env, (jobject)user_data);
    }

    detachCurrentThread();
}
static void APIENTRY debugOutputCallbackAMD(GLuint id, GLenum category, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam) {
    JNIEnv *env = attachCurrentThread();

    if ( env != NULL && !(*env)->ExceptionOccurred(env) && debugOutputCallbackAMDJ != NULL ) {
        (*env)->CallVoidMethod(env, (jobject)userParam, debugOutputCallbackAMDJ,
                               (jint)id,
                               (jint)category,
                               (jint)severity,
                               NewStringNativeWithLength(env, message, length)
                              );
    }

    detachCurrentThread();
}
static void CL_CALLBACK contextCallback(const char *errinfo, const void *private_info, size_t cb, void *user_data) {
    JNIEnv *env = attachCurrentThread();
    jobject private_info_buffer = NULL;

	if ( env != NULL && !(*env)->ExceptionOccurred(env) && contextCallbackJ != NULL ) {
        if ( private_info != NULL )
            private_info_buffer = NewReadOnlyDirectByteBuffer(env, private_info, cb);

        (*env)->CallVoidMethod(env, (jobject)user_data, contextCallbackJ,
            NewStringNativeWithLength(env, errinfo, (jsize)strlen(errinfo)),
            private_info_buffer
        );
    }

    detachCurrentThread();
}
static void APIENTRY DEBUGPROCAMDFunction(GLuint id, GLenum category, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam) {
	JNIEnv *env = (getThreadEnv());
	jboolean async = (env == NULL);
	if ( async ) {
        env = attachCurrentThread();
        if ( env == NULL )
            return;
	}

    (*env)->CallVoidMethod(env, (jobject)userParam, DEBUGPROCAMDMethod,
        (jint)id,
        (jint)category,
        (jint)severity,
        (jint)length,
        (jlong)(intptr_t)message
    );

	if ( async )
        detachCurrentThread();
}