参考:《Java核心技术 卷II:高级特性》第12章 本地方法
########################################################################
本地方法常常需要从传递给它的对象那里得到某种服务。本文首先使用非静态方法进行操作,然后介绍静态方法
Printf3Test.java
/**
* @time 15-11-12
* @author zj
*/
import java.io.*;
class Printf3Test {
public static void main(String[] args) {
double price = 44.95;
double tax = 7.75;
double amountDue = price * (1 + tax / 100);
PrintWriter out = new PrintWriter(System.out);
Printf3.fprint(out, "Amount due = %8.2f\n", amountDue);
out.flush();
}
}
Printf3.java
/**
* @time 15-11-12
* @author zj
*/
import java.io.*;
class Printf3 {
public static native void fprint(PrintWriter out, String format, double x);
static {
System.loadLibrary("Printf3");
}
}
Printf3.c
/**
* @time 15-11-12
* @author zj
*/
#include "Printf3.h"
#include <string.h>
#include <stdlib.h>
#include <float.h>
/**
@param format a string containing a printf specifier
(such as "%8.2f"). Substrings "%%" are skipped.
@return a pointer to the format specifier (skipping the '%')
or NULL if there wasn't a unique format specifier
*/
char* find_format(const char format[]) {
char *p;
char *q;
p=strchr(format, '%');
while (p != NULL && *(p+1) == '%') /* skip %% */
p = strchr(p+2, '%');
if (p == NULL)
return NULL;
/* now check that % is unique */
p++;
q = strchr(p, '%');
while (q != NULL && *(q+1) == '%') /* skip %% */
q = strchr(q+2, '%');
if (q != NULL)
return NULL; /* not unique */
q = p + strspn(p, "-0+#"); /* skip past flags */
q += strspn(q, "0123456789"); /* skip past field width */
if (*q == '.') {
q++;
q += strspn(q, "0123456789");
}
/* skip past precision */
if (strchr("eEfFgG", *q) == NULL)
return NULL;
/* not a floating-point format */
return p;
}
JNIEXPORT void JNICALL Java_Printf3_fprint
(JNIEnv *env, jclass cl, jobject out, jstring format, jdouble x) {
const char *cformat;
char *fmt;
jstring str;
jclass class_PrintWriter;
jmethodID id_print;
cformat = (*env)->GetStringUTFChars(env, format, NULL);
fmt = find_format(cformat);
if (fmt == NULL)
str = format;
else {
char *cstr;
int width = atoi(fmt);
if (width == 0)
width = DBL_DIG + 10;
cstr = (char*)malloc(strlen(cformat)+width);
sprintf(cstr, cformat, x);
str = (*env)->NewStringUTF(env, cstr);
free(cstr);
}
(*env)->ReleaseStringUTFChars(env, format, cformat);
/* now call ps.print(str) */
/* get the class */
class_PrintWriter = (*env)->GetObjectClass(env, out);
/* get the method ID */
id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(Ljava/lang/String;)V");
/* call the method */
(*env)->CallVoidMethod(env, out, id_print, str);
}
详细讲解:
先增强Printf类,给它增加一个与C函数fprintf类似的方法。也就是说,它能够在任意PrintWriter对象上打印一个字符串。
下面是用Java编写的该方法的定义:
class Printf3 {
.......
public native static void fprint(PrintWriter out, String s, double x);
.......
}
首先把要打印的字符串编成一个String对象str,就像我们在sprint方法中已经实现的那样。然后,从实现本地方法的C函数调用PrintWriter类的print方法。
使用如下函数调用,就可以从C调用任何Java方法:
(*env)->CallXxxMethod(env, implicit parameter, methodID, explicit parameters)
根据方法的返回类型,用Void , Int , Object等来替换Xxx。就像你需要一个fieldID来访问某个对象的一个域,需要一个方法的ID来调用方法。你可以通过调用JNI函数GetMethodID,并且提供该类 , 方法的名字和方法签名来获得方法ID。
PrintWriter类有几个名为print的重载方法。基于这个原因,必须提供一个字符串,描述你想要用的特定函数的参数和返回值。例如,如果想要用
void print(java.lang.String)
必须把签名“编”为字符串"(Ljava/lang/String;)V"
下面是进行方法调用的完整步骤:
1.获取隐式参数的类。
2.获取方法ID。
3.进行调用。
/* get the class */
class_PrintWriter = (*env)->GetObjectClass(env, out);
/* get the method ID */
id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(Ljava/lang/String;)V");
/* call the method */
(*env)->CallVoidMethod(env, out, id_print, str);
note:数值型的方法ID和域ID在概念上和反射API中的Method和Field对象相似。可以使用以下函数在两者间进行转换:
jobject ToReflectedMethod(JNIEnv *env, jclass class, jmethodID methodID);
//returns Method object
methodID FromReflectedMethodID(JNIEnv *env, jobject method);
jobject ToReflectedField(JNIEnv *env, jclass class, jfieldID fieldID);
//returns Field object
fieldID FromReflectedField(JNIEnv *env, jobject field)
#############################################################################
静态方法
从本地方法调用静态方法与调用非静态方法类似。两者的差别是:
1.要用GetStaticMethodID和CallStaticXxxMethod函数。
2.当调用方法时,要提供类对象,而不是隐含的参数对象。
例如,从本地方法调用以下静态方法:
System.getProperty("java.class.path")
这个调用的返回值是给出了当前类路径的字符串。
首先,必须找到要用的类。因为没有System类的对象可供使用,故使用FindClass而非GetObjectClass:
jclass class_System = (*env)->FindClass(env, "java/lang/System");
接着,需要静态getProperty方法的ID。该方法的编码签名是:
"(Ljava/lang/String;)Ljava/lang/String;"
既然参数和返回值都是字符串。因此,获取方法ID为:
jmethodID id_getProperty = (*env)->GetStaticMethodID(env, class_System, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
最后,进行调用。注意,类对象被传递给了CallStaticObjectMethod函数:
jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty, (*env)->NewStringUTF(env, "java.class.path"));
该方法的返回值是jobject类型的。如果想要把它当作字符串操作,必须把它转型为jstring。
jstring str_ret = (jstring)obj_ret;
注意:在C中,jstring和jclass类型同后面将要介绍的数组类型一样,都是与jobject等价的类型。因此,在C语言中,前面例子中的转型并不是严格必需的。当时在C++中,这些类型被定义为对拥有正确继承层次关系的"哑类"的指针。例如,将一个jstring不经过转型便赋给jobject在C++中是合法的,当时将jobject赋给jstring必须先转型。
#################################################################
构造器
本地方法可以通过调用构造器来创建新的Java对象。可以调用NewObject函数来调用构造器。
jobject obj_new = (*env)->NewObject(env, methodID, construction parameters);
可以通过指定方法名为"<init>",并指定构造器(返回值为void)的编码签名,从GetMethodID函数中获取该调用必须的方法ID。例如,使用本地方法创建FileOutputStream对象:
const char[] fileName = "...";
jstring str_fileName = (*env)->NewStringUTF(env, fileName);
jclass class_FileOuputStream = (*env)->FindClass(env, "java/io/FileOutputStream");
jmethodID id_FieldOutputStream = (*env)->GetMethodID(env, class_FileOutputStream, "<init>", "(Ljava/lang/String;)V");
jobject obj_stream = (*env)->NewObject(env, class_FileOutputStream, id_FileOutputStream, str_fileName);
注意,构造器的签名接受一个java.lang.String类型的参数,返回类型为void。
###################################################################3
执行Java方法详解:
jmethodID GetMethodID(JNIEnv *env, jclass cl, const char name[], const char methodSignature[])
返回类中某个方法的标识符
Xxx CallXxxMethod(JNIEnv *env, jobject obj, jmethodID id, args)
调用一个方法。返回类型Xxx是Object , Boolean , Byte , Char , Short , Int , Long , Float 或 Double之一。函数有一个可变的参数个数,只要把方法参数附加到方法ID之后即可。
jmethodID GetStaticMethodID(JNIEnv *env, jclass cl, const char name[], const char methodsignature[])
返回类的某个静态方法的标识符
Xxx CallStaticXxxMethod(JNIEnv *env, jclass cl, jmethodID id, args)
调用一个静态方法。返回类型Xxx是Object , Boolean , Byte , Char , Short , Int , Long , Float 或 Double之一。函数有一个可变的参数个数,只要吧方法参数附加到方法ID之后即可。
jobject NewObject(JNIEnv *env, jclass cl, jmethodID id, args)
调用构造器。函数ID从带有函数名为"<init>"和返回类型为void的GetMethodID获取。函数有一个可变的参数个数,只要吧方法参数附加到方法ID之后即可。