JNI初次使用---在C++中得到一系列圆并返回ArrayList,jniarraylist
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是不同的!
<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。
相关文章
- 暂无相关文章
用户点评