2)Native程序中的函数可以调用Java层的函数,也就是说C/C++程序可以调用Java函数。
1、JNI本地调用:(以例子介绍)
java:(HelloWorld.java)
class HelloWorld { private native void print(); public static void main(String[] args){ new HelloWorld().print(); } static { System.loadLibrary("HelloWorld"); } }
******* /* * Class: HelloWorld * Method: print * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); *******
第二个参数的意义取决于该方法是静态还是实例方法,当本地方法作为一个实例方法时,第二个参数相当于对象本身,即this,当本地方法作为一个静态方法时,指向所在类。
C:(HelloWorld.c)
#include <jni.h> #include <stdio.h> #include "HelloWorld.h" JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj) { printf("Hello World!\n"); return; }此时.c文件中的函数要跟.H文件中一样,这样在JAVA调用时才能根据库找到对应的JNI函数,进而调用执行该函数。
package com.example.hellojni; import android.app.Activity; import android.widget.TextView; import android.os.Bundle; public class HelloJni extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); tv.setText( stringFromJNI() ); setContentView(tv); } public native String stringFromJNI(); static { System.loadLibrary("hello-jni"); } }此时会在工程目录中/bin/classes/com/example/hellojni中会自动生成.class文件,
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY)代码如上,android.mk是android提供的一种makefile文件,用来指定编译生成的so库名、引用的头文件等。LOCAL_PATH 用于给出当前文件的路径;LOCAL_MODULE是模块的名字,必须唯一切不能包含空格;LOCAL_SRC_FILES指定要编译的源文件列表;LOCAL_SHARED_LIBRARIES则表示模块在运行时要以来的共享库,在链接时需要。
#include <string.h> #include <jni.h> jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI !"); }这个文件中的函数名必须和生成的.h文件中的函数名一致,否则可能会无法找到,也就是Java、对应的包名类名和函数名组成了新函数名。
#include <string.h> #include <jni.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> jstring native_hello(JNIEnv* env, jobject thiz) { return (*env)->NewStringUTF(env, "动态注册JNI"); } /* *方法对应表 */ static JNINativeMethod gMethods[] = { {"stringFromJNI","()Ljava/lang/String;", (void*)native_hello}, }; /* *为某一个类注册本地方法 */ static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods){ jclass clazz; /* env指向一个JNIEnv结构体,classname对应为Java类名,由于JNINativeMethod中 使用的函数不是全路径名,所以要指明是哪个类。 */ clazz = (*env)->FindClass(env, className); if(clazz == NULL){ return JNI_FALSE; } //实际上是调用JNIEnv的RegisterNatives函数完成注册 if((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0){ return JNI_FALSE; } return JNI_TRUE; } /* *为所有类注册本地方法 */ static int registerNatives(JNIEnv* env){ const char* kClassName = "com/example/hellojni/HelloJni"; return registerNativeMethods(env, kClassName, gMethods, sizeof(gMethods)/sizeof(gMethods[0])); } /* *System.loadLibrary("lib")时调用,如果成功返回JNI版本,失败返回-1 *该函书的第一个参数类型为JavaVM,是虚拟机在JNI层的代表 *每个Java进程只有一个这样的JavaVM */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; jint result = -1; if((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4)!= JNI_OK){ return -1; } assert(env != NULL); //动态注册JNI函数 if(!registerNatives(env)){ return -1; } result = JNI_VERSION_1_4; return result; }在jni.h中定义了JNINativeMethod结构体,利用这个结构体就可以把java中native函数和此处的函数名对应。那为什么需要这个签名信息呢?因为Java支持函数重载,也就是说,可以定义同名但不同参数的函数。但仅仅根据函数名是没法找到具体函数的,JNI技术中就将参数类型和返回值类型的组合成为了一个函数的签名信息,有了签名信息和函数名,就能顺利地找到Java中的函数了。
typedef struct { //java中native函数的名字,不用带包路径 char *name; //Java函数的签名信息。 char *signature; //JNI层对应的函数指针 void *fnPtr; } JNINativeMethod;此结构体中的signature可以通过命令来取得,在class文件所在的目录执行javap -p-s HelloWorld。