最近在读<Android 内核剖析>,mark下

Java 访问C

   Java中可以定义某个函数为native类型,对于native函数,只需要声明即可,因为该函数的实现native,即由相应的C去实现,Java编译器遇到native函数是,不会关心该该函数的具体实现,因此,编译上不会出错.

  程序运行时,在调用native方法之前,程序员必须吧C所生成的动态库装载进来,否则程序会因为找不到相应的native方法而出错.

  当调用native函数是,Java会自动产生一个对应的C中的函数名称,因为Java中声明的函数名称和C中实现的函数名称是不同的,其关系为,后者等于包名加前者的名称,并且中间以下划线分隔.比如 Framework中AssertManager类中声明了以下方法:


private native final void init();

该方法在C中对应的是:

static void android_content_AssertManager_init( JNIEnv* env, jobject clazz)

这种映射关系并不是Java编译器内涵的,程序员完全可以改变,但这是一种编程规范.事实上,当Java调用native是,编译器会向native 引擎传递调用者的包名,以及函数名称,还有参数类型,.

  在产生的C函数中,会包含至少两个参数,前者是JNIEnv对象,该对象是一个Java虚拟机(JVM)所运行的环境,相当于JVM的管家,通过他可以访问JVM内部的各种对象,

第二个参数jobject 是调用该函数的对象,本例中指的就是AssertManager对象.


在程序设计时,如果定义好了Java代码,如何实现响应的native C代码呢?

你可能会想: "那就按照这种转换关系,手工编写响应的C代码,如果编译成动态库,并在Java代码执行时加载该库就可以了." 没错,是这样的,为了辅助你这样做,Java还提供了一个javah工具,该工具可以从一个java文件自动生成响应的头文件,剩下的就是你根据这些头文件在实现具体的内部代码即可.


package com.haiii.android.client;

public class Foo {
	native void foo1();
	
	native void foo2(int a, String b);
	
}



首先要把代码编译成.class字节码文件,如果是Eclipse编译,需要切换到bin目录下,

使用 javah命令:

javah -d ~/Desktop -jni com.haiii.android.client.Foo

-d: 指定输出路径,并且-d必须在-jni之前.

-jni:产生jni头文件.后面类型是Class文件所在的路径.也就是说,当前路径必须在该Class包名的根目录,生成的头文件com_haiii_android_client_Foo.h

内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_haiii_android_client_Foo */

#ifndef _Included_com_haiii_android_client_Foo
#define _Included_com_haiii_android_client_Foo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_haiii_android_client_Foo
 * Method:    foo1
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_haiii_android_client_Foo_foo1
  (JNIEnv *, jobject);

/*
 * Class:     com_haiii_android_client_Foo
 * Method:    foo2
 * Signature: (ILjava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_haiii_android_client_Foo_foo2
  (JNIEnv *, jobject, jint, jstring);

#ifdef __cplusplus
}
#endif
#endif



下次在讲如何吧C代码编译成动态库.产生动态库以后,在Java代码调用native函数前,需要使用System.loadLibrary("lib_name")函数装载该库.