JNI_编程技术__网文整理(中)
15.2.2.3 传递字符串
到目前为止,我们还没有实现Java程序向C程序传递参数,或者C程序向Java程序传递参数。本例程将由Java程序向C程序传入一个字符串,C程序对该字符串转成大写形式后回传给Java程序。
Java源程序如下。
代码清单15-6 在Linux平台上调用C函数的例程——Sample1
public class Sample1
{
public native String stringMethod(String text);
public static void main(String[] args)
{
System.loadLibrary("Sample1");
Sample1 sample = new Sample1();
String text = sample.stringMethod("Thinking In Java");
System.out.println("stringMethod: " + text);
}
}
Sample1.java以“Thinking In Java”为参数调用libSample1.so中的函数stringMethod(),在得到返回的字符串后打印输出。
Sample1.c的源程序如下。
代码清单15-7 在Linux平台上调用C函数的例程——Sample1.c
#include <Sample1.h>
#include <string.h>
JNIEXPORT jstring JNICALL Java_Sample1_stringMethod(JNIEnv *env, jobject obj, jstring string)
{
const char *str = (*env)->GetStringUTFChars(env, string, 0);
char cap[128];
strcpy(cap, str);
(*env)->ReleaseStringUTFChars(env, string, str);
int i=0;
for(i=0;i<strlen(cap);i++)
*(cap+i)=(char)toupper(*(cap+i));
return (*env)->NewStringUTF(env, cap);
}
首先请注意函数头部分,函数接收一个jstring类 型的输入参数,并输出一个jstring类型的参数。jstring是jni.h中定义的数据类型,是JNI框架内特有的字符串类型,因为jni.h在 Sample1.h中被引入,因此在Sample1.c中无须再次引入。
程序的第4行是从JNI调用上下文中获取UTF编码的输入字符,将其放在指针str所指向 的一段内存中。第9行是释放这段内存。第13行是将经过大写转换的字符串予以返回,这一句使用了NewStringUTF()函数,将C语言的字符串指针 转换为JNI的jstring类型。JNIEnv也是在jni.h中定义的,代表JNI调用的上下文,GetStringUTFChars()、ReleaseStringUTFChars()和NewStringUTF()均是JNIEnv的函数。
15.2.2.4 传递整型数组
本节例程将首次尝试在JNI框架内启用数组:C程序向Java程序返回一个定长的整型数组成的数组,Java程序将该数组打印输出。
Java程序的源代码如下。
代码清单15-8 在Linux平台上调用C函数的例程——Sample2
public class Sample2
{
public native int[] intMethod();
public static void main(String[] args)
{
System.loadLibrary("Sample2");
Sample2 sample=new Sample2();
int[] nums=sample.intMethod();
for(int i=0;i<nums.length;i++)
System.out.println(nums[i]);
}
}
Sample2.java调用libSample2.so中的函数intMethod()。Sample2.c的源代码如下。
代码清单15-9 在Linux平台上调用C函数的例程——Sample2.c
#include <Sample2.h>
JNIEXPORT jintArray JNICALL Java_Sample2_intMethod(JNIEnv *env, jobject obj)
{
int i = 1;
jintArray array;//定义数组对象
array = (*env)-> NewIntArray(env, 10);
for(; i<= 10; i++)
(*env)->SetIntArrayRegion(env, array, i-1, 1, &i);
/* 获取数组对象的元素个数 */
int len = (*env)->GetArrayLength(env, array);
/* 获取数组中的所有元素 */
jint* elems = (*env)->GetIntArrayElements(env, array, 0);
for(i=0; i<len; i++)
printf("ELEMENT %d IS %d/n", i, elems[i]);
return array;
}
Sample2.c涉及了两个jni.h定义的整型数 相关的数据类型:jint和jintArray,jint是在JNI框 架内特有的整数类型。程序的第7行开辟出一个长度为10 的jint数组。然后依次向该数组中放入元素1-10。第11行至第16行不是程序的必须部分,纯粹是为了向读者们演示GetArrayLength() 和GetIntArrayElements()这两个函数的使用方法,前者是获取数组长度,后者则是获取数组的首地址以便于遍历数组。
15.2.2.5 传递字符串数组
本节例程是对上节例程的进一步深化:虽然仍然是传递数组,但是数组的基类换成了字符串这样一种对象数据类型。Java程序将向C程序传入一个包含中文字符的字符串,C程序并没有处理这个字符串,而是开辟出一个新的字符串数组返回给Java程序,其中还包含两个汉字字符串。
Java程序的源代码如下。
代码清单15-10 在Linux平台上调用C函数的例程——Sample3
public class Sample3
{
public native String[] stringMethod(String text);
public static void main(String[] args)
throws java.io.UnsupportedEncodingException
{
System.loadLibrary("Sample3");
Sample3 sample = new Sample3();
String[] texts = sample.stringMethod("java编程思想");
for(int i=0;i<texts.length;i++)
{
texts[i]=new String(texts[i].getBytes("ISO8859-1"),"GBK");
System.out.print( texts[i] );
}
System.out.println();
}
}
Sample3.java调用libSample3.so中的函数stringMethod()。Sample3.c的源代码如下:
代码清单15-11 在Linux平台上调用C函数的例程——Sample3.c
#include <Sample3.h>
#include <string.h>
#include <stdlib.h>
#define ARRAY_LENGTH 5
JNIEXPORT jobjectArray JNICALL Java_Sample3_stringMethod
(JNIEnv *env, jobject obj, jstring string)
{
jclass objClass = (*env)->FindClass(env, "java/lang/String");
jobjectArray texts= (*env)->NewObjectArray(env,
(jsize)ARRAY_LENGTH, objClass, 0);
jstring jstr;
char* sa[] = { "Hello,", "world!", "JNI", "很", "好玩" };
int i=0;
for(;i<ARRAY_LENGTH;i++)
{
jstr = (*env)->NewStringUTF( env, sa[i] );
(*env)->SetObjectArrayElement(env, texts, i, jstr);//必须放入jstring
}
return texts;
}
第9、10行是我们需要特别关注的地方:JNI框架并 没有定义专门的字符串数组,而是使用jobjectArray——对象数组,对象数组的基类是jclass,jclass是JNI框架内特有的类型,相当 于Java语言中的Class类型。在本例程中,通过FindClass()函数在JNI上下文中获取到java.lang.String的类型 (Class),并将其赋予jclass变量。
在例程中我们定义了一个长度为5的对象数组texts,并在程序的第18行向其中循环放入预先定义好的sa数组中的字符串,当然前置条件是使用NewStringUTF()函数将C语言的字符串转换为jstring类型。
本例程的另一个关注点是C程序向Java程序传递的中文字符,在Java程序中能否正常显 示的问题。在笔者的试验环境中,Sample3.c是在Linux平台上编辑的,其中的中文字符则是用支持GBK的输入法输入的,而Java程序采用 ISO8859_1字符集存放JNI调用的返回字符,因此在“代码清单15-10在Linux平台上调用C函数的例程——Sample3”的第14行中将 其转码后输出。
15.2.2.6 传递对象数组
本节例程演示的是C程序向Java程序传递对象数组,而且对象数组中存放的不再是字符串,而是一个在Java中自定义的、含有一个topic属性的MailInfo对象类型。
MailInfo对象定义如下。
代码清单15-12 在Linux平台上调用C函数的例程——MailInfo
public class MailInfo {
public String topic;
public String getTopic()
{
return this.topic;
}
public void setTopic(String topic)
{
this.topic=topic;
}
}
Java程序的源代码如下。
代码清单15-13 在Linux平台上调用C函数的例程——Sample4
public class Sample4
{
public native MailInfo[] objectMethod(String text);
public static void main(String[] args)
{
System.loadLibrary("Sample4");
Sample4 sample = new Sample4();
MailInfo[] mails = sample.objectMethod("Thinking In Java");
for(int i=0;i<mails.length;i++)
System.out.println(mails[i].topic);
}
}
Sample4.java调用libSample4.so中的objectMethod()函数。Sample4.c的源代码如下。
代码清单15-14 在Linux平台上调用C函数的例程——Sample4.c
#include <Sample4.h>
#include <string.h>
#include <stdlib.h>
#define ARRAY_LENGTH 5
JNIEXPORT jobjectArray JNICALL Java_Sample4_objectMethod(
JNIEnv *env, jobject obj, jstring string)
{
jclass objClass = (*env)->FindClass(env, "java/lang/Object");
jobjectArray mails= (*env)->NewObjectArray(env,
(jsize)ARRAY_LENGTH, objClass, 0);
jclass objectClass = (*env)->FindClass(env, "MailInfo");
jfieldID topicFieldId = (*env)->GetFieldID(env, objectClass,
"topic", "Ljava/lang/String;");
int i=0;
for(;i<ARRAY_LENGTH;i++)
{
(*env)->SetObjectField(env, obj, topicFieldId, string);
(*env)->SetObjectArrayElement(env, mails, i, obj);
}
return mails;
}
程序的第9、10行读者们应该不会陌生,在上一节的例 程中已经出现过,不同之处在于这次通过FindClass()函数在JNI上下文中获取的是java.lang.Object的类型(Class),并将 其作为基类开辟出一个长度为5的对象数组,准备用来存放MailInfo对象。
程序的第12、13行的目的则是创建一个jfieldID类型的变量,在JNI中,操作对 象属性都是通过jfieldID进行的。第12行首先查找得到MailInfo的类型(Class),然后基于这个jclass进一步获取其名为 topic的属性,并将其赋予jfieldID变量。
程序的第18、19行的目的是循环向对象数组中放入jobject对象。 SetObjectField()函数属于首次使用,该函数的作用是向jobject的属性赋值,而值的内容正是Java程序传入的jstring变量 值。请注意在向对象属性赋值和向对象数组中放入对象的过程中,我们使用了在函数头部分定义的jobject类型的环境参数obj作为中介。至此,JNI框 架固有的两个环境入参env和obj,我们都有涉及。
Chap7:Jni中C++和Java的参数传递
如何使用JNI的一些基本方法和过程在网上多如牛毛,如果你对Jni不甚了解,不知道Jni是做什么的,如何建立一个基本的jni程序,或许可以参考下面下面这些文章:
<利用VC++6.0实现JNI的最简单的例子>
<JNI入门教程之HelloWorld篇>
<SUN JNI Tutorial>
这些资料的例子中,大多数只是输入一些简单的参数,获取没有参数。而在实际的使用过程中,往往需要对参数进行处理转换。才可以被C/C++程序识别。比如我们在C++中有一个结构(Struct)DiskInfo ,需要传递一个类似于DiskInfo *pDiskInfo的参数,类似于在C++这样参数如何传递到Java中呢?下面我们就来讨论C++到Java中方法的一些常见参数的转换:
1.定义Native Java类:
如果你习惯了使用JNI,你就不会觉得它难了。既然本地方法是由其他语言实现的,它们在Java中没有函数体。但是,所有本地代码必须用本地关键词native声明,成为Java类的成员。假设我们在C++中有这么一个结构,它用来描述硬盘信息:
//硬盘信息
struct {
char name[256];
int serial;
}DiskInfo;
那么我们需要在Java中定义一个类来与之匹配,声明可以写成这样:
class DiskInfo {
//名字
public String name;
//序列号
public int serial;
}
在这个类中,申明一些Native的本地方法,来测试方法参数的传递,分别定义了一些函数,用来传递结构或者结构数组,具体定义如下面代码:
/**//****************** 定义本地方法********************/
//输入常用的数值类型(Boolean,Byte,Char,Short,Int,Float,Double)
public native void displayParms(String showText, int i, boolean bl);
//调用一个静态方法
public native int add(int a, int b);
//输入一个数组
public native void setArray(boolean[] blList);
//返回一个字符串数组
public native String[] getStringArray();
//返回一个结构
public native DiskInfo getStruct();
//返回一个结构数组
public native DiskInfo[] getStructArray();
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
2.编译生成C/C++头文件
定义好了Java类之后,接下来就要写本地代码。本地方法符号提供一个满足约定的头文件,使用Java工具Javah可以很容易地创建它而不用手动去创建。你对Java的class文件使用javah命令,就会为你生成一个对应的C/C++头文件。
1)、在控制台下进入工作路径,本工程路径为:E:/work/java/workspace/JavaJni。
2)、运行javah 命令:javah-classpath E:/work/java/workspace/JavaJni com.sundy.jnidemo ChangeMethodFromJni
本文生成的C/C++头文件名为:com_sundy_jnidemo_ChangeMethodFromJni.h
3.在C/C++中实现本地方法
生成C/C++头文件之后,你就需要写头文件对应的本地方法。注意:所有的本地方法的第一个参数都是指向JNIEnv结构的。这个结构是用来调用JNI函数的。第二个参数jclass的意义,要看方法是不是静态的(static)或者实例(Instance)的。前者,jclass代表一个类对象的引用,而后者是被调用的方法所属对象的引用。
返回值和参数类型根据等价约定映射到本地C/C++类型,如表JNI类型映射所示。有些类型,在本地代码中可直接使用,而其他类型只有通过JNI调用操作。
表A ※ JNI类型映射
Java类型本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
3.1 使用数组:
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表B),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
表B
函数 Java数组类型 本地类型
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble
JNI数组存取函数
当你对数组的存取完成后,要确保调用相应的ReleaseXXXArrayElements函数,参数是对应Java数组和GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相关的资源。
为了使用java对象的数组,你必须使用GetObjectArrayElement函数和SetObjectArrayElement函数,分别去get,set数组的元素。GetArrayLength函数会返回数组的长度。
3.2 使用对象
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。
表C列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
表C
函数 描述
GetFieldID 得到一个实例的域的ID
GetStaticFieldID 得到一个静态的域的ID
GetMethodID 得到一个实例的方法的ID
GetStaticMethodID 得到一个静态方法的ID
※域和方法的函数
如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。
表D
Java类型 符号
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
objects对象 Lfully-qualified-class-name;L类名
Arrays数组 [array-type [数组类型
methods方法 (argument-types)return-type(参数类型)返回类型
※确定域和方法的符号
下面我们来看看,如果通过使用数组和对象,从C++中的获取到Java中的DiskInfo 类对象,并返回一个DiskInfo数组:
//返回一个结构数组,返回一个硬盘信息的结构数组
JNIEXPORT jobjectArray JNICALL
Java_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray
(JNIEnv *env, jobject _obj)
{
//申明一个object数组
jobjectArray args = 0;
//数组大小
jsize len = 5;
//获取object所属类,一般为java/lang/Object就可以了
jclass objClass = (env)->FindClass("java/lang/Object");
//新建object数组
args = (env)->NewObjectArray(len, objClass, 0);
/**//* 下面为获取到Java中对应的实例类中的变量*/
//获取Java中的实例类
jclass objectClass = (env)->FindClass("com/sundy/jnidemo/DiskInfo");
//获取类中每一个变量的定义
//名字
jfieldID str = (env)->GetFieldID(objectClass,"name","Ljava/lang/String;");
//序列号
jfieldID ival = (env)->GetFieldID(objectClass,"serial","I");
//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i < len; i++ )
{
//给每一个实例的变量付值
jstring jstr = WindowsTojstring(env,"我的磁盘名字是 D:");
//(env)->SetObjectField(_obj,str,(env)->NewStringUTF("my name is D:"));
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,10);
//添加到objcet数组中
(env)->SetObjectArrayElement(args, i, _obj);
}
//返回object数组
return args;
}
全部的C/C++方法实现代码如下:
/**//*
*
* 一缕阳光(sundy)版权所有,保留所有权利。
*/
/**//**
*
* TODO Jni 中一个从Java到C/C++参数传递测试类
*
* @author 刘正伟(sundy)
* @see http://www.cnweblog.com/sundy
* @see mailto:sundy26@126.com
* @version 1.0
* @since 2005-4-30
*
* 修改记录:
*
* 日期 修改人 描述
*----------------------------------------------------------------------------------------------
*
*
*
*/
// JniManage.cpp : 定义 DLL 应用程序的入口点。
//
package com.sundy.jnidemo;
#include "stdafx.h"
#include <stdio.h>
#include <math.h>
#include "jni.h"
#include "jni_md.h"
#include "./head/Base.h"
#include "head/wmi.h"
#include "head/com_sundy_jnidemo_ChangeMethodFromJni.h" //通过javah –jni javactransfer 生成
#include <stdio.h>
#include "stdlib.h"
#include "string.h"
#pragma comment (lib,"BaseInfo.lib")
#pragma comment (lib,"jvm.lib")
//硬盘信息
struct {
char name[256];
int serial;
}DiskInfo;
/**//*BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
LPTSTR strName = new CHAR[256] ;
(*GetHostName)(strName);
printf("%s/n",strName);
delete [] strName;
return TRUE;
}*/
//将jstring类型转换成windows类型
char* jstringToWindows( JNIEnv *env, jstring jstr );
//将windows类型转换成jstring类型
jstring WindowsTojstring( JNIEnv* env, char* str );
//主函数
BOOL WINAPI DllMain(HANDLE hHandle, DWORD dwReason, LPVOID lpReserved)
{
return TRUE;
}
//输入常用的数值类型 Boolean,Byte,Char,Short,Int,Float,Double
JNIEXPORT void JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_displayParms
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* szStr = (env)->GetStringUTFChars(s, 0 );
printf( "String = [%s]/n", szStr );
printf( "int = %d/n", i );
printf( "boolean = %s/n", (b==JNI_TRUE ? "true" : "false") );
(env)->ReleaseStringUTFChars(s, szStr );
}
//调用一个静态方法,只有一个简单类型输出
JNIEXPORT jint JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_add
(JNIEnv *env, jobject, jint a, jint b)
{
int rtn = (int)(a + b);
return (jint)rtn;
}
/**/输入一个数组,这里输入的是一个Boolean类型的数组
JNIEXPORT void JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_setArray
(JNIEnv *env, jobject, jbooleanArray ba)
{
jboolean* pba = (env)->GetBooleanArrayElements(ba, 0 );
jsize len = (env)->GetArrayLength(ba);
int i=0;
// change even array elements
for( i=0; i < len; i+=2 )
{
pba[i] = JNI_FALSE;
printf( "boolean = %s/n", (pba[i]==JNI_TRUE ? "true" : "false") );
}
(env)->ReleaseBooleanArrayElements(ba, pba, 0 );
}
/**/返回一个字符串数组
JNIEXPORT jobjectArray JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStringArray
(JNIEnv *env, jobject)
{
jstring str;
jobjectArray args = 0;
jsize len = 5;
char* sa[] = { "Hello,", "world!", "JNI", "is", "fun" };
int i=0;
args = (env)->NewObjectArray(len,(env)->FindClass("java/lang/String"),0);
for( i=0; i < len; i++ )
{
str = (env)->NewStringUTF(sa[i] );
(env)->SetObjectArrayElement(args, i, str);
}
return args;
}
//返回一个结构,这里返回一个硬盘信息的简单结构类型
JNIEXPORT jobject JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStruct
(JNIEnv *env, jobject obj)
{
/**//* 下面为获取到Java中对应的实例类中的变量*/
//获取Java中的实例类
jclass objectClass = (env)->FindClass("com/sundy/jnidemo/DiskInfo");
//获取类中每一个变量的定义
//名字
jfieldID str = (env)->GetFieldID(objectClass,"name","Ljava/lang/String;");
//序列号
jfieldID ival = (env)->GetFieldID(objectClass,"serial","I");
//给每一个实例的变量付值
(env)->SetObjectField(obj,str,(env)->NewStringUTF("my name is D:"));
(env)->SetShortField(obj,ival,10);
return obj;
}
//返回一个结构数组,返回一个硬盘信息的结构数组
JNIEXPORT jobjectArray JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray
(JNIEnv *env, jobject _obj)
{
//申明一个object数组
jobjectArray args = 0;
//数组大小
jsize len = 5;
//获取object所属类,一般为ava/lang/Object就可以了
jclass objClass = (env)->FindClass("java/lang/Object");
//新建object数组
args = (env)->NewObjectArray(len, objClass, 0);
/**//* 下面为获取到Java中对应的实例类中的变量*/
//获取Java中的实例类
jclass objectClass = (env)->FindClass("com/sundy/jnidemo/DiskInfo");
//获取类中每一个变量的定义
//名字
jfieldID str = (env)->GetFieldID(objectClass,"name","Ljava/lang/String;");
//序列号
jfieldID ival = (env)->GetFieldID(objectClass,"serial","I");
//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i < len; i++ )
{
//给每一个实例的变量付值
jstring jstr = WindowsTojstring(env,"我的磁盘名字是 D:");
//(env)->SetObjectField(_obj,str,(env)->NewStringUTF("my name is D:"));
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,10);
//添加到objcet数组中
(env)->SetObjectArrayElement(args, i, _obj);
}
//返回object数组
return args;
}
//将jstring类型转换成windows类型
char* jstringToWindows( JNIEnv *env, jstring jstr )
{
int length = (env)->GetStringLength(jstr );
const jchar* jcstr = (env)->GetStringChars(jstr, 0 );
char* rtn = (char*)malloc( length*2+1 );
int size = 0;
size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL );
if( size <= 0 )
return NULL;
(env)->ReleaseStringChars(jstr, jcstr );
rtn[size] = 0;
return rtn;
}
//将windows类型转换成jstring类型
jstring WindowsTojstring( JNIEnv* env, char* str )
{
jstring rtn = 0;
int slen = strlen(str);
unsigned short * buffer = 0;
if( slen == 0 )
rtn = (env)->NewStringUTF(str );
else
{
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
buffer = (unsigned short *)malloc( length*2 + 1 );
if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
rtn = (env)->NewString( (jchar*)buffer, length );
}
if( buffer )
free( buffer );
return rtn;
}
Java 测试native代码
这没有什么多说的,看代码吧
//主测试程序
public static void main(String[] args) {
ChangeMethodFromJni changeJni = new ChangeMethodFromJni();
//输入常用的数值类型(string int boolean)
System.out
.println("------------------输入常用的数值类型(string int boolean)-----------");
changeJni.displayParms("Hello World!", 100, true);
//调用一个静态方法
System.out.println("------------------调用一个静态方法-----------");
int ret = changeJni.add(12, 20);
System.out.println("The result is: " + String.valueOf(ret));
//输入一个数组
System.out.println("------------------输入一个数组-----------");
boolean[] blList = new boolean[] { true, false, true };
changeJni.setArray(blList);
//返回一个字符串数组
System.out.println("------------------返回一个字符串数组-----------");
String[] strList = changeJni.getStringArray();
for (int i = 0; i < strList.length; i++) {
System.out.print(strList[i]);
}
System.out.println();
System.out.println("------------------返回一个结构-----------");
//返回一个结构
DiskInfo disk = changeJni.getStruct();
System.out.println("name:" + disk.name);
System.out.println("Serial:" + disk.serial);
//返回一个结构数组
System.out.println("------------------返回一个结构数组 -----------");
DiskInfo[] diskList = changeJni.getStructArray();
for (int i = 0; i < diskList.length; i++) {
System.out.println("name:" + diskList[i].name);
System.out.println("Serial:" + diskList[i].serial);
}
}
注:本程序在VS2003,eclipse (jse5.0) winxp sp2编译通过
posted on 2005-05-02 20:22 sundy 阅读(4406) 评论(21) 编辑 收藏 所属分类: Java
评论
# re: Jni中C++和Java的参数传递 2005-05-22 14:35 张磊
请问如果想返回byte[]类型该怎么做 回复 更多评论
# re: Jni中C++和Java的参数传递 2005-05-23 08:37 sundy
因为:
byte[] jbyteArray 比特型数组
所以你将byte[] 作为一个jbyteArray数组传递就可以了
回复 更多评论
# re: Jni中C++和Java的参数传递 2005-09-21 14:46 小影
请问如果我想把在C++里面计算好的一个二维数组传回给java程序接受,该怎么写代码呢?我找了很多这方面的书和资料,都没有关于传递二维数组的介绍,请您给予指导,多谢啦^_^ 回复 更多评论
# re: Jni中C++和Java的参数传递 2005-09-21 17:47 sundy
我没有直接传递过二维数组
但我想你可以把试一试二维数组转换成为一个Hashmap的数组传出来。
请参考"如何在Jni中传递出Hashmap的数组?"的一些代码
回复 更多评论
# re: Jni中C++和Java的参数传递 2005-12-26 16:32 wangjian
返回一个结构数组时,为什么每个对象的数据都是一样的?即5个Diskinfo的成员值都相同,能不能不相同? 回复更多评论
# re: Jni中C++和Java的参数传递 2005-12-26 16:55 wangjian
我把5个DiskInfo对象的成员serial分别设置为1、2、3、4、5,可是传递到java后5个对象的serial成员值都是5,为什么这样阿?盼回复,多谢! 回复更多评论
# re: Jni中C++和Java的参数传递 2005-12-27 21:51 sundy
//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i < len; i++ )
{
......
//添加到objcet数组中
(env)->SetObjectArrayElement(args, i, _obj);
}
你看看设置的_Obj是不是都是同一个??
回复 更多评论
# re: Jni中C++和Java的参数传递 2005-12-28 13:32 wangjian
如下所示,我就是把你程序中(env)->SetShortField(_obj,ival,10)的参数10换成i,结果每个对象都是对象的serial成员值都是4,请问怎样实现多个不同对象的传递?
for(int i=0; i < len; i++ )
{
jstring jstr = WindowsTojstring(env,"我的磁盘名字是D:");
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,i);
(env)->SetObjectArrayElement(args, i, _obj);
}
回复 更多评论
# re: Jni中C++和Java的参数传递 2005-12-28 15:15 sundy
应该没有问题的呀,
SetObjectArrayElement的时候,_obj是不同的吗?
要不你将for循环改为:
jstring jstr = WindowsTojstring(env,"我的磁盘名字是C:");
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,0);
(env)->SetObjectArrayElement(args, 0, _obj);
jstring jstr = WindowsTojstring(env,"我的磁盘名字是D:");
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,1);
(env)->SetObjectArrayElement(args, 1, _obj);
看看对吗? 回复更多评论
# re: Jni中C++和Java的参数传递 2005-12-29 20:42 wangjian
这样作不对,不过我找到正确的方法了,要用构造函数生成新的对象。回复 更多评论
# re: Jni中C++和Java的参数传递 2006-01-17 11:07 luli
SQLRETURN SQLAllocHandle( SQLSMALLINT HandleType,
SQLHANDLE InputHandle,
SQLHANDLE * OutputHandlePtr
)
这是odbc api里的一个函数 SQLHANDLE 是一个结构
c#里的引用方式如下
[DllImport("ODBC32.dll")]
private static extern short SQLAllocHandle(short hType, IntPtr inputHandle, out IntPtr outputHandle);
但我不清楚SQLHANDLE 结构具体怎么构造的 因此我无法用java类来模拟
我是菜鸟 望解答谢过了 回复 更多评论
# re: Jni中C++和Java的参数传递 2006-01-17 14:25 luli
忘了补充 SQLHANDLE InputHandle与SQLHANDLE * OutputHandlePtr
一个是结构 一个是结构指针那我是否该如下模拟
class SQLHANDLE
{
}
public class test
{
SQLHANDLE a=new SQLHANDLE ();
public static void main(String args[]) {
int i=SQLAllocHandle( SQLSMALLINT HandleType, new SQLHANDLE(),a)
}
}
回复 更多评论
# re: Jni中C++和Java的参数传递 2006-03-21 17:31 Hefe
WideCharToMultiByte();
MultiByteToWideChar();
请问这两个函数实现什么功能,请作者给出代码,多谢!
回复 更多评论
# re: Jni中C++和Java的参数传递 2006-03-22 08:47 sundy
@Hefe look here: http://www.google.com/search?hl=zh-CN&lr=lang_zh-CN&q=WideCharToMultiByte
回复 更多评论
# re: Jni中C++和Java的参数传递 2006-03-28 17:40 dijk
要在c函数中调用java类的类成员的方法,比如调用JEditorPane类型成员的setText方法,该怎么办? 回复 更多评论
# re: Jni中C++和Java的参数传递 2006-04-16 21:33 陈世雄
java中函数的处理中,对于对象类型(非基本类型int,long...)的输入参数,函数中是可以修改输入参数的内容,函数执行完毕,修改仍然是有效的。
jni中是否也是这样呢?
回复 更多评论
# re: Jni中C++和Java的参数传递 2006-04-18 17:50 王文波
你好:
向你请教一个问题:我想用jini来调用dll。我在jbuilder中新建的简单的project调用jini运行正常。但是,我现在要对一个工程软件进行二次开发,该软件的
开发也使用jbuilder生成一个project,然后放在指定的路径下就可以了,该软件在运行的时候会自动读取该project。我在这个软件二次开发的project中使用
jini,则总是报错:unsatisfiedlinkError get()。其中get()方法名。请问该怎么解决这个问题?
我的邮箱:zwj23232@tom.com回复 更多评论
# re: Jni中C++和Java的参数传递 2006-05-29 21:25 single
# re: Jni中C++和Java的参数传递 2005-12-29 20:42 wangjian
这样作不对,不过我找到正确的方法了,要用构造函数生成新的对象。回复
---------------------------------------------------
能说说方法吗? 回复更多评论
# re: Jni中C++和Java的参数传递 2006-08-29 11:34 yangyongfa
我正在做JNI,是在C++中调用JAVA类的方法,请问,我在JAVA类的方法中参数使 用的是byte[],而我在C++中是把一个文件读成unsigned char*,请问怎么可以正确调用JAVA中的方法? 类中方法原型:public boolean AddHoyuBox2DB(String BoxName, byte[] BoxFile,byte[] WDHPic,int BoxFileBinLen, int WDHPicBinLen, String ParameterText, byte[] XXPic,int PicBinLen, byte[] SeriousPics,int SeriousPicsBinLen,String FileLenStr) ?
回复 更多评论
# re: Jni中C++和Java的参数传递 2007-10-25 15:27 vampire
c的结构提里写有一个**p,指针的指针,在java中该如何封装??? 回复 更多评论
# re: Jni中C++和Java的参数传递 2007-12-11 13:13 Focus
@single
for(int i=0; i < len; i++ )
{
jobject objTemp = (env)->AllocObject(objectClass); //释放问题??这个是否需要释放不是很懂
//objectClass是函数上面给的那个
jstring jstr = WindowsTojstring(env,"我的磁盘名字是D:");
(env)->SetObjectField(objTemp,str,jstr);
(env)->SetShortField(objTemp,ival,i);
(env)->SetObjectArrayElement(args, i, objTemp);
}
这个 可以实现数组 元素相同的问题
Chap8:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组
近遇到一个问题,请各位帮忙解决下:
如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组?BYTE为unsigned char类型
这两个我理解应该是相同的吧,强制类型转换好像不启作用,应该如何转换呢?
该问题已经关闭: 问题已解决,之前代码有问题 jbyte * arrayBody = env->GetByteArrayElements(data,0); jsize theArrayLengthJ = env->GetArrayLength(data); BYTE * starter = (BYTE *)arrayBody;
Chap5:使用JNI技术实现java程序调用第三方dll(c/c++)文件的功能
JAVA的跨平台的特性深受java程 序员们的喜爱,但正是由于它为了实现跨平台的目的,使得它和本地机器的各种内部联系变得很少,大大约束了它的功能,比如与一些硬件设备通信,往往要花费很 大的精力去设计流程编写代码去管理设备端口,而且有一些设备厂商提供的硬件接口已经经过一定的封装和处理,不能直接使用java程序通过端口和设备通信,这种情况下就得考虑使用java程序去调用比较擅长同系统打交道的第三方程序,从1.1版本开始的JDK提供了解决这个问题的技术标准:JNI技术.
JNI是Java Native Interface(Java本地接口)的缩写,本地是相对于java程序来说的,指直接运行在操作系统之上,与操作系统直接交互的程序.从1.1版本的JDK开始,JNI就作为标准平台的一部分发行.在JNI出现的初期是为了Java程序与本地已编译语言,尤其是C和C++的互操作而设计的,后来经过扩展也可以与c和c++之外的语言编写的程序交互,例如Delphi程序.
使用JNI技术固然增强了java程序的性能和功能,但是它也破坏了java的跨平台的优点,影响程序的可移植性和安全性,例如由于其他语言(如C/C++)可能能够随意地分配对象/占用内存,Java的指针安全性得不到保证.但在有些情况下,使用JNI是可以接受的,甚至是必须的,例如上面提到的使用java程序调用硬件厂商提供的类库同设备通信等,目前市场上的许多读卡器设备就是这种情况.在这必须使用JNI的情况下,尽量把所有本地方法都封装在单个类中,这个类调用单个的本地库文件,并保证对于每种目标操作系统,都可以用特定于适当平台的版本替换这个文件,这样使用JNI得到的要比失去的多很多.
现在开始讨论上面提到的问题,一般设备商会提供两种类型的类库文件,windows系统的会包含.dll/.h/.lib文件,而linux系统的会包含.so/.a文件,这里只讨论windows系统下的c/c++编译的dll文件调用方法.
我把设备商提供的dll文件称之为第三方dll文件,之所以说第三方,是因为JNI直 接调用的是按它的标准使用c/c++语言编译的dll文件,这个文件是客户程序员按照设备商提供的.h文件中的列出的方法编写的dll文件,我称之为第二 方dll文件,真正调用设备商提供的dll文件的其实就是这个第二方dll文件.到这里,解决问题的思路已经产生了,大慨分可以分为三步:
1>编写一个java类,这个类包含的方法是按照设备商提供的.h文件经过变形/转换处理过的,并且必须使用native定义.这个地方需要注意的问题是java程序中定义的方法不必追求和厂商提供的头文件列出的方法清单中的方法具有相同的名字/返回值/参数,因为一些参数类型如指针等在java中没法模拟,只要能保证这个方法能实现原dll文件中的方法提供的功能就行了;
2>按JNI的规则使用c/c++语言编写一个dll程序;
3>按dll调用dll的规则在自己编写的dll程序里面调用厂商提供的dll程序中定义的方法.
我之前为了给一个java项目添加IC卡读写功能,曾经查了很多资料发现查到的资料都是只说到第二步,所以剩下的就只好自己动手研究了.下面结合具体的代码来按这三个步骤分析.
1>假设厂商提供的.h文件中定义了一个我们需要的方法:
__int16 __stdcall readData( HANDLE icdev, __int16 offset, __int16 len, unsigned char *data_buffer );
a.__int16定义了一个不依赖于具体的硬件和软件环境,在任何环境下都占16 bit的整型数据(java中的int类型是32 bit),这个数据类型是vc++中特定的数据类型,所以我自己做的dll也是用的vc++来编译.
b.__stdcall表示这个函数可以被其它程序调用,vc++编译的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为__stdcall方式,WINAPI都采用这种方式.c/c++语言默认的调用方式是__cdecl,所以在自己做可被java程序调用的dll时一定要加上__stdcall的声明,否则在java程序执行时会报类型不匹配的错误.
c.HANDLE icdev是windows操作系统中的一个概念,属于win32的一种数据类型,代表一个核心对象在某一个进程中的唯一索引,不是指针,在知道这个索引代表的对象类型时可以强制转换成此类型的数据.
这些知识都属于win32编程的范围,更为详细的win32资料可以查阅相关的文档.
这个方法的原始含义是通过设备初始时产生的设备标志号icdev,读取从某字符串在内存空间中的相对超始位置offset开始的共len个字符,并存放到data_buffer指向的无符号字符类型的内存空间中,并返回一个16 bit的整型值来标志这次的读设备是否成功,这里真正需要的是unsigned char *这个指针指向的地址存放的数据,而java中没有指针类型,所以可以考虑定义一个返回字符串类型的java方法,原方法中返回的整型值也可以按经过一定的规则处理按字符串类型传出,由于HANDLE是一个类型于java中的Ojbect类型的数据,可以把它当作int类型处理,这样java程序中的方法定义就已经形成了:
String readData( int icdev, int offset, int len );
声明这个方法的时候要加上native关键字,表明这是一个与本地方法通信的java方法,同时为了安全起见,此文方法要对其它类隐藏,使用private声明,再另外写一个public方法去调用它,同时要在这个类中把本地文件加载进来,最终的代码如下:
package test;
public class LinkDll
{
//从指定地址读数据
private native String readData( int icdev, int offset, int len );
public String readData( int icdev, int offset, int len )
{
return this.readDataTemp( icdev, offset, len );
}
static
{
System.loadLibrary( "TestDll" );//如果执行环境是linux这里加载的是SO文件,如果是windows环境这里加载的是dll文件
}
}
2>使用JDK的javah命令为这个类生成一个包含类中的方法定义的.h文件,可进入到class文件包的根目录下(只要是在 classpath参数中的路径即可),使用javah命令的时候要加上包名javah test.LinkDll,命令成功后生成一个名为test_LinkDll.h的头文件.
文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated*/
#include <jni.h>
/* Header for class test_LinkDll */
#ifndef _Included_test_LinkDll #define
Included_test_LinkDll
#ifdef __cplusplus extern "C" { #endif
/*
* Class: test_LinkDll
* Method: readDataTemp
* Signature: (III)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_test_LinkDll_readDataTemp(JNIEnv *, jobject, jint, jint, jint);
#ifdef __cplusplus } #endif
#endif
可以看出,JNI为了实现和dll文件的通信,已经按它的标准对方法名/参数类型/参数数目作了一定的处理,其中的JNIEnv*/jobjtct这两个参数是每个JNI方法固有的参数,javah命令负责按JNI标准为每个java方法加上这两个参数.JNIEnv是指向类型为JNIEnv_的一个特殊JNI数据结构的指针,当由C++编译器编译时JNIEnv_结构其实被定义为一个类,这个类中定义了很多内嵌函数,通过使用"->"符号,可以很方便使用这些函数,如:
(env)->NewString( jchar* c, jint len )
可以从指针c指向的地址开始读取len个字符封装成一个JString类型的数据.
其中的jchar对应于c/c++中的char,jint对应于c/c++中的len,JString对应于java中的String,通过查看jni.h可以看到这些数据类型其实都是根据java和c/c++中的数据类型对应关系使用typedef关键字重新定义的基本数据类型或结构体.
具体的对应关系如下:
Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
更为详细的资料可以查阅JNI文档.
需要注意的问题:test_LinkDll.h文件包含了jni.h文件;
3>使用vc++ 6.0编写TestDll.dll文件,这个文件名是和java类中loadLibrary的名称一致.
a>使用vc++6.0 新建一个Win32 Dynamic-Link Library的工程文件,工程名指定为TestDll
b>把源代码文件和头文件使用"Add Fiels to Project"菜单加载到工程中,若使用c来编码,源码文件后缀名为.c,若使用c++来编码,源码文件扩展名为.cpp,这个一定要搞清楚,因为对于不同的语言,使用JNIEnv指针的方式是不同的.
c>在这个文件里调用设备商提供的dll文件,设备商一般提供三种文件:dll/lib/h,这里假设分别为A.dll/A.lib/A.h.
这个地方的调用分为动态调用和静态调用静态调用即是只要把被调用的dll文件放到path路径下,然后加载lib链接文件和.h头文件即可直接调用A.dll中的方法:
把设备商提供的A.h文件使用"Add Fiels to Project"菜单加载到这个工程中,同时在源代码文件中要把这个A.h文件使用include包含进来;
然后依次点击"Project->settings"菜单,打开link选项卡,把A.lib添加到"Object/library modules"选项中.
具体的代码如下:
//读出数据,需要注意的是如果是c程序在调用JNI函数时必须在JNIEnv的变量名前加*,如(*env)->xxx,如果是c++程序,则直接使用(env)->xxx
#include<WINDOWS.H>
#include<MALLOC.H>
#include<STDIO.H>
#include<jni.h>
#include "test_LinkDll.h"
#include "A.h"
JNIEXPORT jstring JNICALL Java_test_LinkDll_readDataTemp( JNIEnv *env, jobject jo, jint ji_icdev, jint ji_len )
{
//*************************基本数据声明与定义******************************
HANDLE H_icdev = (HANDLE)ji_icdev;//设备标志符
__int16 i16_len = (__int16)ji_len;//读出的数据长度,值为3,即3个HEX形式的字符
__int16 i16_result;//函数返回值
__int16 i16_coverResult;//字符转换函数的返回值
int i_temp;//用于循环的中间变量
jchar jca_result[3] = { 'e', 'r', 'r' };//当读数据错误时返回此字符串
//无符号字符指针,指向的内存空间用于存放读出的HEX形式的数据字符串
unsigned char* uncp_hex_passward = (unsigned char*)malloc( i16_len );
//无符号字符指针,指向的内存空间存放从HEX形式转换为ASC形式的数据字符串
unsigned char* uncp_asc_passward = (unsigned char*)malloc( i16_len * 2 );
//javachar指针,指向的内存空间存放从存放ASC形式数据字符串空间读出的数据字符串
jchar *jcp_data = (jchar*)malloc(i16_len*2+1);
//javaString,存放从java char数组生成的String字符串,并返回给调用者
jstring js_data = 0;
//*********读出3个HEX形式的数据字符到uncp_hex_data指定的内存空间**********
i16_result = readData( H_icdev, 6, uncp_hex_data );//这里直接调用的是设备商提供的原型方法.
if ( i16_result != 0 )
{
printf( "读卡错误....../n" );
//这个地方调用JNI定义的方法NewString(jchar*,jint),把jchar字符串转换为JString类型数据,返回到java程序中即是String
return (env)->NewString( jca_result, 3 );
}
printf( "读数据成功....../n" );
//**************HEX形式的数据字符串转换为ASC形式的数据字符串**************
i16_coverResult = hex_asc( uncp_hex_data, uncp_asc_data, 3 );
if ( i16_coverResult != 0 )
{
printf( "字符转换错误!/n" );
return (env)->NewString( jca_result, 3 );
}
//**********ASC char形式的数据字符串转换为jchar形式的数据字符串***********
for ( i_temp = 0; i_temp < i16_len; i_temp++ )
jcp_data[i_temp] = uncp_hex_data[i_temp];
//******************jchar形式的数据字符串转换为java String****************
js_data = (env)->NewString(jcp_data,i16_len);
return js_data;
}
动态调用,不需要lib文件,直接加载A.dll文件,并把其中的文件再次声明,代码如下:
#include<STDIO.H>
#include<WINDOWS.H>
#include "test_LinkDll.h"
//首先声明一个临时方法,这个方法名可以随意定义,但参数同设备商提供的原型方法的参数保持一致.
typedef int ( *readDataTemp )( int, int, int, unsigned char * );//从指定地址读数据
//从指定地址读数据
JNIEXPORT jstring JNICALL Java_readDataTemp( JNIEnv *env, jobject jo, jint ji_icdev, jint ji_offset, jint ji_len )
{
int i_temp;
int i_result;
int i_icdev = (int)ji_icdev;
int i_offset = (int)ji_offset;
int i_len = (int)ji_len;
jchar jca_result[5] = { 'e', 'r', 'r' };
unsigned char *uncp_data = (unsigned char*)malloc(i_len);
jchar *jcp_data = (jchar *)malloc(i_len);
jstring js_data = 0;
//HINSTANCE是win32中同HANDLE类似的一种数据类型,意为Handle to an instance,常用来标记App实例,在这个地方首先把A.dll加载到内存空间,以一个App的形式存放,然后取
得它的instance交给dllhandle,以备其它资源使用.
HINSTANCE dllhandle;
dllhandle = LoadLibrary( "A.dll" );
//这个地方首先定义一个已声明过的临时方法,此临时方法相当于一个结构体,它和设备商提供的原型方法具有相同的参数结构,可互相转换
readDataTemp readData;
//使用win32的GetProcAddress方法取得A.dll中定义的名为readData的方法,并把这个方法转换为已被定义好的同结构的临时方法,
//然后在下面的程序中,就可以使用这个临时方法了,使用这个临时方法在这时等同于使用A.dll中的原型方法.
readData = (readDataTemp) GetProcAddress( dllhandle, "readData" );
i_result = (*readData)( i_icdev, i_offset, i_len, uncp_data );
if ( i_result != 0 )
{
printf( "读数据失败....../n" );
return (env)->NewString( jca_result, 3 );
}
for ( i_temp = 0; i_temp < i_len; i_temp++ )
{
jcp_data[i_temp] = uncp_data[i_temp];
}
js_data = (env)->NewString( jcp_data, i_len );
return js_data;
}
4>以上即是一个java程序调用第三方dll文件的完整过程,当然,在整个过程的工作全部完成以后,就可以使用java类LinkDll中的public String radData( int, int, int )方法了,效果同直接使用c/c++调用这个设备商提供的A.dll文件中的readData方法几乎一样.
总结:JNI技术确实是提高了java程序的执行效率,并且扩展了java程序的功能,但它也确确实实破坏了java程序的最重要的优点:平台无关性,所以除非必须(不得不)使用JNI技术,一般还是提倡写100%纯java的程序.根据自己的经验及查阅的一些资料,把可以使用JNI技术的情况罗列如下:
1>需要直接操作物理设备,而没有相关的驱动程序,这时候我们可能需要用C甚至汇编语言来编写该设备的驱动,然后通过JNI调用;
2>涉及大量数学运算的部分,用java会带来些效率上的损失;
3>用java会产生系统难以支付的开销,如需要大量网络链接的场合;
4>存在大量可重用的c/c++代码,通过JNI可以减少开发工作量,避免重复开发.
另外,在利用JNI技术的时候要注意以下几点:
1>由于Java安全机制的限制,不要试图通过Jar文件的方式发布包含本地化方法的Applet到客户端;
2>注意内存管理问题,虽然在本地方法返回Java后将自动释放局部引用,但过多的局部引用将使虚拟机在执行本地方法时耗尽内存;
3>JNI技术不仅可以让java程序调用c/c++代码,也可以让c/c++代码调用java代码.
注:有一个名叫Jawin开源项目实现了直接读取第三方dll文件,不用自己辛苦去手写一个起传值转换作用的dll文件,有兴趣的可以研究一下.但 是我用的时候不太顺手,有很多规则限制,像自己写程序时可以随意定义返回值,随意转换类型,用这个包的话这些都是不可能的了,所以我的项目还没开始就把它 抛弃了.
Chap9:如何编写jni方法(转载)
作者:crazycow 发布时间:2008-11-16 14:44:21.0
http://blog.chinaunix.net/u1/38994/showart_1099528.html
一、概述:
在 这篇文章中将会简单介绍如何编制一些简单的JNI 方法。我们都知道JNI方法可以帮助我们调用用C/c++编写的函数,这样如果一项工作已经用C /c++语言实现的话,我们就可以不用花很大的力气再用JAVA语言对这一工作进行再实现,只要编制相应的JNI函数,就可以轻松实现JAVA语言对C /c++函数的调用,从而大大减轻程序开发人员的工作量。
在这个项目中,我们编制了很多小实例,通过阅读,运行这些小实例,你可以轻松的学会如何编制JNI方法。这篇文档可以帮助你更好的理解及实现这些实例。
现 在让我们进入主题。首先,我们看一下这个项目的体系构架。该项目分为两部分,一部分用c语言是c语言的例子,另一部分是c++语言的例子。每部分都包含 java,src源文件目录,以及一个Makefile文件。java目录中是需要调用JNI函数的JAVA源程序,含有后缀名.java。src 目录 中含有JNI函数的实现代码,包括.c或.cpp文件和.h文件。Makefile文件是对 java 、src 目录下的文件进行编译组织进而生成可执 行文件的文件。当Makefile文件执行以后还会生成以下子目录:lib , class ,bin目录 。lib 目录中包含项目中生成的静态函数库 文件libJNIExamples.so,java程序所调用的JNI方法都是通过这个库来调用的。class 目录中包含由java目录下 的.java 文件生成的.class文件。bin目录中是一个可执行的shell脚本文件。在执行该脚本的时候,项目所有程序实例的运行结果都将一并显 示在屏幕上。
具体执行步骤为:
make
cd bin
./run.sh
下面来介绍一下在这个项目中所实现的实例:
1. 如何调用标准C/c++中的函数--例如:printf(...)
2. 如何调用C/c++中自定义的函数
3. 如何在jni函数中访问java类中的对象实例域
4. 如何在jni函数中访问java类中的静态实例域
5. 如何在jni函数中调用java对象的方法
6. 如何在jni函数中调用java类的静态方法
7. 如何在jni函数中传递基本数据类型参数
8. 如何在jni函数中传递对象类型参数
9. 如何在jni函数中处理字符串
10. 如何在jni函数中处理数组
11. 处理jni函数中的返回值情况
12. 在jni中实现创建java类对象
二、基本步骤:
在介绍这些例子之前,让我们先来看看编写jni方法所需要的基本步骤,这些实例都是用c来实例来讲解,至于c++的实例和c的实例区别不大,只要作稍微的修改即可,在文档的末尾我们将介绍这些内容:
1、要想定义jni方法,首先得要在java语言中对这一方法进行声明(自然这一声明过程要在类中进行)
声明格式如下:
publicnativevoid print(); System.loadLibrary(“JNIExamples”); }
jni 函数用关键字native方法声明。
2、对该类的源文件进行编译使用javac命令,生成相应的.class文件。
3、用javah -jni为函数生成一个在java调用和实际的c函数之间的转换存根,该存根通过从虚拟机栈中取出参数信息,并将其传递给已编译的C函数来实现转换。
4、建立一个特殊的共享库,并从该共享库到处这个存根,在上面的例子中使用了System.loadLibrary,来加载libJNIExamples共享库。
三、配置运行环境:
在 编写一个简单的jni函数之前我们必须配置相应的运行环境。jdk的配置在这里就不作介绍,这里主要说的是库的路径。当调用 System.loadLibrary(..)时,编译器会到我们系统设置的库路径中寻找该库。修改路径的方法和修改任何环境变量的方法基本相同,只要在 /etc/bash.bashrc目录下增加一行LD_LIBRARY_PATH=.:./lib:$(LD_LIBRARY_PATH)即可。也可以通 过命令行export LD_LIBRARY_PATH=.:./lib:$(LD_LIBRARY_PATH)
四、运行实例分析:
1、实例一:在jni中调用标准c中自带的函数printf():
下面以实例1为例来详细说明编写jni方法的详细过程。
(1)、定义包含jni函数的类Print.java:
{
/*********************************************************************** * the print() function will call the printf() funcion which is a ANSI c funciton * *************************************************************************/
Public native void print();
System.loadLibrary("JNIExamples");
}
}
在上面的实例 中,使用public native void print();语句来定义了一个Print类的jni方法。并用 Sysgem.loadLibrary(“JNIExamples”)语句来加载libJNIExamples.so库。注意:加载的语句一定要用 static关键字声明在静态块中,以保证引用该类时该库始终被加载。
(2)、对该类进行编译:javac Print.java。生成Print.class类,然后用javah 产生一个Print.h的头文件:javah Print。长生的Print.h文件格式如下:
/* DO NOT EDIT THIS FILE - it is machine generated *//* Header for class Print */ JNIEXPORT void JNICALL Java_Print_print (JNIEnv *, jobject); }
其中的加粗字体为要实现的JNI函数生命部分。
(3)、编写JNI函数的实现部分Print.c
JNIEXPORT void JNICALL Java_Print_print (JNIEnv *env, jobject obj)
{
printf("example1:in this example a printf() function in ANSI C is called/n");
printf("Hello,the output is generated by printf() function in ANSI C/n");
}
在这个文件中实现了一个简单的Jni方法。该方法调用ANSI C 中的printf()函数,输出了两个句子。
(4)、将本地函数编译到libJNIExamples.so的库中:
使用语句:gcc -fPIC -I/usr/jdk1.5/include -I/usr/jdk1.5/include/linux -shared -o libJNIExamples.so Print.c。
(5)、至此Jni函数已全部实现,可以在java代码中调用拉。
在此我们使用一个简单的类来对实现的jni方法进行测试,下面是PrintTest.java的源代码部分:
publicstaticvoid main(String[] args) { Print p = new Print(); p.print(); } }
(6)、对PrintTest.java进行编译执行得到如下结果:
example1:in this example a printf() function in ANSI C is called
Hello,the output is generated by printf() function in ANSI C .
下面介绍的每个实例实现的步骤也都是按着上述步骤执行的。所以介绍时只介绍实现的关键部分。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
2、实例二、调用c语言用户定义的函数
(源程序为:java/Cfunction.java java/C_functionTest.java src/Cfunction.c src/Cfunction.h )
当需要在java程序中调用用c所实现的函数是,需要在需要调用该c函数的类中定义一个jni方法,在该jni方法中去调用该c函数,相当于用java方法把c函数封装起来,以供java程序调用。
在实例二中我们简单定义了一个printHello()函数,该函数的功能只是输出一句话,如果要在java程序中调用该函数,只需在jni函数中调用即可,和调用ANSI C中自带的prinf()函数没有任何区别。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
3、实例三、在jni函数中访问java类中的对象实例域
(源程序为:java/CommonField.java java/CommonFieldTest.java src/CommonField.c src/CommonField.h )
jni函数的实现部分是在c 语言中实现的,如果它想访问java中定义的类对象的实例域需要作三步工作,
(1)调用GetObjectClass()函数得到该对像的类,该函数返回一个jclass类型值。
(2)调用GetFieldID()函数得到要访问的实例域在该类中的id。
(3)调用GetXXXField()来得到要访问的实例域的值。其中XXX和要访问的实例域的类型相对应。
在jni中java 编程语言和c 语言数据类型的对应关系为java原始数据类型前加 'j' 表示对应c语言的数据类型例如boolean 为jboolean ,int 为 jint,double 为jdouble等。对象类型的对应类型为jobject。
在 本实例中,您可以看到我们在java/CommonField.java 中定义了类CommonField类,其中包含int a , int b 两 个实例域,我们要在jni函数getCommonField()中对这两个域进行访问和修改。你可以在 src/CommonField.c中找到该函数 的实现部分。
以下语句是对该域的访问(以下代码摘自:src/CommonField.c):
jclass class_Field = (*env)->GetObjectClass(env,obj);
jfieldID fdA = (*env)->GetFieldID(env,class_Field,"a","I");
jfieldID fdB = (*env)->GetFieldID(env,class_Field,"b","I");
jint valueA = (*env)->GetIntField(env,obj,fdA);
jint valueB = (*env)->GetIntField(env,obj,fdB);
在jni 中对所有jni函数的调用都要用到env指针,该指针也是每一个本地方法的第一个参数,他是函数指针表的指针,所以,必须在每一个jni调用前面加上(*env)->GetObjectClass(env,obj)函数调用返回obj对像的类型,其中obj 参数表示要你想要得到类型的类对象。
jfieldID GetFieldID(JNIEnv *env,jclass cl, const char name[], const char sig[]) 该 函数返回一个域的标识符name 表示域名,sig表示编码的域签名。所谓编码的签名即编码类型的签名在上例中类中的a实例域为int 型,用"I”来表 示,同理"B” 表示byte ,"C” 表示 char , “D”表示 double ,”F” 表示float,“J”表示long, “S” 表 示short , “V” 表示void ,”Z”表示 boolean类型。
GetIntField(env,obj,fdA),用来访问obj对象的fdA域,如果要访问的域为double类型,则要使用GetDoubleField(env,obj,fdA)来访问,即类型对应GetXXXField中的XXX。
以下函数用来修改域的值:
(*env)->SetIntField(env,obj,fdA,109); (*env)->SetIntField(env,obj,fdB,145);
这和获得域的值类似,只是该函数多了一个要设置给该域的值参数。
访问对象实例域的相关函数如下:
jfieldID GetFieldID(JNIEnv *env, jclass cl, const char name[], const char sig[])
该函数返回一个域的标识符。各参数含义如下:
env JNI 接口指针;cl 类对象 ; name 域名; sig 编码的域签名
XXX GetXXXField(JNIEnv *env, jobject obj, jfieldID id)
该函数返回域的值。域类型XXX是Object, Boolean, byte, char , short, int ,long ,float, double 中类型之一。
参数 env JNI借口指针;obj为域所在对象;id为域的标识符。
void SetXXXField(JNIEnv *env,jobject obj, jfieldID id, XXX value)
该函数用于设置域的值。XXX的含义同上,
参数中env, obj , id 的含义也同上,value 值为将要设置的值。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
4、实例四:在jni函数中访问类的静态实例域
(java/Field.java java/FieldTest.java src/Field.c src/Field.h)
因为静态实例域并不属于某个对象,而是属于一个类,所以在要访问静态实例域时,和访问对象的实例域不同,它所调用的函数是(以实例四来说明,一下代码摘自src/Field.c):
jclass class_Field = (*env)->FindClass(env,"Field");
jfieldID fdA = (*env)->GetStaticFieldID(env,class_Field,"a","I");
jint valueA = (*env)->GetStaticIntField(env,class_Field,fdA);
(*env)->SetStaticIntField(env,class_Field,fdA,111);
由于没有 对象,必须使用FindClass代替GetObjectClass来获得类引用。在FindClass()的第二个参数是类的编码签名,类的编码签名和基本类型的编码签名有所不同,如果类在当前包中,就直接是类的名称,如果类不在当前包中则要加入该类的详细路径:例如String类在java.lang 包中,则String的签名要写成( Ljava/lang/String;),其中的(L和;)是不可少的,其中(;)是表达是的终止符。其他三个函数和访问对象数据域基本没什么区别。
5、实例五:在jni函数中调用java对象的方法
(java/CommonMethod.java java/CommonMethodTest.java src/CommonMehod.c src/CommonMethod.h )
在jni函数中我们不仅要对java对象的数据域进行访问,而且有时也需要调用java中类对象已经实现的方法,实例五就是关于这方面的实现的。在src/CommonMethod.c中我们可以找到下面的代码:
JNIEXPORT void JNICALL Java_CommonMethod_callMethod (
JNIEnv *env, jobject obj, jint a, jstring s)
{
printf("example 5:in this example,a object's method will be called/n");
jclass class_CommonMethod = (*env)->GetObjectClass(env,obj);
jmethodID md = (*env)->GetMethodID(env,class_CommonMethod,"print",
"(ILjava/lang/String;)V");
(*env)->CallVoidMethod(env,obj,md,a,s);
}
该代码部分展示了如何实现对java类对象函数的调用过程。从以上代码部分我们可以看到,要实现该调用需要有三个步骤,调用三个函数
jclass class_CommonMethod = (*env)->GetObjectClass(env,obj);
jmethodID md = (*env)->GetMethodID(env,class_CommonMethod,"print","(ILjava/lang/String;)V");
(*env)->CallVoidMethod(env,obj,md,a,s);
GetObjectClass(...)函数获得要调用对象的类;GetMethodID(...)获得要调用的方法相对于该类的ID号;CallXXXMethod(...)调用该方法。
在 编写该调用过程的时候,需要注意的仍然是GetMethodID(...)函数中编码签名的问题,在该实例中,我们要做的是找到CommonMethod 类的print(int a, String s)方法,该方法打印整数a,和字符串s 的直。在函数的编码签名部分(该部分以加粗、并加有下划 线)GetMethodID(env,class_CommonMethod,"print","(ILjava/lang /String;)V"); 从左往右可以查看,括号中的内容为要调用方法的参数部分内容,I表示第一个参数为int类型,“Ljava/lang /String;”表示第二个参数为String类型,V表示返回值类型为空void,如果返回值类型不为空,则使用相应的类型签名。返回值类型是和下面将要使用的调用该方法的函数CallXXXMethod(...)相关联的,该函数的xxx要用相应的类型来替换,在此实例中为void,如果返回值类型 为int类型则调用该方法的函数就为CallIntMethod(...)。
6、实例六:在jni函数中调用java类的静态方法
(java/Method.java java/MethodTest.java src/Method.h src/Method.c)
实例五中介绍了如何调用类对象的方法,在此实例中我们将介绍如何调用java类的静态方法在此实例中我们在/java/Method.java中定义了静态方法:
public static void print() {
System.out.println("this is a static method of class Method");
}
该函数的功能就是打印字符串“ this is a static method of class Method”;
我们在src/Method.c中实现了对该方法调用的jni函数:
JNIEXPORT void JNICALL Java_Method_callMethod (JNIEnv *env, jobject obj)
{
printf("example 6:in this example, the class's static method will be called/n");
jclass class_Method = (*env)->FindClass(env,"Method");
jmethodID md = (*env)->GetStaticMethodID(env,class_Method,"print","()V");
(*env)->CallStaticVoidMethod(env,class_Method,md); }
和实例五不同的是,我们要调用的三个函数变为:
FindClass(...)、GetStaticMethodID(...)、CallStaticVoidMethod(...)。
其中的机制和实例五是一样的。再次就不做过多的介绍。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
7、实例七:jni函数中传递基本数据类型参数
(java/Basic.java java/BasicTest.java src/Basic.c src/Basic.h) 在java/Basic.java中,我们定义了一个public native void raiseValue(int a)函数,该函数将打印使value的值增加a,并打印原来的value和新的value值。
在src/Basic.c中给出了该jni函数的实现部分。
JNIEXPORT void JNICALL Java_Basic_raiseValue (
JNIEnv *env, jobject obj, jint a)
{
printf("example 7: in this example, a integer type parament will be passed to the jni method/n");
jclass class_Basic = (*env)->GetObjectClass(env,obj);
jfieldID fd = (*env)->GetFieldID(env,class_Basic,"value","I");
jint v = (*env)->GetIntField(env,obj,fd);
v = v+a;
(*env)->SetIntField(env,obj,fd,v);
}
在此函数实现中,因为要访问Basic类中的value域,所以调用了GetObjectClass(...), GetFieldID(...), GetIntField(...)函数获取value值,下面一步 的 “ = v+a; ”说明,传递基本类型参数的处理方式和在c语言中的基本数据类型的处理无异。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
8、实例八:在jni函数中传递对象类型参数
(java/Book.java java/BookTest.java src/BookTest.c src/BookTest.h)
在该实例中演示了在jni函数中传递对象函数的过程。
我们在该实例中定义了一个类Book
total_page = t;
}
publicint getTotalPage() { }
publicint getCurrentPage() { }
current_page++;
}
}
然后我们在java/BookTest.java中定义jni函数
public native void bookCurrentStatus(Book b);
该函数需要一个Book类型的参数,并返回该参数的当前状态,包括该书一共有多少页的total_page,以及当前页current_page。函数的实现部分为(src/BookTest.c)
JNIEXPORT void JNICALL Java_BookTest_bookCurrentStatus (JNIEnv *env,
jobject this_obj, jobject obj)
{
printf("example 8: in this example, a object parament will be passed to the jni method。/n");
jclass class_book = (*env)->GetObjectClass(env,obj);
jmethodID id_getTotal = (*env)->GetMethodID(env,
class_book,"getTotalPage","()I");
jmethodID id_getCurrent = (*env)->GetMethodID(env,
class_book,"getCurrentPage","()I");
jint total_page = (*env)->CallIntMethod(env,
obj,id_getTotal);
jint current_page = (*env)->CallIntMethod(env,
obj,id_getCurrent);
printf("the total page is:%d and the current page is :%d/n",
total_page,current_page);
}
该函数包含三个参数(JNIEnv *env, jobject this_obj, jobject obj) ,第二 个jobject this_obj参数表示当前的jni 函数所属于的类对象,第三个jobject obj参数表示传递的参数Book类型的类对象。
对于实现部分,基本和实例五--调用java类对象的方法中的操作相同,就不作详解。
9、实例九:在jni函数中处理字符串
(java/Str.java java/StrTest.java src/Str.c src/Str.h)
在该实例中我们讲解如何传递、处理字符串参数。
在java/Str.java中我们定义了一个 printString(String s) 的方法,用来处理字符串参数。
在src/Str.c中我们可以看到该函数的实现部分:
JNIEXPORT void JNICALL Java_Str_printString (JNIEnv *env,
jobject obj, jstring s)
{
printf("example 9: in this example, a String object parament will be passed to the jni method./n");
const char* string = (char*)(*env)->GetStringUTFChars(env,s,NULL);
printf("%s is put out in native method/n",string);
(*env)->ReleaseStringUTFChars(env,s,(jbyte*)string);
}
实现过程中调用了两个函数:GetStringUTFChars(...)、 ReleaseStringUTFChars(...)。
GetStringUTFChars(...) 用来获取String对象的字符串,并将其抓那还为char*类型,这应该字符串就可以在c语言中进行处理拉。 ReleaseStringUTFChars(...)用于当该字符串使用完成后,将其进行垃圾回收。记住,当使用完字符串时一定不要忘记调用该函数。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
10、实例十:在jni函数中处理数组
(java/Arr.java java/ArrTest.java src/Arr.c src/Arr.h)
java中所有的数组类型都有相对应的c语言类型,其中jarray类型表示一个泛型数组
boolean[] --jbooleanArray
byte[]--jbyteArray
char[]--jcharArary
int[]---jcharArray
short[]---jshortArray
long[]---jlongArray
float[]--jfloatArray
double[]—-jdoubleArray
Object[]--- jobjectArray。
当访问数组时,可以通过GetObjectAraryElement和SetObjectArrayElement方法访问对象数组的元素。
而 对于一般类型数组,你可以调用GetXXXAraryElements来获取一个只想数组起始元素的指针,而当你不在使用该数组时,要记得调用 ReleaseXXXArrayElements,这样你所作的改变才能保证在原始数组里得到反映。当然如果你需要得到数组的长度,可以调用 GetArrayLength函数。
在本实例中,我们在Arr.java中定义一个本地方法:print(int intArry[]),该函数的功能为对该数组进行输出,在src/Arr.c中我们可以看到该方法的实现过程如下:
JNIEXPORT void JNICALL Java_Arr_print (JNIEnv *env,
jobject obj, jintArray intArray)
{
printf("example 10:in this example, a array parament will be passed to the jni method./n");
jint* arr = (*env)->GetIntArrayElements(env,intArray,NULL);
n = (*env)->GetArrayLength(env,intArray);
printf("the native method output the int array/n");
for( i = 0;i<(*env)->GetArrayLength(env,intArray);i++)
{
printf("%d ",arr[i]);
}
(*env)->ReleaseIntArrayElements(env,intArray,arr,0);
}
我们在此调用了GetIntArrayElements(...)来获取一个指向intArray[]数组第一个元素的指针。
用getArrayLength(..)函数来得到数组的长度,以方便数组遍历时使用。最后应用ReleaseArrayElements(...)函数来释放该数组指针。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
11、实例十一:在jni中的返回值问题
(java/ReturnValue.java java/ReturnValueTest.java java/BookClass.java src/ReturnValue.c src/ReturnValue.h)
在java/ReturnValue类中定义了三个jni方法: returnInt(),returnString() ,returnObject()
三个方法,分别返回int , String , Object 类型的值。
其在src/ReturnValue.c中的实现分别为:
JNIEXPORT jint JNICALL Java_ReturnValue_returnInt (
JNIEnv *env, jobject obj)
{
jclass class_ReturnValue = (*env)->GetObjectClass(env,obj);
jfieldID fd = (*env)->GetFieldID(env,class_ReturnValue,"value","I");
jint v = (*env)->GetIntField(env,obj,fd);
return v;
}
* Signature: ()Ljava/lang/String;
JNIEXPORT jstring JNICALL Java_ReturnValue_returnString (
JNIEnv *env, jobject obj)
{
printf("example 11: in this example, the int and object of return value will be proceeding/n");
jclass class_ReturnValue = (*env)->GetObjectClass(env,obj);
jfieldID fd = (*env)->GetFieldID(env,class_ReturnValue,"name","Ljava/lang/String;");
jstring jstr = (jstring)(*env)->GetObjectField(env,obj,fd);
}
* * Method: returnObject
JNIEXPORT jobject JNICALL Java_ReturnValue_returnObject (
JNIEnv *env, jobject obj)
{
jclass class_ReturnValue = (*env)->GetObjectClass(env,obj);
jfieldID fd = (*env)->GetFieldID(env,class_ReturnValue,"myBook","LBookClass;");
jobject jbook = (jstring)(*env)->GetObjectField(env,obj,fd);
}
在这里分别涉及到了对java类对象的一般参数,String参数,以及Object参数的访问。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
12、实例十二:在jni中创建java类对象:
(java/Test.java src/CreateObj.c src/CreateObj.h)
如果想要在jni函数创建java类对象则要引用java 类的构造器方法,通过调用NewObject函数来实现。
NewObject函数的调用方式为:
jobject obj_new = (*env)->NewObject(env,class, methodid, paraments);
在该实例中,我们在java/Test.java 中定义了Book1类,要在CreateObj类的modifyProperty() jni方法中创建该类对象。我们可以在src/CreateObj.c中看到该jni方法创建对象的过程:
jobject book;
jclass class_book;
jmethodID md_book;
class_book = (*env)->FindClass(env,"LBook1;");
md_book = (*env)->GetMethodID(env,class_book,"<init>","(IILjava/lang/String;)V");
book = (*env)->NewObject(env,class_book,md_book,100,1,"huanghe");
在 创建对象的过程中可以看到,要创建一个java类对象,首先需要得到得到使用FindClass函数得到该类,然后使用GetMethodID方法得到该 类的构造器方法id,主义在此时构造器的函数名始终为:"”,其后函数的签名要符合函数签名规则。在此我们的构造器有三个参数:int , int, String.
并且其返回值类型要永久为空,所以函数签名为:"(IILjava/lang/String;)V"
然后我们调用NewObject()函数来创建该类的对象,在此之后就可以使用该对象拉。
以上内容介绍的是jni函数c语言的实现实例。如果想要使用c++的实例,我们只需要把其中的每一个函数调用过程作稍微的修改:
例如:(*env)->NewObject(env,class_book,md_book,100,1,”huanghe”);
修改为:(env)->NewObject(class_book,md_book,100,1,”huanghe”);
即修改(*env)为(env)再把参数中的env去掉。然后把所有c的函数改为c++的函数就OK拉。
具体情况可以去查看我们的c++实例代码.
Chap10:在 Windows 中实现 Java 本地方法
级别: 初级
David WendtWebSphere Development Research Triangle Park, NC
1999 年 5 月 01 日
本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。这些示例包括传递和返回常用的数据类型。
本文中的示例使用 Sun Microsystems 公司创建的 Java DevelopmentKit (JDK) 版本 1.1.6和 Java本地接口 (JNI)规范。 用 C 语言编写的本地代码是用 MicrosoftVisual C++ 编译器编译生成的。
简介
本文提供调用本地 C 代码的 Java 代码示例,包括传递和返回某些常用的数据类型。本地方法包含在特定于平台的可执行文件中。就本文中的示例而言,本地方法包含在 Windows 32 位动态链接库 (DLL) 中。
不过我要提醒您,对 Java 外部的调用通常不能移植到其他平台上,在 applet 中还可能引发安全异常。实现本地代码将使您的 Java 应用程序无法通过 100% 纯 Java 测试。但是,如果必须执行本地调用,则要考虑几个准则:
- 将您的所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。
- 本地方法要简单。尽量将您的 DLL 对任何第三方(包括 Microsoft)运行时 DLL 的依赖减到最小。使您的本地方法尽量独立,以将加载您的 DLL 和应用程序所需的开销减到最小。如果需要运行时 DLL,必须随应用程序一起提供它们。
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.Java 调用 C
示例 1 -- 传递参数
在第一个示例中,我们将三个常用参数类型传递给本地函数: String、 int和 boolean 。本例说明在本地C 代码中如何引用这些参数。
public class MyNative { public void showParms( String s, int i, boolean b ) { showParms0( s, i , b ); } private native void showParms0( String s, int i, boolean b ); static { System.loadLibrary( "MyNative" ); } } |
请注意,本地方法被声明为专用的,并创建了一个包装方法用于公用目的。这进一步将本地方法同代码的其余部分隔离开来,从而允许针对所需的平台对它进行优化。static子句加载包含本地方法实现的 DLL。
javac MyNative.java(将 .java 编译为 .class) javah -jni MyNative(生成 .h 文件) |
这将生成一个 MyNative.h 文件,其中包含一个本地方法原型,如下所示:
/* * Class: MyNative * Method: showParms0 * Signature: (Ljava/lang/String;IZ)V */ JNIEXPORT void JNICALL Java_MyNative_showParms0 (JNIEnv *, jobject, jstring, jint, jboolean); |
本地方法是在文件 MyNative.c 中用 C 语言实现的:
#include <stdio.h> #include "MyNative.h" JNIEXPORT void JNICALL Java_MyNative_showParms0 (JNIEnv *env, jobject obj, jstring s, jint i, jboolean b) { const char* szStr = (*env)->GetStringUTFChars( env, s, 0 ); printf( "String = [%s]/n", szStr ); printf( "int = %d/n", i ); printf( "boolean = %s/n", (b==JNI_TRUE ? "true" : "false") ); (*env)->ReleaseStringUTFChars( env, s, szStr ); } |
MyNative.dll 是通过编译 C 源文件创建的。下面的编译语句使用Microsoft Visual C++ 编译器:
cl -Ic:/jdk1.1.6/include -Ic:/jdk1.1.6/include/win32 -LD MyNative.c -FeMyNative.dll |
MyNative.dll 已创建好,现在就可将其用于 MyNative 类了。
可以这样测试这个本地方法:在 MyNative 类中创建一个 main 方法来调用 showParms 方法,如下所示:
public static void main( String[] args ) { MyNative obj = new MyNative(); obj.showParms( "Hello", 23, true ); obj.showParms( "World", 34, false ); } |
java MyNative Can't find class MyNative |
java MyNative String = [Hello] int = 23 boolean = true String = [World] int = 34 |
boolean = false 示例 2 -- 返回一个值
本例将说明如何在本地方法中实现返回代码。
将这个方法添加到 MyNative 类中,这个类现在变为以下形式:
public class MyNative { public void showParms( String s, int i, boolean b ) { showParms0( s, i , b ); } public int hypotenuse( int a, int b ) { return hyptenuse0( a, b ); } private native void showParms0( String s, int i, boolean b ); private native int hypotenuse0( int a, int b ); static { System.loadLibrary( "MyNative" ); } /* 测试本地方法 */ public static void main( String[] args ) { MyNative obj = new MyNative(); System.out.println( obj.hypotenuse(3,4) ); System.out.println( obj.hypotenuse(9,12) ); } } |
javah -jni MyNative |
生成的 MyNative.h 现在包含 hypotenuse0 原型,如下所示:
/* * Class: MyNative * Method: hypotenuse0 * Signature: (II)I */ JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *, jobject, jint, jint); |
该方法是在 MyNative.c 源文件中实现的,如下所示:
#include <stdio.h> #include <math.h> #include "MyNative.h" JNIEXPORT void JNICALL Java_MyNative_showParms0 (JNIEnv *env, jobject obj, jstring s, jint i, jboolean b) { const char* szStr = (*env)->GetStringUTFChars( env, s, 0 ); printf( "String = [%s]/n", szStr ); printf( "int = %d/n", i ); printf( "boolean = %s/n", (b==JNI_TRUE ? "true" : "false") ); (*env)->ReleaseStringUTFChars( env, s, szStr ); } JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *env, jobject obj, jint a, jint b) { int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) ); return (jint)rtn; } |
再次请注意,jint 和 int 值是可互换的。
使用相同的编译语句重新编译这个 DLL:
cl -Ic:/jdk1.1.6/include -Ic:/jdk1.1.6/include/win32 -LD MyNative.c -FeMyNative.dll |
现在执行 java MyNative 将输出 5 和 15 作为斜边的值。
示例 3 -- 静态方法
public static int hypotenuse( int a, int b ) { return hypotenuse0(a,b); } ... private static native int hypotenuse0( int a, int b ); |
现在运行 javah 为 hypotenuse0创建一个新原型,生成的原型如下所示:
/* * Class: MyNative * Method: hypotenuse0 * Signature: (II)I */ JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *, jclass, jint, jint); |
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0 (JNIEnv *env, jclass cls, jint a, jint b) { int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) ); return (jint)rtn; } |
本质上,jobject 参数已变为 jclass 参数。此参数是指向MyNative.class 的一个句柄。main 方法可更改为以下形式:
public static void main( String[] args ) { System.out.println( MyNative.hypotenuse( 3, 4 ) ); System.out.println( MyNative.hypotenuse( 9, 12 ) ); } |
因为方法是静态的,所以调用它不需要实例化 MyNative 对象。本文后面的示例将使用静态方法。
示例 4 -- 传递数组
本例说明如何传递数组型参数。本例使用一个基本类型,boolean,并将更改数组元素。下一个示例将访问 String(非基本类型)数组。将下面的方法添加到 MyNative.java 源代码中:
public static void setArray( boolean[] ba ) { for( int i=0; i < ba.length; i++ ) ba[i] = true; setArray0( ba ); } ... private static native void setArray0( boolean[] ba ); |
在本例中,布尔型数组被初始化为 true,本地方法将把特定的元素设置为 false。同时,在 Java 源代码中,我们可以更改 main 以使其包含测试代码:
boolean[] ba = new boolean[5]; MyNative.setArray( ba ); for( int i=0; i < ba.length; i++ ) System.out.println( ba[i] ); |
在编译源代码并执行 javah 以后,MyNative.h 头文件包含以下的原型:
/* * Class: MyNative * Method: setArray0 * Signature: ([Z)V */ JNIEXPORT void JNICALL Java_MyNative_setArray0 (JNIEnv *, jclass, jbooleanArray); |
JNIEXPORT void JNICALL Java_MyNative_setArray0 (JNIEnv *env, jclass cls, jbooleanArray ba) { jboolean* pba = (*env)->GetBooleanArrayElements( env, ba, 0 ); jsize len = (*env)->GetArrayLength(env, ba); int i=0; // 更改偶数数组元素 for( i=0; i < len; i+=2 ) pba[i] = JNI_FALSE; (*env)->ReleaseBooleanArrayElements( env, ba, pba, 0 ); } |
示例 5 -- 传递 Java String 数组
本例将通过最常用的非基本类型,Java String,说明如何访问非基本对象的数组。字符串数组被传递给本地方法,而本地方法只是将它们显示到控制台上。
MyNative 类定义中添加了以下几个方法:
public static void showStrings( String[] sa ) { showStrings0( sa ); } private static void showStrings0( String[] sa ); |
String[] sa = new String[] { "Hello,", "world!", "JNI", "is", "fun." }; MyNative.showStrings( sa ); |
JNIEXPORT void JNICALL Java_MyNative_showStrings0 (JNIEnv *env, jclass cls, jobjectArray sa) { int len = (*env)->GetArrayLength( env, sa ); int i=0; for( i=0; i < len; i++ ) { jobject obj = (*env)->GetObjectArrayElement(env, sa, i); jstring str = (jstring)obj; const char* szStr = (*env)->GetStringUTFChars( env, str, 0 ); printf( "%s ", szStr ); (*env)->ReleaseStringUTFChars( env, str, szStr ); } printf( "/n" ); } |
数组元素可以通过 GetObjectArrayElement 访问。
示例 6 -- 返回 Java String 数组
最后一个示例说明如何在本地代码中创建一个字符串数组并将它返回给Java 调用者。MyNative.java 中添加了以下几个方法:
public static String[] getStrings() { return getStrings0(); } private static native String[] getStrings0(); |
更改 main 以使 showStrings 将 getStrings 的输出显示出来:
MyNative.showStrings( MyNative.getStrings() ); |
JNIEXPORT jobjectArray JNICALL Java_MyNative_getStrings0 (JNIEnv *env, jclass cls) { jstring str; jobjectArray args = 0; jsize len = 5; char* sa[] = { "Hello,", "world!", "JNI", "is", "fun" }; int i=0; args = (*env)->NewObjectArray(env, len, (*env)->FindClass(env, "java/lang/String"), 0); for( i=0; i < len; i++ ) { str = (*env)->NewStringUTF( env, sa[i] ); (*env)->SetObjectArrayElement(env, args, i, str); } return args; } |
2.调试
c:/jdk1.1.6/include;c:/jdk1.1.6/include/win32 |
下面是设置一个 Visual C++ 6.0 项目来调试本地方法的步骤。
<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->
- 选择 Build 下拉式菜单下的 Build MyNative.dll 来建立这个项目。确保将项目的活动配置设置为调试(这通常是缺省值)。
- 在 Project Settings 下,设置 Debug 选项卡来调用适当的 Java 解释器,如下所示:
<!--[if !vml]--><!--[endif]-->
当执行这个程序时,忽略“在 java.exe 中找不到任何调试信息”的消息。当调用本地方法时,在 C 代码中设置的任何断点将在适当的地方停止 Java 程序的执行。
3.其他信息
上面这些示例说明了如何在 C 源文件中使用 JNI 方法。如果使用 C++,则请将相应方法的格式从:
(*env)->JNIMethod( env, .... ); |
env->JNIMethod( ... ); |
在C++ 中,JNI 函数被看作是 JNIEnv 类的成员方法。
Chap11:JNI编程系列之基础篇
最近干一个活需要从Java调用C++编译的动态链接库,研究了一下JNI,现在将网上搜罗的文档和自己的体会贡献出来。
下面是一个简单的Java程序HelloWorld.java,
public static void main(String[] args) {
System.loadLibrary("HelloWorld");
如果没有native关键字,这一行代码就是普通Java方法的声明。关键字native表明这是一个用本地语言实现的方法。
System.loadLibrary("HelloWorld");
这行代码的作用是调用名为HelloWorld的动态链接库,在Windows下,是HelloWorld.dll,在Linux下是HelloWorld.so。
显然现在这个Java程序是不能运行的。要运行它先要做下面的工作。执行
执行完这两条语句之后,会生成一个名为HelloWorld.h的文件,它的内容应该是这样的,
/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class HelloWorld */
JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject);
这里的jni.h,只要你安装了JDK就能在安装目录下找到它。
不要修改这个文件的内容,现在要做的是写一个名为HelloWorld.cpp程序,实现上面这个.h文件里的函数,
JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject) {
std::cout << "Hello World!"<< std::endl;
这是一个最简单的C++程序。将它编译为动态链接库,我们得到HelloWorld.dll,将这个.dll文件拷到HelloWorld.java文件所在的目录下。执行
1. 写一个Java程序,将你希望用C语言实现的方法用native关键字标识出来,同时加上调用动态链接库的语句。
System.loadLibrary("HelloWorld");
在class或bin目录下(其下或其子目录下有 javac命令生成的*.class文件)执行
3. 根据.h文件,写一个.cpp程序,编译成动态链接库,并将其复制到.java文件所在的路径下。
Chap12:JNI编程系列之中级篇(上)
首先要强调的是,native方法不但可以传递Java的基本类型做参数,还可以传递更复杂的类型,比如String,数组,甚至自定义的类。这一切都可以在jni.h中找到答案。
Java的String和C++的string是不能对等起来的,所以处理起来比较麻烦。先看一个例子,
// native method that prints a prompt and reads a line
private native String getLine(String prompt);
public static void main(String args[]) {
String input = p.getLine("Type a line: ");
System.out.println("User typed: " + input);
String getLine(String prompt);
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);
jstring是JNI中对应于String的类型,但是和基本类型不同的是,jstring不能直接当作C++的string用。如果你用
其实要处理jstring有很多种方式,这里只讲一种我认为最简单的方式,看下面这个例子,
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
str = env->GetStringUTFChars(prompt, false);
return NULL; /* OutOfMemoryError already thrown */
std::cout << str << std::endl;
env->ReleaseStringUTFChars(prompt, str);
char* tmpstr = "return string succeeded";
jstring rtstr = env->NewStringUTF(tmpstr);
在上面的例子中,作为参数的prompt不能直接被C++程序使用,先做了如下转换
str = env->GetStringUTFChars(prompt, false);
返回的时候,要生成一个jstring类型的对象,也必须通过如下命令,
jstring rtstr = env->NewStringUTF(tmpstr);
这里用到的GetStringUTFChars和NewStringUTF都是JNI提供的处理String类型的函数,还有其他的函数这里就不一一列举了。
/****************************************************/
3. 数组类型的传递
和String一样,JNI为Java基本类型的数组提供了j*Array类型,比如int[]对应的就是jintArray。来看一个传递int数组的例子,Java程序就不写了,
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
carr = env->GetIntArrayElements(arr, false);
return 0; /* exception occurred */
env->ReleaseIntArrayElements(arr, carr, 0);
4. 二维数组和String数组
在JNI中,二维数组和String数组都被视为object数组,因为数组和String被视为object。仍然用一个例子来说明,这次是一个二维int数组,作为返回值。
jclass intArrCls = env->FindClass("[I");
result = env->NewObjectArray(size, intArrCls, NULL);
for (int i = 0; i < size; i++) {
jint tmp[256]; /* make sure it is large enough! */
jintArray iarr = env->NewIntArray(size);
for(int j = 0; j < size; j++) {
env->SetIntArrayRegion(iarr, 0, size, tmp);
env->SetObjectArrayElement(result, i, iarr);
因为要返回值,所以需要新建一个jobjectArray对象。
jclass intArrCls = env->FindClass("[I");
String是通过“Ljava/lang/String;”表示的,那相应的,String数组就应该是“[Ljava/lang/String;”。
result = env->NewObjectArray(size, intArrCls, NULL);
jintArray iarr = env->NewIntArray(size);
env->SetIntArrayRegion(iarr, 0, size, tmp);
env->SetObjectArrayElement(result, i, iarr);
通过上面这些步骤,我们就创建了一个二维int数组,并赋值完毕,这样就可以做为参数返回了。
Chap13:JNI编程系列之高级篇
1. 在一般的Java类中定义native方法
在前两篇的例子中,都是将native方法放在main方法的Java类中,实际上,完全可以在任何类中定义native方法。这样,对于外部来说,这个类和其他的Java类没有任何区别。
2. 访问Java类的域和方法
native方法虽然是native的,但毕竟是方法,那么就应该同其他方法一样,能够访问类的私有域和方法。实际上,JNI的确可以做到这一点,我们通过几个例子来说明,
public native void nativeMethod();
System.out.println("call java method succeeded");
JNIEXPORT void JNICALL Java_testclass_ClassCallDLL_nativeMethod(JNIEnv *env, jobject obj)
jclass cls = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(cls, "str_", "Ljava/lang/String;");
jstring jstr = (jstring)env->GetObjectField(obj, fid);
const char *str = env->GetStringUTFChars(jstr, false);
if(std::string(str) == "abcde")
std::cout << "access field succeeded" << std::endl;
fid = env->GetFieldID(cls, "number_", "I");
env->SetIntField(obj, fid, i);
jmethodID mid = env->GetMethodID(cls, "javaMethod", "()V");
env->CallVoidMethod(obj, mid);
jfieldID fid = env->GetFieldID(cls, "str_", "Ljava/lang/String;");
jstring jstr = (jstring)env->GetObjectField(obj, fid);
fid = env->GetFieldID(cls, "number_", "I");
env->SetIntField(obj, fid, i);
第一行代码同前面一样,获得number_的id,第二行我们通过SetIntField函数将i的值赋给number_,其他类似的函数可以参考JDK的文档。
jmethodID mid = env->GetMethodID(cls, "javaMethod", "()V");
env->CallVoidMethod(obj, mid);
long f (int n, String s, int[] arr);
3. 在native方法中使用用户定义的类
jclass cls = env->FindClass("Ltestclass/ClassB;");
jmethodID id = env->GetMethodID(cls, "<init>", "(D)V");
jobject obj = env->NewObjectA(cls, id, args);
jobject obj = env->NewObjectA(cls, id, args);
生成了一个ClassB的对象,args是ClassB的构造函数的参数,它是一个jvalue*类型。
4. 异常处理
在C++和Java的编程中,异常处理都是一个重要的内容。但是在JNI中,麻烦就来了,native方法是通过C++实现的,如果在native方法中发生了异常,如何传导到Java呢?
JNI提供了实现这种功能的机制。我们可以通过下面这段代码抛出一个Java可以接收的异常,
errCls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(errCls, "thrown from C++ code");
如果要抛出其他类型的异常,替换掉FindClass的参数即可。这样,在Java中就可以接收到native方法中抛出的异常。
Chap14:如何在C/C++中调用Java
<!--[if !vml]--><!--[endif]-->
1.环境搭建
<!--[if !vml]--><!--[endif]-->
图2 设置集成开发环境图
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
将目录C:/JDK/include和C:/JDK/include/win32加入到开发环境的Include Files目录中,
同时将C: /JDK/lib目录添加到开发环境的Library Files目录中。这三个目录是JNI定义的一些常量、结构及方法的头文件和库文件。
throw new IllegalAccessException("exception occur.");
2.初始化虚拟机
本地代码在调用Java方法之前必须先加载Java虚拟机,而后所有的Java程序都在虚拟机中执行。
3.访问类方法
初始化了Java虚拟机后,就可以开始调用Java的方法。要调用一个Java对象的方法必须经过几个步骤:
3.1.获取指定对象的类定义(jclass)
jclass cls = (*env)-> GetObjectClass(env, obj); //其中obj是要引用的对象,类型是jobject
3.2.读取要调用方法的定义(jmethodID)
打开命令行窗口并运行 javap -s -p jni.test.Demo 得到运行结果如下:
假设我们已经有一个jni.test.Demo的实例obj
*/
jstring msg = (*env)-> CallObjectMethod(env, obj, mid);
如果该方法是静态的方法那只需要将最后一句代码改为以下写法即可:
jstring msg = (*env)-> CallStaticObjectMethod(env, cls, mid);
*/
3.3.调用方法
4访问类属性
访问类的属性与访问类的方法大体上是一致的,只不过是把方法变成属性而已。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
4.1.获取指定对象的类(jclass)
这一步与访问类方法的第一步完全相同,具体使用参看访问类方法的第一步。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
4.2.读取类属性的定义(jfieldID)
jfieldID (JNICALL *GetFieldID)
(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID (JNICALL *GetStaticFieldID)
(JNIEnv *env, jclass clazz, const char *name, const char *sig);
这两个函数中第一个参数为JNI环境;clazz为类的定义;name为属性名称;第四个参数同样是为了表达属性的类型。前面我们使用javap工具获取类的详细定义的时候有这样两行:
其中第二行注释的内容就是第四个参数要填的信息,这跟访问类方法时是相同的。
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
4.3.读取和设置属性值
jfieldID field = (*env)->GetFieldID(env,obj,"msg"," Ljava/lang/String;");
jstring msg = (*env)->GetObjectField(env, cls, field); //msg就是对应Demo的msg
jfieldID field2 = (*env)->GetStaticFieldID(env,obj,"COUNT","I");
jint count = (*env)->GetStaticIntField(env,cls,field2);
5.访问构造函数
jclass cls = (*env)->FindClass(env, "jni/test/Demo");
首先通过类的名称获取类的定义,相当于Java中的Class.forName方法
jmethodID mid = (*env)->GetMethodID(env,cls,"<init>","(Ljava/lang/String;)V ");
jobject demo = (*env)->NewObject( env ,cls,mid,0 );
访问构造函数必须使用NewObject的函数来调用前面获取的构造函数的定义
6.数组处理
(*env)-> NewIntArray(env, 10);
(*env)->SetIntArrayRegion(env, array, i-1, 1, &i);
访问数组首先应该知道数组的长度及元素的类型。现在我们把创建的数组中的每个元素值打印出来,代码如下:
int len = (*env)->GetArrayLength(env, array);
jint* elems = (*env)-> GetIntArrayElements(env, array, 0);
printf("ELEMENT %d IS %d/n", i, elems[i]);
7.中文处理
· 方法一,将Java中文字符串转为本地字符串
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
int JStringToChar(JNIEnv *env, jstring str, LPTSTR desc, int desc_len)
//在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型
wchar_t *w_buffer = new wchar_t[1024];
ZeroMemory(w_buffer,1024*sizeof(wchar_t));
//使用GetStringChars而不是GetStringUTFChars
wcscpy(w_buffer,env->GetStringChars(str,0));
env->ReleaseStringChars(str,w_buffer);
//调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串
//关于函数WideCharToMultiByte的使用请参考MSDN
len = WideCharToMultiByte(CP_ACP,0,w_buffer,1024,desc,desc_len,NULL,NULL);
}
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
· 方法二,将C的字符串转为Java能识别的Unicode字符串
jstring NewJString(JNIEnv* env,LPCTSTR str)
jchar* buffer = new jchar[slen];
int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen);
jstring js = env->NewString(buffer,len);
8.异常
9.线程和同步访问
DWORD WINAPI ThreadProc(PVOID dwParam)
JavaVM jvm = (JavaVM*)dwParam; /* 将虚拟机通过参数传入 */
(*jvm)-> AttachCurrentThread(jvm, & env, NULL);
(*jvm)-> DetachCurrentThread(jvm);
}
< !--[if !supportLineBreakNewLine]-->
< !--[endif]-->
10.时间
由于rt.jar有差不多10MB,但是其中有很大一部分文件并不需要,可以根据实际的应用情况进行删除。例如程序如果没有用到Java Swing,就可以把涉及到Swing的文件都删除后重新打包。
Chap15:基本JNI调用技术(c/c++与java互调)
在C/C++中调用Java的方法一般分为五个步骤:初始化虚拟机、获取类、创建类对象、调用方法和退出虚拟机。
options[0].optionString = "-Djava.compiler=NONE";
options[1].optionString = "-Djava.class.path=.";
options[2].optionString = "-verbose:jni";
vm_args.version = JNI_VERSION_1_4;
vm_args.ignoreUnrecognized = JNI_TRUE;
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
一个应用程序只需要一个虚拟机,但是每个线程需要自己的虚拟机运行环境。我们从一个虚拟机获取多个当前线程的运行环境,代码如下:
result=jvm->AttachCurrentThread( reinterpret_cast<void**>(& env ), 0 );
在进行方法调用之前,需要先获取相应的类,类名称必须包括包名,其中的“.”用“/”代替。
JavaClass = env->FindClass("com/test/TestInterface");
如果需要调用的方法静态方法,则可以跳过本步骤。反之,则需要构造该对象。构造对象是通过调用类的构造函数来实现的,构咱函数的方法声明为<init>, GetMethodID方法的参数在下一步骤详细说明。
ctor = env->GetMethodID(JavaClass,"<init>","()V");
obj = env->NewObject(JavaClass, ctor);
jmethodID methodID = env->GetMethodID( JavaClass, "setTest","(I)V");
env->CallVoidMethod( obj, methodID,12);
public void setTest(int inTest);
在JNI接口定义中,只有最后一个线程退出时,该方法才会返回,但是我只用一个线程,调用该方法也无法返回。故此建议系统退出时执行该方法,或者整个程序退出时,让虚拟机自己释放。
wchar_t *w_buffer =(wchar_t *)env->GetStringChars(str,0);
env->ReleaseStringChars(str,(const unsigned short *)w_buffer);
C/C++中调用Java时,一定要捕获并处理Java方法抛出的异常信息,否则可能导致C/C++进程的核心转储(Core Dump)。
msg = (jstring)env->CallObjectMethod(obj, mid);
1、 用Java native关键字声明方法为本地方法(非Java语言实现)。
3、 用“javah –jni XXX”命令从该class文件生成C语言头文件(XXX.h)。
4、 采用C语言实现该头文件声明的方法,将实现类编译成库文件(libXXX.so)。
5、 在Java程序中使用System.loadLibrary(XXX)加载该库文件(需要设置-Djava.library.path环境变量指向该库文件存放路径)。
6、 即可象调用Java方法一样,调用native方式声明的本地方法。
Chap16:JNI的c代码中,另外一个线程获取 JNIEnv
2009-06-19 14:36
JNI 中,JNIEnv* 指针变量只对当前线程有效。如果是其他的线程,需要先获得 JVM* 指针,然后再获得当前线程的JNIEnv* 指针。部分示例代码为:
/** Invoker.cpp, Invoker.java */
#include< jni.h>
#include< stdio.h>
#include "Invoker.h"
#include "invoker_include.h"
JavaVM * jvm;
JNIEnv * static_env;
jobject * jObject; // 线程间公用,必须使用 global reference
jclass c; // 必须使用 global reference
jmethodID m; // 必须使用global reference
/*****************************
* Class: Invoker
* Method: register
* Signature: ()V
*****************************/
JNIEXPORT void JNICALL Java_Invoker_register (JNIEnv *env, jobject arg)
{
jObject = arg;
// printf("object: %x, %x. /n", &arg, &jObject);
printf("[main] Invoker registered. /n");
jclass bgpClass = (*env)->GetObjectClass(env, arg);
jmethodID methodId = (*env)->GetMethodID(env, bgpClass, "invoke", "()V");
printf("[main]-class: %d, method: %d /n", bgpClass, methodId);
(*env)->CallVoidMethod(env, arg, methodId);
// Global reference
(*env)->GetJavaVM(env, &jvm);
jObject = (*env)->NewGlobalRef(env, arg);
c = (*env)->NewGlobalRef(env, bgpClass);
m = (*env)->NewGlobalRef(env, methodId);
start(invoke_java_method);
(*env)->DeleteGlobalRef(env, c); // 手动销毁 global reference
(*env)->DeleteGlobalRef(env, m); // 手动销毁 global reference
(*env)->DeleteGlobalRef(env, jObject);
(*jvm)->DetachCurrentThread(jvm); // 销毁线程
(*jvm)->DestroyJavaVM(jvm); // ?销毁虚拟机
}
// Test method
JNIEXPORT void JNICALL Java_Invoker_println (JNIEnv *env, jobject obj, jstring string)
{
const char *str = (*env)->GetStringUTFChars(env, string, 0);
printf("[main] %s/n",str);
(*env)->ReleaseStringUTFChars(env, string, str);
}
// Callback method 回调函数
int invoke_java_method ()
{
(*jvm)->AttachCurrentThread(jvm, (void**)&static_env, 0); // 获得当前线程可以使用的 JNIEnv * 指针
(*static_env)->CallVoidMethod(static_env, jObject, m); // 调用 Java 方法
printf("[callback] java method invoked, invoker class: %x ... /n", &jObject);
}
chap 17:当JNI遇到多线程--java对象如何被C++中的多个线程访问?
java中要访问C++代码时, 使用JNI是唯一选择. 然而,在多线程的情况下, 可能出现以下问题:
问题描述:
一个java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回.同时把JNI接口的指针JNIEnv *env,和jobject obj保存在DLL中的变量里.
一段时间后,DLL中的消息接收线程接收到服务器发来的消息,并试图通过保存过的env和obj来调用先前的java对象的方法来处理此消息.
然而,JNI文档上说,JNI接口的指针JNIEnv*不能在c++的线程间共享,在我的程序中,如果接收线程试图调用java对象的方法,程序会突然退出.
不知道有没有方法突破JNI接口的指针不能在多个c++线程中共享的限制?
解决办法:
在 http://java.sun.com/docs/books/jni/html/pitfalls.html#29161 提到,
JNI接口指针不可为多个线程共用,但是java虚拟机的JavaVM指针是整个jvm公用的. 于是,在DLL中可以调用:
static JavaVM* gs_jvm;
env->GetJavaVM(&gs_jvm); 来获取JavaVM指针.获取了这个指针后,在DLL中的另一个线程里,可以调用:
JNIEnv *env;
gs_jvm->AttachCurrentThread((void **)&env, NULL);
来将DLL中的线程 "attached to the virtual machine"(不知如何翻译...),同时获得了这个线程在jvm中的 JNIEnv指针.
由于我需要做的是在DLL中的一个线程里改变某个java对象的值,所以,还必须获取那个java对象的jobject指针.同 JNIEnv 指针一样,jobject指针也不能在多个线程中共享. 就是说,不能直接在保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它.幸运的是,可以用
gs_object=env->NewGlobalRef(obj);
来将传入的obj保存到gs_object中,从而其他线程可以使用这个gs_object来操纵那个java对象了.
示例代码如下:
(1)java代码:
//file name: Test.java
import java.io.*;
class Test implements Runnable
{
public int value = 0;
private Thread tx=null;
public Test()
{
tx=new Thread(this,"tx");
}
static
{
System.loadLibrary("Test");
}
public native void setEnev();
public static void main(String args[])
{
Test t = new Test();
t.setEnev();
System.out.println("ok in java main");
t.tx.start ();
try
{
Thread.sleep(10000000);
}catch(Exception e)
{
System.out.println("error in main");
}
}
public void run()
{
try
{
while(true)
{
Thread.sleep(1000);
System.out.println(value);
}
}catch(Exception e)
{
System.out.println("error in run");
}
}
}
(2) DLL代码:
//cpp file name: Test.cpp:
#include "test.h"
#include<windows.h>
#include<stdio.h>
static JavaVM *gs_jvm=NULL;
static jobject gs_object=NULL;
static int gs_i=10;
void WINAPI ThreadFun(PVOID argv)
{
JNIEnv *env;
gs_jvm->AttachCurrentThread((void **)&env, NULL);
jclass cls = env->GetObjectClass(gs_object);
jfieldID fieldPtr = env->GetFieldID(cls,"value","I");
while(1)
{
Sleep(100);
//在DLL中改变外面的java对象的value变量的值.
env->SetIntField(gs_object,fieldPtr,(jint)gs_i++);
}
}
JNIEXPORT void JNICALL Java_Test_setEnev(JNIEnv *env, jobject obj)
{
printf("come into test.dll/n");
//Returns “0” on success; returns a negative value on failure.
int retGvm=env->GetJavaVM(&gs_jvm);
//直接保存obj到DLL中的全局变量是不行的,应该调用以下函数:
gs_object=env->NewGlobalRef(obj);
HANDLE ht=CreateThread( NULL,0,
(LPTHREAD_START_ROUTINE)ThreadFun,0,
NULL,NULL);
printf("the Handle ht is:%d/n",ht);
}
chap 18:JNI在多线程中的应用
引文地址:http://blog.csdn.net/hust_liuX/archive/2006/12/25/1460486.aspx
我在这里将文章整理了一下,重新修改了部分描述和增加了一些重要的说明事项。修改文如下:
问题描述:
一个java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回,同时把JNI接口的指针JNIEnv *env(虚拟机环境指针),和jobject obj保存在DLL中的变量里.
一段时间后,DLL中的消息接收线程接收到服务器发来的消息,
并试图通过保存过的env和obj来调用先前的java对象的方法(相当于JAVA回调方法)来处理此消息.此时程序会突然退出(崩溃).
解决办法:
解决此问题首先要明白造成这个问题的原因。那么崩溃的原因是什么呢?
JNI文档上有明确表述: The JNIEnv
pointer, passed as the first argument to every native method, can only be used in the thread with which it is associated. It is wrong to cache the JNIEnv
interface pointer obtained from one thread, and use that pointer in another thread.
意思就是JNIEnv指针不能直接在多线程中共享使用。上面描述的程序崩溃的原因就在这里:回调时的线程和之前保存变量的线程共享了这个JNIEnv *env指针和jobject obj变量。
在 http://java.sun.com/docs/books/jni/html/other.html#26206 提到,JNIEnv *env指针不可为多个线程共用,但是java虚拟机的JavaVM指针是整个jvm公用的,我们可以通过JavaVM来得到当前线程的JNIEnv指针。
于是,在第一个线程A中调用:
JavaVM* gs_jvm;
env->GetJavaVM(&gs_jvm); //来获取JavaVM指针.获取了这个指针后,将该JavaVM保存起来。
在另一个线程B里,调用
JNIEnv *env;
gs_jvm->AttachCurrentThread((void **)&env, NULL);
//这里就获得了B这个线程在jvm中的JNIEnv指针.
这里还必须获取那个java对象的jobject指针,因为我们要回调JAVA方法.同 JNIEnv 指针一样,jobject指针也不能在多个线程中共享. 就是说,不能直接在保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它.幸运的是,可以用
- gs_object=env->NewGlobalRef(obj);//创建一个全局变量
来将传入的obj(局部变量)保存到gs_object中,从而其他线程可以使用这个gs_object(全局变量)来操纵这个java对象了.
示例代码如下:
(1)java代码:Test.java:
- import java.io.*;
- class Test implements Runnable
- {
- public int value = 0;
- static{ System.loadLibrary("Test");}
- public native void setEnev();//本地方法
- public static void main(String args[]) throws Exception
- {
- Test t = new Test();
- t.setEnev(); //调用本地方法
- while(true)
- {
- Thread.sleep(1000);
- System.out.println(t.value);
- }
- }
- }
(2) DLL代码:Test.cpp:
<!--[if !supportLists]-->1. <!--[endif]-->#include "test.h"
<!--[if !supportLists]-->2. <!--[endif]-->#include<windows.h>
<!--[if !supportLists]-->3. <!--[endif]-->#include<stdio.h>
<!--[if !supportLists]-->4. <!--[endif]-->static JavaVM *gs_jvm=NULL;
<!--[if !supportLists]-->5. <!--[endif]-->static jobject gs_object=NULL;
<!--[if !supportLists]-->6. <!--[endif]-->static intgs_i=10;
<!--[if !supportLists]-->7. <!--[endif]-->
<!--[if !supportLists]-->8. <!--[endif]-->JNIEXPORT void JNICALL Java_Test_setEnev(JNIEnv *env, jobject obj)
<!--[if !supportLists]-->9. <!--[endif]-->{
<!--[if !supportLists]-->10. <!--[endif]--> env->GetJavaVM(&gs_jvm); //保存到全局变量中JVM
<!--[if !supportLists]-->11. <!--[endif]--> //直接赋值obj到DLL中的全局变量是不行的,应该调用以下函数:
<!--[if !supportLists]-->12. <!--[endif]--> gs_object=env->NewGlobalRef(obj);
<!--[if !supportLists]-->13. <!--[endif]-->
<!--[if !supportLists]-->14. <!--[endif]-->HANDLEht=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFun,0,NULL,NULL);
<!--[if !supportLists]-->15. <!--[endif]-->}
<!--[if !supportLists]-->16. <!--[endif]-->
<!--[if !supportLists]-->17. <!--[endif]-->void WINAPI ThreadFun(PVOIDargv)//JNI中线程回调这个方法
<!--[if !supportLists]-->18. <!--[endif]-->{
<!--[if !supportLists]-->19. <!--[endif]-->JNIEnv *env;
<!--[if !supportLists]-->20. <!--[endif]-->gs_jvm->AttachCurrentThread((void**)&env, NULL);
<!--[if !supportLists]-->21. <!--[endif]-->jclass cls = env->GetObjectClass(gs_object);
<!--[if !supportLists]-->22. <!--[endif]-->jfieldID fieldPtr = env->GetFieldID(cls,"value","I");
<!--[if !supportLists]-->23. <!--[endif]-->
<!--[if !supportLists]-->24. <!--[endif]-->while(1)
<!--[if !supportLists]-->25. <!--[endif]-->{
<!--[if !supportLists]-->26. <!--[endif]--> Sleep(100);
<!--[if !supportLists]-->27. <!--[endif]--> //这里改变JAVA对象的属性值(回调JAVA)
<!--[if !supportLists]-->28. <!--[endif]--> env->SetIntField(gs_object,fieldPtr,(jint)gs_i++);
<!--[if !supportLists]-->29. <!--[endif]--> }
<!--[if !supportLists]-->30. <!--[endif]-->}
<!--[if !supportLists]-->31. <!--[endif]-->
<!--[if !supportLists]-->32. <!--[endif]-->
JNI
There are certain constraints that you must keep in mind when writing native methods that are to run in a multithreaded environment. By understanding and programming within these constraints, your native methods will execute safely no matter how many threads simultaneously execute a given native method. For example:
- A JNIEnv pointer is only valid in the thread associated with it. You must not pass this pointer from one thread to another, or cache and use it in multiple threads. The Java virtual machine passes a native method the same JNIEnv pointer in consecutive invocations from the same thread, but passes different JNIEnv pointers when invoking that native method from different threads. Avoid the common mistake of caching the JNIEnv pointer of one thread and using the pointer in another thread.
- Local references are valid only in the thread that created them. You must not pass local references from one thread to another. You should always convert local references to global references whenever there is a possibility that multiple threads may use the same reference.
chap 19:JNI限制(多线程)
JNI限制:
There are certain constraints that you must keep in mind when writing native methods that are to run in a multithreaded environment. By understanding and programming within these constraints, your native methods will execute safely no matter how many threads simultaneously execute a given native method. For example:
A JNIEnv pointer is only valid in the thread associated with it. You must not pass this pointer from one thread to another, or cache and use it in multiple threads.
The Java virtual machine passes a native method the same JNIEnv pointer in consecutive invocations from the same thread, but passes different JNIEnv pointers when invoking that native method from different threads. Avoid the common mistake of caching the JNIEnv pointer of one thread and using the pointer in another thread.
Local references are valid only in the thread that created them. You must not pass local references from one thread to another. You should always convert local references to global references whenever there is a possibility that multiple threads may use the same reference.
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lovingprince/archive/2008/08/19/2793504.aspx
chap 20:使用 Java Native Interface 的最佳实践
JNI 的发展
JNI 自从 JDK 1.1 发行版以来一直是 Java 平台的一部分,并且在 JDK 1.2 发行版中得到了扩展。JDK 1.0 发行版包含一个早期的本机方法接口,但是未明确分隔本机代码和 Java 代码。在这个接口中,本机代码可以直接进入 JVM 结构,因此无法跨 JVM 实现、平台或者甚至各种 JDK 版本进行移植。使用 JDK 1.0 模型升级含有大量本机代码的应用程序,以及开发能支持多个 JVM 实现的本机代码的开销是极高的。
JDK 1.1 中引入的 JNI 支持:
- 版本独立性
- 平台独立性
- VM 独立性
- 开发第三方类库
有一个有趣的地方值得注意,一些较年轻的语言(如 PHP)在它们的本机代码支持方面仍然在努力克服这些问题。
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
2009 年 7 月 27 日
Java™ 本机接口(Java Native Interface,JNI)是一个标准的Java API,它支持将 Java 代码与使用其他编程语言编写的代码相集成。如果您希望利用已有的代码资源,那么可以使用 JNI 作为您工具包中的关键组件 —— 比如在面向服务架构(SOA)和基于云的系统中。但是,如果在使用时未注意某些事项,则 JNI 会迅速导致应用程序性能低下且不稳定。本文将确定 10 大 JNI 编程缺陷,提供避免这些缺陷的最佳实践,并介绍可用于实现这些实践的工具。
Java 环境和语言对于应用程序开发来说是非常安全和高效的。但是,一些应用程序却需要执行纯 Java 程序无法完成的一些任务,比如:
- 与旧有代码集成,避免重新编写。
- 实现可用类库中所缺少的功能。举例来说,在 Java 语言中实现
ping
时,您可能需要 Internet Control Message Protocol (ICMP) 功能,但基本类库并未提供它。 - 最好与使用 C/C++ 编写的代码集成,以充分发掘性能或其他与环境相关的系统特性。
- 解决需要非 Java 代码的特殊情况。举例来说,核心类库的实现可能需要跨包调用或者需要绕过其他 Java 安全性检查。
JNI 允许您完成这些任务。它明确分开了 Java 代码与本机代码(C/C++)的执行,定义了一个清晰的 API 在这两者之间进行通信。从很大程度上说,它避免了本机代码对 JVM 的直接内存引用,从而确保本机代码只需编写一次,并且可以跨不同的 JVM 实现或版本运行。
借助 JNI,本机代码可以随意与 Java 对象交互,获取和设计字段值,以及调用方法,而不会像 Java 代码中的相同功能那样受到诸多限制。这种自由是一把双刃剑:它牺牲 Java 代码的安全性,换取了完成上述所列任务的能力。在您的应用程序中使用 JNI 提供了强大的、对机器资源(内存、I/O 等)的低级访问,因此您不会像普通 Java 开发人员那样受到安全网的保护。JNI 的灵活性和强大性带来了一些编程实践上的风险,比如导致性能较差、出现 bug 甚至程序崩溃。您必须格外留意应用程序中的代码,并使用良好的实践来保障应用程序的总体完整性。
本文介绍 JNI 用户最常遇到的 10 大编码和设计错误。其目标是帮助您认识到并避免它们,以便您可以编写安全、高效、性能出众的 JNI 代码。本文还将介绍一些用于在新代码或已有代码中查找这些问题的工具和技巧,并展示如何有效地应用它们。
JNI 编程缺陷可以分为两类:
- 性能:代码能执行所设计的功能,但运行缓慢或者以某种形式拖慢整个程序。
- 正确性:代码有时能正常运行,但不能可靠地提供所需的功能;最坏的情况是造成程序崩溃或挂起。
1.性能缺陷
1.1.不缓存方法 ID、字段 ID 和类
举例来说,清单 1 展示了调用静态方法所需的 JNI 代码:
int val=1; jmethodID method; jclass cls; cls = (*env)->FindClass(env, "com/ibm/example/TestClass"); if ((*env)->ExceptionCheck(env)) { return ERR_FIND_CLASS_FAILED; } method = (*env)->GetStaticMethodID(env, cls, "setInfo", "(I)V"); if ((*env)->ExceptionCheck(env)) { return ERR_GET_STATIC_METHOD_FAILED; } (*env)->CallStaticVoidMethod(env, cls, method,val); if ((*env)->ExceptionCheck(env)) { return ERR_CALL_STATIC_METHOD_FAILED; } |
当我们每次希望调用方法时查找类和方法 ID 都会产生六个本机调用,而不是第一次缓存类和方法 ID 时需要的两个调用。
缓存会对您应用程序的运行时造成显著的影响。考虑下面两个版本的方法,它们的作用是相同的。清单 2 使用了缓存的字段 ID:
int sumValues2(JNIEnv* env, jobject obj, jobject allValues){ jint avalue = (*env)->GetIntField(env, allValues, a); jint bvalue = (*env)->GetIntField(env, allValues, b); jint cvalue = (*env)->GetIntField(env, allValues, c); jint dvalue = (*env)->GetIntField(env, allValues, d); jint evalue = (*env)->GetIntField(env, allValues, e); jint fvalue = (*env)->GetIntField(env, allValues, f); return avalue + bvalue + cvalue + dvalue + evalue + fvalue; } |
<!--[if !vml]--><!--[endif]--> |
|
int sumValues2(JNIEnv* env, jobject obj, jobject allValues){ jclass cls = (*env)->GetObjectClass(env,allValues); jfieldID a = (*env)->GetFieldID(env, cls, "a", "I"); jfieldID b = (*env)->GetFieldID(env, cls, "b", "I"); jfieldID c = (*env)->GetFieldID(env, cls, "c", "I"); jfieldID d = (*env)->GetFieldID(env, cls, "d", "I"); jfieldID e = (*env)->GetFieldID(env, cls, "e", "I"); jfieldID f = (*env)->GetFieldID(env, cls, "f", "I"); jint avalue = (*env)->GetIntField(env, allValues, a); jint bvalue = (*env)->GetIntField(env, allValues, b); jint cvalue = (*env)->GetIntField(env, allValues, c); jint dvalue = (*env)->GetIntField(env, allValues, d); jint evalue = (*env)->GetIntField(env, allValues, e); jint fvalue = (*env)->GetIntField(env, allValues, f); return avalue + bvalue + cvalue + dvalue + evalue + fvalue } |
清单 2 用3,572 ms 运行了 10,000,000 次。清单 3 用了 86,217 ms — 多花了 24 倍的时间。
1.2.触发数组副本
<!--[if !vml]--><!--[endif]--> |
|
jlong getElement(JNIEnv* env, jobject obj, jlongArray arr_j, int element){ jboolean isCopy; jlong result; jlong* buffer_j = (*env)->GetLongArrayElements(env, arr_j, &isCopy); result = buffer_j[element]; (*env)->ReleaseLongArrayElements(env, arr_j, buffer_j, 0); return result; } jlong getElement2(JNIEnv* env, jobject obj, jlongArray arr_j, int element){ jlong result; (*env)->GetLongArrayRegion(env, arr_j, element,1, &result); return result; } |
<!--[if !vml]--><!--[endif]--> |
|
1.3.回访而不是传递参数
考虑清单 5 中的两个方法,第二个方法假定我们缓存了字段 ID:
int sumValues(JNIEnv* env, jobject obj, jint a, jint b,jint c, jint d, jint e, jint f){ return a + b + c + d + e + f; } int sumValues2(JNIEnv* env, jobject obj, jobject allValues){ jint avalue = (*env)->GetIntField(env, allValues, a); jint bvalue = (*env)->GetIntField(env, allValues, b); jint cvalue = (*env)->GetIntField(env, allValues, c); jint dvalue = (*env)->GetIntField(env, allValues, d); jint evalue = (*env)->GetIntField(env, allValues, e); jint fvalue = (*env)->GetIntField(env, allValues, f); return avalue + bvalue + cvalue + dvalue + evalue + fvalue; } |
<!--[if !vml]--><!--[endif]--> |
|
1.4.错误认定本机代码与 Java 代码之间的界限
<!--[if !vml]--><!--[endif]--> |
|
举例来说,如果我们希望使用 JNI 为某个串行端口提供接口,则可以构造两种不同的接口。第一个版本如清单 6 所示:
/** * Initializes the serial port and returns a java SerialPortConfig objects * that contains the hardware address for the serial port, and holds * information needed by the serial port such as the next buffer * to write data into * * @param env JNI env that can be used by the method * @param comPortName the name of the serial port * @returns SerialPortConfig object to be passed ot setSerialPortBit * and getSerialPortBit calls */ jobject initializeSerialPort(JNIEnv* env, jobject obj, jstring comPortName); /** * Sets a single bit in an 8 bit byte to be sent by the serial port * * @param env JNI env that can be used by the method * @param serialPortConfig object returned by initializeSerialPort * @param whichBit value from 1-8 indicating which bit to set * @param bitValue 0th bit contains bit value to be set */ void setSerialPortBit(JNIEnv* env, jobject obj, jobject serialPortConfig, jint whichBit, jint bitValue); /** * Gets a single bit in an 8 bit byte read from the serial port * * @param env JNI env that can be used by the method * @param serialPortConfig object returned by initializeSerialPort * @param whichBit value from 1-8 indicating which bit to read * @returns the bit read in the 0th bit of the jint */ jint getSerialPortBit(JNIEnv* env, jobject obj, jobject serialPortConfig, jint whichBit); /** * Read the next byte from the serial port * * @param env JNI env that can be used by the method */ void readNextByte(JNIEnv* env, jobject obj); /** * Send the next byte * * @param env JNI env that can be used by the method */ void sendNextByte(JNIEnv* env, jobject obj); |
/** * Initializes the serial port and returns an opaque handle to a native * structure that contains the hardware address for the serial port * and holds information needed by the serial port such as * the next buffer to write data into * * @param env JNI env that can be used by the method * @param comPortName the name of the serial port * @returns opaque handle to be passed to setSerialPortByte and * getSerialPortByte calls */ jlong initializeSerialPort2(JNIEnv* env, jobject obj, jstring comPortName); /** * sends a byte on the serial port * * @param env JNI env that can be used by the method * @param serialPortConfig opaque handle for the serial port * @param byte the byte to be sent */ void sendSerialPortByte(JNIEnv* env, jobject obj, jlong serialPortConfig, jbyte byte); /** * Reads the next byte from the serial port * * @param env JNI env that can be used by the method * @param serialPortConfig opaque handle for the serial port * @returns the byte read from the serial port */ jbyte readSerialPortByte(JNIEnv* env, jobject obj, jlong serialPortConfig); |
<!--[if !vml]--><!--[endif]--> |
|
JNI_编程技术__网文整理(中)相关推荐
- JNI_编程技术__网文整理
Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 Chap 4:用javah产生一个.h文件... 17 Chap5:j ...
- JNI_编程技术__网文整理(下)
1.5.使用大量本地引用而未通知 JVM JNI 函数返回的任何对象都会创建本地引用.举例来说,当您调用 GetObjectArrayElement() 时,将返回对数组中对象的本地引用.考虑清单 8 ...
- Word开发工具Aspose.Words功能演示:在C ++中以编程方式在Word文档中添加或删除页眉和页脚
Word文档中的页眉和页脚用于格式化和显示重要信息,例如主题,章节,页码,Copywrite等.以编程方式使用Word文档时,可能需要添加或删除页眉和页脚.为此,本文将教您如何使用C ++在Word文 ...
- 如何使用C ++以编程方式在Word文档中使用注释?
Microsoft Word使您能够向Word文档添加注释.在诸如建议改进文档或共享文本思想等情况下,注释可能会有所帮助.在某些情况下,需要以编程方式管理评论.为此,本文将教您如何使用C ++在Wor ...
- Spring+Hiberate 多数据源的网文整理
解决方案: http://www.th7.cn/Program/java/2011/10/23/44664.shtml 分析共享Spring配置数据源四种方式(附相应jar包) :http://ww ...
- LabVIEW编程技术邪派高手之「速成指引」
这是我知乎上的同名文章的推广引介,其中也包含了我自己的在学习LabVIEW路上的成长故事. 目前为止,本文应该是知乎上最长.最全的LabVIEW的万字(10905字)面向对象编程技术索引文章,并且还再 ...
- C# Word文档中插入、提取图片,文字替换图片
Download Files: http://www.c-sharpcorner.com/UploadFile/26b237/image-operations-using-word-document- ...
- 优秀的Java程序员应具备哪些编程技术?
想要成为一名合格的java程序猿,需要学习的知识是有很多的,但是基础知识一定要非常牢固,基础不牢固的程序员,随时都会被新的知识和技术所淘汰,下盘不稳风一吹就倒,那么具体作为一个优秀的Java程序员应具 ...
- 【数说】人气网文修炼手册之最深的网文套路知多少
[数说]人气网文修炼手册之最深的网文套路知多少 好的书名能吸引大批读者,网文的书名较之传统文学更要讲究个不俗不涩,更要起到博关注的目的.如今这看似纷繁的网文世界中,实则充斥着无数的套路. 读完本篇,您 ...
- 【人工智能】全球老外正跟你同步修仙!AI垂直文本翻译助力国产网文出海,规模将达300亿!...
转载:大数据文摘 国庆长假已经结束了,假前拟定的计划完成了多少呢? 人多不想出门,应该多看看文学经典充实自己,还是看几部老电影稍微怀个旧? 文摘菌猜,最后肯定只是拿着新买的iPhone 11或者Mat ...
最新文章
- 计算机网络系统--Microsoft Lync 与 腾讯通RTX 对比(转载)
- spring中getBeansWithAnnotation(Class<? extends Annotation> annotationType)方法
- 目标指令c语言是什么,什么是C中的目标文件?
- js tool 方法之删除数组指定项
- 《剑指offer》数值的整数次方
- 后端:50 个 经典 Spring 面试题,值得收藏!
- 金蝶国际公布2020年全年业绩,云业务收入增长45.6%
- app启动页自动跳转源码_Jenkins集成appium自动化测试(Windows篇)
- PostgreSQL 当月最后一天的工作日 , 计算日期是星期几
- 小波变换与傅里叶变换的区别
- 【Erlang开源项目】HTTP客户端ibrowse
- IE8的样式兼容性适应方法【转】
- 电影下载地址大集合,只要是电影就能找到
- 爬虫入门(1)--糗百
- 看看最新的考试 c语言 noip模拟 纯llq原创作品
- spring-rabbitmq Direct reply-to 模式
- Facade - 外观模式
- 大数据与O2O:能看到的三四线城乡互联网的未来
- 谈谈 微软 鲍尔默退休,他错在哪里?该怎么做!
- 单片机怎么学?有必要参加单片机培训班吗?
热门文章
- 科研实习 | 清华大学交叉信息研究院弋力老师招收三维视觉科研实习生
- 百度地图jsApi,地图拖动,中心定位图标不动,准确获取拖动过后中心定位图标所在位置
- 一张图片中多个图标如何通过CSS定位显示?
- Jquery UI常用插件
- 生成验证码 大写、小写字母,数字 java
- 光盘/硬盘“无法复制:数据错误(循环冗余检查)”的解决方案
- Go语言学习系列 -- 大道至简—GO语言最佳实践​​​​​​​
- 中国杰出前十大程序员_10个值得关注的杰出青少年应用程序开发人员
- 清理或破坏病毒流氓若干
- 耿建超英语语法---时态+疑问句