最近在读<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")函数装载该库.