欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > > 文章正文

JNI初次使用---在C++中得到一系列圆并返回ArrayList,jniarraylist

来源: javaer 分享于  点击 39010 次 点评:6

JNI初次使用---在C++中得到一系列圆并返回ArrayList,jniarraylist


目标

整个流程可以简单地描述为:对一幅图片,对它进行某种操作,然后得到一系列的圆作为返回结果。 这里的问题便是,某种操作是在C++中实现的,而我的主要代码部分是在Java中,所以就涉及到了在Java中调用C++的混合编程了。 解决办法是使用JNI的方式,在Java设计的函数应该有如下形式 public ArrayList<Circle> getCircles(String imgUrl) 也就是传入图片url给C++,C++中对图像进行处理,得到很多圆作为结果,然后创建一个ArrayList保存这些圆,并返回这个动态链表。


首先做个简单的C++,Java混编实验

一个HelloWorld方法输出一下“HelloWorld"以及字符串转换cToJava函数。

MyNative.java的编写

很简单的一个类,首先要载入C++的动态链接库,比如我有的是CLib.dll,那么写成 System.loadLibrary("CLib")就好了,后缀名不用加的哦,Java会自动寻找与补充。
方法有2个,1个是HelloWorld方法,还有一个则是用来调用C++中方法的cToJav native关键字表面方法是调用本地资源 static静态方法
public class MyNative
{
	static
	{
		System.loadLibrary( "CLib" );
	}
	public native static void HelloWord();
	public native static String cToJava(byte[] para);
}


编译MyNative.java

我的MyNative.java存在D:\MyEclipse7workspace\Native\src目录中,所以我打算在cmd中先进入这个目录。

可是我输入cd 命令之后,如下,当前目录还是在c:\user。很奇怪呢


原来呀,要首先进入d:盘才行诶,进入d盘的方式是直接输入d:就可以了

之后在用javac命令就好啦,编译.java文件生成.class

接着再生成.h文件,输入javah MyNative就好了


最后目录中的文件如下:


生成DLL

cl -I c:/"Program Files"/Java/jdk1.6.0_10/include -I c:/"Program Files"/Java/jdk1.6.0_10/include/win32  -LD Hello.cpp -FeCLib.dll 

注意-Fe和CLib.dll是连在一起的,如果不放在一起,则CLib.dll会被当做输入文件,然后报错哦!
        这里面有几部分。c:/"Program Files"/Java/jdk1.6.0_10/include 是本地的java home的路径。在include和includewin32目录下面有产生动态连接库需要的几个.h文件,包括jni.h(在所有的实现native方法的c文件里面都要include这个文件)等等。 注意啦,像Program Files这样有空格的字符串要用双引号""包含起来的哦!

几个问题

问题'JAVAC' 不是内部或外部命令

'JAVAC' 不是内部或外部命令,也不是可运行的程序 或批处理文件。

http://jingyan.baidu.com/article/1e5468f924210a484961b7f0.html

不过要说明的是,CLASSPATH只写成.;%JAVA_HOME%\lib是不行的,因为有两个文件夹的jar都要包含。

我开始用.;%JAVA_HOME%\lib这样的方式没成功,后来3个变量都设置成绝对路径才成功的。

环境变量的设置方法是(win7):右键点计算机(开始菜单有,桌面一般也有),选择属性,之后选择高级系统设置,接着打开环境变量。

要修改的是系统变量,我的设置如下

JAVA_HOME = C:\Program Files\Java\jdk1.6.0_10

CLASSPATH = C:\Program Files\Java\jdk1.6.0_10\lib\tools.jar;C:\Program Files\Java\jdk1.6.0_10\jre\lib\rt.jar

PATH = C:\Program Files\Java\jdk1.6.0_10\bin

命令行中测试下,ok啦!可以用windows+R键打开运行,之后填写cmd即可打开命令行。

windows键就是ctrl和alt中间的那个有个窗口图标的键,R是字母键。


问题cl命令找不到

'cl'不是内部或外部命令 我已经安装好了vs,为什么会这样呢? 首先,来看看我的环境变量,在命令行中输入set,会显示环境变量,其中VSXX0COMNTOOLS的就是vs的环境变量,下面显示我安装了3个vs,因为装了太多所以cl不知道该选哪个了么?


输入 call "%vs110COMNTOOLS%"vsvars32.bat设置cl使用vs2012的环境变量,接着测试cl,ok啦


dll文件放置问题

生成dll后,如果按照默认放置,是会出现问题的哦!

按照上面的方式生成dll之后,在工程的bin和src目录下都会出现相应的dll。

命令行方式

我在src目录下运行命令行的java MyNative可以成功,因为src目录下有一个dll。


如果把这个dll去掉,就会出现java.lang.UnsatisfiedLinkError错误。


Eclipse方式

如果不做任何处理,在Eclipse下运行,则会报java.lang.UnsatisfiedLinkError错误。

 解决办法是将产生的.dll文件放到工程目录下边


这样运行就会成功啦。


真正的实验

首先编写要用到的自定义类Circle.java

有x,y以及半径radius三个成员,分别为它们生成get和set方法。
public class Circle {
	int x;
	int y;
	int radius;
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
	public int getRadius() {
		return radius;
	}
	public void setRadius(int radius) {
		this.radius = radius;
	}
	public String toString() {
		return  x + ":" + y + ":" + radius + "\n";
	}
}

接着编写MyNative.java生成调用方法

这里用System.loadLibrary调用FindCircles.dll,本地调用方法则只有一个findCircles即可。
import java.util.ArrayList;

public class MyNative
{
	static
	{
		System.loadLibrary( "FindCircles" );
	}
	public native static ArrayList<Circle> findCircles(String imgUrl);
}
这一回我把它们都放在包c中,那么我进入src目录之后该如何编译呢? 如下蓝色框不行。绿色框ok!

这一回生成的.h文件名字是c_MyNative.h,命名是包名_类名.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class c_MyNative */

#ifndef _Included_c_MyNative
#define _Included_c_MyNative
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     c_MyNative
 * Method:    findCircles
 * Signature: (Ljava/lang/String;)Ljava/util/ArrayList;
 */
JNIEXPORT jobject JNICALL Java_c_MyNative_findCircles
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif


接着编写对应的findCircle.cpp

首先添加头文件 #include "c_MyNative.h"
接着自定义一个在C++中使用的Circle
//自定义圆类型
struct Circle
{
	int x;
	int y;
	int radius;
	Circle()
	{
		x = y = radius = 0;
	}
	Circle(int x1, int y1, int r1)
	{
		x = x1;
		y = y1;
		radius = r1;
	}
};
我打算用vector存储所有的结果圆,所以需要
//加入头文件
#include <vector>
//引入名空间
using namespace std;
//定义动态数组
vector<Circle> circles;
这里名空间之前忘记加了,因为是在cl编译器下编译,所以它总是在vector<Circle>这里提示 缺少";"(在<前面)。 所以名空间还是好重要的哦!!!!!!!!千万不能只加头文件呀!! 之后我们要实现函数 /*
 * Class:     c_MyNative
 * Method:    findCircles
 * Signature: (Ljava/lang/String;)Ljava/util/ArrayList;
 */
JNIEXPORT jobject JNICALL Java_c_MyNative_findCircles
  (JNIEnv *env, jclass thiz, jstring imgUrl)
它的输入是个String,输出则是个ArrayList<Circle> 这个函数的实现步骤主要分为:
  • jstring转换为C++中的char *;
  • 创建一个ArrayList<Circle>类;
  • 将vector<Circle>中的数据存到ArrayList中;
  • 注意,C++中使用的Circle和Java中的Circle是不同的!
首先说一下几个基本的函数, env->FindClass是找类句柄的函数,后边跟的是类的地址,比如java.lang.String,包之间是用'/'来分隔的哦

<span >jclass clsstring = env->FindClass("java/lang/String");</span>

env->GetMethodID获取类的方法,有3个参数,第一个是类句柄,第二个是方法名,第三个则是参数的签名

比如下面第一个"<init">是构造函数,"()V"中()里边的是输入参数,()后边的是返回值,这里输入参数为空,返回void类型。

第二个add是ArrayList的方法,参数是object对象,返回值是boolean,即Z。

第三个getBytes是String的方法,输入是参数,输出是byte[]数组,所以是[B

<pre name="code" class="cpp"><span >jmethodID construct = env->GetMethodID(cls_ArrayList,"<init>","()V");  
jmethodID arrayList_add = env->GetMethodID(cls_ArrayList,"add","(Ljava/lang/Object;)Z"); 
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");</span>

关于使用的参数签名方式有如下几种


jstring转换为C++中的char *

代码如下:
  • 首先通过env->FindClass根据String类在Java中的签名得到对应的jclass。
  • 接着设置编码格式为utf-8,防止出现乱码。
  • utf-8使用挺广泛的哦,在Java Web里边如果想要避免中文乱码,也是设置成utf-8编码格式的。
  • 接着得到String句柄classstring中的方法getBytes的句柄mid。
  • 之后通过env->CallObjectMethod调用getBytes方法将输入jstring imgUrl转换为byte[]
  • 接着得到byte[]数组长度alen,判断它的长度,如果大于0,则为char *rtn申请空间,并用memcpy函数完成字符串拷贝。
  • 最后释放申请的临时空间哦,这个不要忘了,要不就可能出现内存溢出
        char* rtn = NULL;
	jclass clsstring = env->FindClass("java/lang/String");
	jstring strencode = env->NewStringUTF("utf-8");
	jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
	jbyteArray barr= (jbyteArray)env->CallObjectMethod(imgUrl, mid, strencode);
	jsize alen = env->GetArrayLength(barr);
	jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
	if (alen > 0)
	{
			 rtn = (char*)malloc(alen + 1);
			 memcpy(rtn, ba, alen);
			 rtn[alen] = 0;
	}
	env->ReleaseByteArrayElements(barr, ba, 0);

接着创建一个ArrayList<Circle>

  • 首先得到类句柄cls_ArrayList,
  • 之后得到它的构造函数句柄construct
  • 接着调用env->NewObject创建一个ArrayList为obj_ArrayList
  • 最后得到它的创建方法,注意此时并没有注明ArrayList里边装的具体是什么类,用Object对象来代替哦。
//find the ArrayList Object by its signature
	jclass cls_ArrayList = env->FindClass("java/util/ArrayList");
	//"<init>" denotes the construction method 
	//"()V" denotes no input parameters and the return type is void 
	jmethodID construct = env->GetMethodID(cls_ArrayList,"<init>","()V");  
	//generate an instance of ArrayList
	jobject obj_ArrayList = env->NewObject(cls_ArrayList,construct,"");  
	jmethodID arrayList_add = env->GetMethodID(cls_ArrayList,"add","(Ljava/lang/Object;)Z");  
  • 接着找到在Java中自定义的Circle类,我放在包c中,所以是c/Circle,注意不是c.Circle呀!
  • 依然是找到它的构造函数方法
  • env->GetFieldID则是Circle类中定义的成员变量,比如env->GetFiledID(circleClass,"x","I"),变量名为x,类型为int
<pre name="code" class="cpp">        jclass circleClass = env->FindClass("c/Circle");  
	//none argument construct function  
	jmethodID construct_circle = env->GetMethodID(circleClass,"<init>","()V");  
	//接着根据类中变量的名字以及类型得到相应变量
	jfieldID mX = env->GetFieldID(circleClass,"x","I");  
	jfieldID mY = env->GetFieldID(circleClass,"y","I");
	jfieldID mRadius = env->GetFieldID(circleClass,"radius","I");

  • 然后我们创建5个Circle加入到vector里边
        int i;  
	for (i=0;i<5;i++){  
		Circle cirTem(i,i+1,i+2);
		circles.push_back(cirTem);
	}
  • 把vector中的数据依次加入到ArrayList中
               for(i=0;i<circles.size();i++){  
		jobject circleObject = env->NewObject(circleClass,construct_circle);
		Circle cirTem = circles[i];
		env->SetIntField(circleObject, mX, cirTem.x); 
		env->SetIntField(circleObject, mY, cirTem.y);  
		env->SetIntField(circleObject, mRadius, cirTem.radius);  
		env->CallObjectMethod(obj_ArrayList,arrayList_add,circleObject);  
	}  
  • 最后返回创建的ArrayList
return obj_ArrayList;  
最后MyNative.java如下
package c;

import java.util.ArrayList;

public class MyNative
{
	static
	{
		System.loadLibrary( "FindCircles" );
	}
	public native static ArrayList<Circle> findCircles(String imgUrl);
	
	public static void main(String[] args){
		ArrayList<Circle> circles = MyNative.findCircles("abc");
		System.out.println(circles.size());	
		for (int i = 0; i < circles.size(); i++){
			System.out.println(i +" : " + circles.get(i).toString());
		}
	}
}

Web工程的dll放置

这次是在web工程中使用,里边没有了bin目录, 关于dll的放置,如果是在命令行中编译的话,则是在src目录里边必须要有dll。

如果是在MyEclipse工程中run的话,则在工程目录里边要有dll。




相关文章

    暂无相关文章

用户点评