- 浏览: 153308 次
文章分类
- 全部博客 (205)
- AOSE-计算机网络 (5)
- AOSE-数据库 (2)
- AOSE-UML建模 (10)
- AOSE-软件测试 (2)
- AOSE-个人学习和总结 (1)
- java (8)
- java-功能 (10)
- java-数据 (1)
- java-设计模式 (3)
- java-javaweb-软件架构 (14)
- java-javaweb-消息服务 (7)
- java-javaweb-权限控制 (5)
- java-javaweb-spring (9)
- java-javaweb-hibernate (1)
- java-javaweb-freemarker (1)
- java-javaweb-DWR (1)
- java-javaweb-compass (1)
- java-javaweb-jbpm (4)
- java-android-开发框架 (1)
- java-android (49)
- java-android-性能优化 (5)
- java-android-webRTC (4)
- hadoop (15)
- javascript (4)
- javascript组件 (1)
- javascript-JQuery组件 (5)
- html5 (4)
- other-杂文 (5)
- other-常用 (2)
- other-开源软件 (3)
- other-计算机新型技术 (1)
- Ubuntu (5)
- java-项目管理 (2)
- java-web (1)
- OpenGL (7)
- VC (3)
- java-javaweb-activiti (1)
- 算法 (2)
- 操作系统 (1)
- java-opencv (1)
最新评论
JNI其实是Java Native Interface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其他语言的通信(主要是C&C++)。也许不少人觉 得Java已经足够强大,为什么要需要JNI这种东西呢?我们知道Java是一种平台无关性的语言,平台对于上层的java代码来说是透明的,所以在多数 时间我们是不需要JNI的,但是假如你遇到了如下的三种情况之一呢?
你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
你的Java代码中需要用到某种算法,不过算法是用C实现并封装在动态链接库文件(DLL)当中的。
对于上述的三种情况,如果没有JNI的话,那就会变得异常棘手了。就算找到解决方案了,也是费时费力。其实说到底还是会增加开发和维护的成本。
说了那么多一通废话,现在进入正题。看过JDK源代码的人肯定会注意到在源码里有很多标记成native的方法。这些个方法只有方法签名但是没有方 法体。其实这些naive方法就是我们说的 java native interface。他提供了一个调用(invoke)的接口,然后用C或者C++去实现。我们首先来编写这个“桥梁”.我自己的开发环境是 j2sdk1.4.2_15 + eclipse 3.2 + VC++ 6.0,先在eclipse里建立一个HelloFore的Java工程,然后编写下面的代码。
Java代码
package com.chnic.jni;
public class SayHellotoCPP {
public SayHellotoCPP(){
}
public native void sayHello(String name);
}
一般的第一个程序总是HelloWorld。今天换换口味,把world换成一个名字。我的native本地方法有一个String的参数。会传 递一个name到后台去。本地方法已经完成,现在来介绍下javah这个方法,接下来就要用javah方法来生成一个相对应的.h头文件。
javah是一个专门为JNI生成头文件的一个命令。CMD打开控制台之后输入javah回车就能看到javah的一些参数。在这里就不多介绍 我们要用的是 -jni这个参数,这个参数也是默认的参数,他会生成一个JNI式的.h头文件。在控制台进入到工程的根目录,也就是HelloFore这个目录,然后输 入命令。
Java代码
javah -jni com.chnic.jni.SayHellotoCPP
命令执行完之后在工程的根目录就会发现com_chnic_jni_SayHellotoCPP.h 这个头文件。在这里有必要多句嘴,在执行javah的时候,要输入完整的包名+类名。否则在以后的测试调用过程中会发生java.lang.UnsatisfiedLinkError这个异常。
到这里java部分算是基本完成了,接下来我们来编写后端的C++代码。(用C也可以,只不过cout比printf用起来更快些,所以这里俺偷下 懒用C++)打开VC++首先新建一个Win32 Dynamic-Link library工程,之后选择An empty DLL project空工程。在这里我C++的工程是HelloEnd,把刚刚生成的那个头文件拷贝到这个工程的根目录里。随便用什么文本编辑器打开这个头文 件,发现有一个如下的方法签名。
Cpp代码
/*
* Class: com_chnic_jni_SayHellotoCPP
* Method: sayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv *, jobject, jstring);
仔细观察一下这个方法,在注释上标注类名、方法名、签名(Signature),至于这个签名是做什么用的,我们以后再说。在这里最重要的是 Java_com_chnic_jni_SayHellotoCPP_sayHello这个方法签名。在Java端我们执行 sayHello(String name)这个方法之后,JVM就会帮我们唤醒在DLL里的Java_com_chnic_jni_SayHellotoCPP_sayHello这个方 法。因此我们新建一个C++ source file来实现这个方法。
Cpp代码
#include <iostream.h>
#include "com_chnic_jni_SayHellotoCPP.h"
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv* env, jobject obj, jstring name)
{
const char* pname = env->GetStringUTFChars(name, NULL);
cout << "Hello, " << pname << endl;
}
因为我们生成的那个头文件是在C++工程的根目录不是在环境目录,所以我们要把尖括号改成单引号,至于VC++的环境目录可以在 Tools->Options->Directories里设置。F7编译工程发现缺少jni.h这个头文件。这个头文件可以 在%JAVA_HOME%\include目录下找到。把这个文件拷贝到C++工程目录,继续编译发现还是找不到。原来是因为在我们刚刚生成的那个头文件 里,jni.h这个文件是被 #include <jni.h>引用进来的,因此我们把尖括号改成双引号#include "jni.h",继续编译发现少了jni_md.h文件,接着在%JAVA_HOME%\include\win32下面找到那个头文件,放入到工程根目 录,F7编译成功。在Debug目录里会发现生成了HelloEnd.dll这个文件。
这个时候后端的C++代码也已经完成,接下来的任务就是怎么把他们连接在一起了,要让前端的java程序“认识并找到”这个动态链接库,就必须把这个DLL放在windows path环境变量下面。有两种方法可以做到:
把这个DLL放到windows下面的sysytem32文件夹下面,这个是windows默认的path
复制你工程的Debug目录,我这里是C:\Program Files\Microsoft Visual Studio\MyProjects\HelloEnd\Debug这个目录,把这个目录配置到User variable的Path下面。重启eclipse,让eclipse在启动的时候重新读取这个path变量。
比较起来,第二种方法比较灵活,在开发的时候不用来回copy dll文件了,节省了很多工作量,所以在开发的时候推荐用第二种方法。在这里我们使用的也是第二种,eclipse重启之后打开 SayHellotoCPP这个类。其实我们上面做的那些是不是是让JVM能找到那些DLL文件,接下来我们要让我们自己的java代码“认识”这个动态 链接库。加入System.loadLibrary("HelloEnd");这句到静态初始化块里。
Java代码
package com.chnic.jni;
public class SayHellotoCPP {
static{
System.loadLibrary("HelloEnd");
}
public SayHellotoCPP(){
}
public native void sayHello(String name);
}
这样我们的代码就能认识并加载这个动态链接库文件了。万事俱备,只欠测试代码了,接下来编写测试代码。
Java代码
SayHellotoCPP shp = new SayHellotoCPP();
shp.sayHello("World");
我们不让他直接Hello,World。我们把World传进去,执行代码。发现控制台打印出来Hello, World这句话。就此一个最简单的JNI程序已经开发完成。也许有朋友会对CPP代码里的
Cpp代码
const char* pname = env->GetStringUTFChars(name, NULL);
这句有疑问,这个GetStringUTFChars就是JNI给developer提供的API,我们以后再讲。在这里不得不多句嘴。
因为JNI有一个Native这个特点,一点有项目用了JNI,也就说明这个项目基本不能跨平台了。
JNI调用是相当慢的,在实际使用的之前一定要先想明白是否有这个必要。
因为C++和C这样的语言非常灵活,一不小心就容易出错,比如我刚刚的代码就没有写析构字符串释放内存,对于java developer来说因为有了GC 垃圾回收机制,所以大多数人没有写析构函数这样的概念。所以JNI也会增加程序中的风险,增大程序的不稳定性。
其实在Java代码中,除了对本地方法标注native关键字和加上要加载动态链接库之外,JNI基本上是对上层coder透明的,上层coder调用那些本地方法的时候并不知道这个方法的方法体究竟是在哪里,这个道理就像我们用JDK所提供的API一样。所以在Java中使用JNI还是很简单的,相比之下在C++中调用java,就比前者要复杂的多了。
现在来介绍下JNI里的数据类型。在C++里,编译器会很据所处的平台来为一些基本的 数据类型来分配长度,因此也就造成了平台不一致性,而这个问题在Java中则不存在,因为有JVM的缘故,所以Java中的基本数据类型在所有平台下得到 的都是相同的长度,比如int的宽度永远都是32位。基于这方面的原因,java和c++的基本数据类型就需要实现一些mapping,保持一致性。下面 的表可以概括:
Java类型 本地类型 JNI中定义的别名
int long jint
long _int64 jlong
byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double double jdouble
Object _jobject* jobject
上面的表格是我在网上搜的,放上来给大家对比一下。对于每一种映射的数据类型,JNI的设计者其实已经帮我们取好了相应的别名以方便记忆。如果想了解一些更加细致的信息,可以去看一些jni.h这个头文件,各种数据类型的定义以及别名就被定义在这个文件中。
了解了JNI中的数据类型,下面就来看这次的例子。这次我们用Java来实现一个前端的market(以下就用Foreground代替)用CPP来实现一个后端factory(以下用backend代替)。我们首先还是来编写包含本地方法的java类。
Java代码
package com.chnic.service;
import com.chnic.bean.Order;
public class Business {
static{
System.loadLibrary("FruitFactory");
}
public Business(){
}
public native double getPrice(String name);
public native Order getOrder(String name, int amount);
public native Order getRamdomOrder();
public native void analyzeOrder(Order order);
public void notification(){
System.out.println("Got a notification.");
}
public static void notificationByStatic(){
System.out.println("Got a notification in a static method.");
}
}
这个类里面包含4个本地方法,一个静态初始化块加载将要生成的dll文件。剩下的方法都是很普通的java方法,等会在backend中回调这些方法。这个类需要一个名为Order的JavaBean。
Java代码
package com.chnic.bean;
public class Order {
private String name = "Fruit";
private double price;
private int amount = 30;
public Order(){
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
JavaBean中,我们为两个私有属性赋值,方便后面的例子演示。到此为止除了测试代码之外的Java端的代码就全部高调了,接下来进行生成.h 头文件、建立C++工程的工作,在这里就一笔带过,不熟悉的朋友请回头看第一篇。在工程里我们新建一个名为Foctory的C++ source file 文件,去实现那些native方法。具体的代码如下。
Cpp代码
#include <iostream.h>
#include <string.h>
#include "com_chnic_service_Business.h"
jobject getInstance(JNIEnv* env, jclass obj_class);
JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
jobject obj,
jstring name)
{
const char* pname = env->GetStringUTFChars(name, NULL);
cout << "Before release: " << pname << endl;
if (strcmp(pname, "Apple") == 0)
{
env->ReleaseStringUTFChars(name, pname);
cout << "After release: " << pname << endl;
return 1.2;
}
else
{
env->ReleaseStringUTFChars(name, pname);
cout << "After release: " << pname << endl;
return 2.1;
}
}
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
jobject obj,
jstring name,
jint amount)
{
jclass order_class = env->FindClass("com/chnic/bean/Order");
jobject order = getInstance(env, order_class);
jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V");
env->CallVoidMethod(order, setName_method, name);
jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V");
env->CallVoidMethod(order, setAmount_method, amount);
return order;
}
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
jobject obj)
{
jclass business_class = env->GetObjectClass(obj);
jobject business_obj = getInstance(env, business_class);
jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V");
env->CallVoidMethod(obj, notification_method);
jclass order_class = env->FindClass("com/chnic/bean/Order");
jobject order = getInstance(env, order_class);
jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
jint amount = env->GetIntField(order, amount_field);
cout << "amount: " << amount << endl;
return order;
}
JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
jclass cls,
jobject obj)
{
jclass order_class = env->GetObjectClass(obj);
jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;");
jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
const char* pname = env->GetStringUTFChars(name_str, NULL);
cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
env->CallStaticVoidMethod(cls, notification_method_static);
}
jobject getInstance(JNIEnv* env, jclass obj_class)
{
jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
jobject obj = env->NewObject(obj_class, construction_id);
return obj;
}
可以看到,在我Java中的四个本地方法在这里全部被实现,接下来针对这四个方法来解释下,一些JNI相关的API的使用方法。先从第一个方法讲起吧:
1.getPrice(String name)
这个方法是从foreground传递一个类型为string的参数到backend,然后backend判断返回相应的价格。在cpp的代码中, 我们用GetStringUTFChars这个方法来把传来的jstring变成一个UTF-8编码的char型字符串。因为jstring的实际类型是 jobject,所以无法直接比较。
GetStringUTFChars方法包含两个参数,第一参数是你要处理的jstring对象,第二个参数是否需要在内存中生成一个副本对象。将 jstring转换成为了一个const char*了之后,我们用string.h中带strcmp函数来比较这两个字符串,如果传来的字符串是“Apple”的话我们返回1.2。反之返回 2.1。在这里还要多说一下ReleaseStringUTFChars这个函数,这个函数从字面上不难理解,就是释放内存用的。有点像cpp里的析构函 数,只不过Sun帮我们已经封装好了。由于在JVM中有GC这个东东,所以多数java coder并没有写析构的习惯,不过在JNI里是必须的了,否则容易造成内存泄露。我们在这里在release之前和之后分别打出这个字符串来看一下效果。
粗略的解释完一些API之后,我们编写测试代码。
Java代码
Business b = new Business();
System.out.println(b.getPrice("Apple"));
运行这段测试代码,控制台上打出
Before release: Apple
After release:
你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
你的Java代码中需要用到某种算法,不过算法是用C实现并封装在动态链接库文件(DLL)当中的。
对于上述的三种情况,如果没有JNI的话,那就会变得异常棘手了。就算找到解决方案了,也是费时费力。其实说到底还是会增加开发和维护的成本。
说了那么多一通废话,现在进入正题。看过JDK源代码的人肯定会注意到在源码里有很多标记成native的方法。这些个方法只有方法签名但是没有方 法体。其实这些naive方法就是我们说的 java native interface。他提供了一个调用(invoke)的接口,然后用C或者C++去实现。我们首先来编写这个“桥梁”.我自己的开发环境是 j2sdk1.4.2_15 + eclipse 3.2 + VC++ 6.0,先在eclipse里建立一个HelloFore的Java工程,然后编写下面的代码。
Java代码
package com.chnic.jni;
public class SayHellotoCPP {
public SayHellotoCPP(){
}
public native void sayHello(String name);
}
一般的第一个程序总是HelloWorld。今天换换口味,把world换成一个名字。我的native本地方法有一个String的参数。会传 递一个name到后台去。本地方法已经完成,现在来介绍下javah这个方法,接下来就要用javah方法来生成一个相对应的.h头文件。
javah是一个专门为JNI生成头文件的一个命令。CMD打开控制台之后输入javah回车就能看到javah的一些参数。在这里就不多介绍 我们要用的是 -jni这个参数,这个参数也是默认的参数,他会生成一个JNI式的.h头文件。在控制台进入到工程的根目录,也就是HelloFore这个目录,然后输 入命令。
Java代码
javah -jni com.chnic.jni.SayHellotoCPP
命令执行完之后在工程的根目录就会发现com_chnic_jni_SayHellotoCPP.h 这个头文件。在这里有必要多句嘴,在执行javah的时候,要输入完整的包名+类名。否则在以后的测试调用过程中会发生java.lang.UnsatisfiedLinkError这个异常。
到这里java部分算是基本完成了,接下来我们来编写后端的C++代码。(用C也可以,只不过cout比printf用起来更快些,所以这里俺偷下 懒用C++)打开VC++首先新建一个Win32 Dynamic-Link library工程,之后选择An empty DLL project空工程。在这里我C++的工程是HelloEnd,把刚刚生成的那个头文件拷贝到这个工程的根目录里。随便用什么文本编辑器打开这个头文 件,发现有一个如下的方法签名。
Cpp代码
/*
* Class: com_chnic_jni_SayHellotoCPP
* Method: sayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv *, jobject, jstring);
仔细观察一下这个方法,在注释上标注类名、方法名、签名(Signature),至于这个签名是做什么用的,我们以后再说。在这里最重要的是 Java_com_chnic_jni_SayHellotoCPP_sayHello这个方法签名。在Java端我们执行 sayHello(String name)这个方法之后,JVM就会帮我们唤醒在DLL里的Java_com_chnic_jni_SayHellotoCPP_sayHello这个方 法。因此我们新建一个C++ source file来实现这个方法。
Cpp代码
#include <iostream.h>
#include "com_chnic_jni_SayHellotoCPP.h"
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv* env, jobject obj, jstring name)
{
const char* pname = env->GetStringUTFChars(name, NULL);
cout << "Hello, " << pname << endl;
}
因为我们生成的那个头文件是在C++工程的根目录不是在环境目录,所以我们要把尖括号改成单引号,至于VC++的环境目录可以在 Tools->Options->Directories里设置。F7编译工程发现缺少jni.h这个头文件。这个头文件可以 在%JAVA_HOME%\include目录下找到。把这个文件拷贝到C++工程目录,继续编译发现还是找不到。原来是因为在我们刚刚生成的那个头文件 里,jni.h这个文件是被 #include <jni.h>引用进来的,因此我们把尖括号改成双引号#include "jni.h",继续编译发现少了jni_md.h文件,接着在%JAVA_HOME%\include\win32下面找到那个头文件,放入到工程根目 录,F7编译成功。在Debug目录里会发现生成了HelloEnd.dll这个文件。
这个时候后端的C++代码也已经完成,接下来的任务就是怎么把他们连接在一起了,要让前端的java程序“认识并找到”这个动态链接库,就必须把这个DLL放在windows path环境变量下面。有两种方法可以做到:
把这个DLL放到windows下面的sysytem32文件夹下面,这个是windows默认的path
复制你工程的Debug目录,我这里是C:\Program Files\Microsoft Visual Studio\MyProjects\HelloEnd\Debug这个目录,把这个目录配置到User variable的Path下面。重启eclipse,让eclipse在启动的时候重新读取这个path变量。
比较起来,第二种方法比较灵活,在开发的时候不用来回copy dll文件了,节省了很多工作量,所以在开发的时候推荐用第二种方法。在这里我们使用的也是第二种,eclipse重启之后打开 SayHellotoCPP这个类。其实我们上面做的那些是不是是让JVM能找到那些DLL文件,接下来我们要让我们自己的java代码“认识”这个动态 链接库。加入System.loadLibrary("HelloEnd");这句到静态初始化块里。
Java代码
package com.chnic.jni;
public class SayHellotoCPP {
static{
System.loadLibrary("HelloEnd");
}
public SayHellotoCPP(){
}
public native void sayHello(String name);
}
这样我们的代码就能认识并加载这个动态链接库文件了。万事俱备,只欠测试代码了,接下来编写测试代码。
Java代码
SayHellotoCPP shp = new SayHellotoCPP();
shp.sayHello("World");
我们不让他直接Hello,World。我们把World传进去,执行代码。发现控制台打印出来Hello, World这句话。就此一个最简单的JNI程序已经开发完成。也许有朋友会对CPP代码里的
Cpp代码
const char* pname = env->GetStringUTFChars(name, NULL);
这句有疑问,这个GetStringUTFChars就是JNI给developer提供的API,我们以后再讲。在这里不得不多句嘴。
因为JNI有一个Native这个特点,一点有项目用了JNI,也就说明这个项目基本不能跨平台了。
JNI调用是相当慢的,在实际使用的之前一定要先想明白是否有这个必要。
因为C++和C这样的语言非常灵活,一不小心就容易出错,比如我刚刚的代码就没有写析构字符串释放内存,对于java developer来说因为有了GC 垃圾回收机制,所以大多数人没有写析构函数这样的概念。所以JNI也会增加程序中的风险,增大程序的不稳定性。
其实在Java代码中,除了对本地方法标注native关键字和加上要加载动态链接库之外,JNI基本上是对上层coder透明的,上层coder调用那些本地方法的时候并不知道这个方法的方法体究竟是在哪里,这个道理就像我们用JDK所提供的API一样。所以在Java中使用JNI还是很简单的,相比之下在C++中调用java,就比前者要复杂的多了。
现在来介绍下JNI里的数据类型。在C++里,编译器会很据所处的平台来为一些基本的 数据类型来分配长度,因此也就造成了平台不一致性,而这个问题在Java中则不存在,因为有JVM的缘故,所以Java中的基本数据类型在所有平台下得到 的都是相同的长度,比如int的宽度永远都是32位。基于这方面的原因,java和c++的基本数据类型就需要实现一些mapping,保持一致性。下面 的表可以概括:
Java类型 本地类型 JNI中定义的别名
int long jint
long _int64 jlong
byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double double jdouble
Object _jobject* jobject
上面的表格是我在网上搜的,放上来给大家对比一下。对于每一种映射的数据类型,JNI的设计者其实已经帮我们取好了相应的别名以方便记忆。如果想了解一些更加细致的信息,可以去看一些jni.h这个头文件,各种数据类型的定义以及别名就被定义在这个文件中。
了解了JNI中的数据类型,下面就来看这次的例子。这次我们用Java来实现一个前端的market(以下就用Foreground代替)用CPP来实现一个后端factory(以下用backend代替)。我们首先还是来编写包含本地方法的java类。
Java代码
package com.chnic.service;
import com.chnic.bean.Order;
public class Business {
static{
System.loadLibrary("FruitFactory");
}
public Business(){
}
public native double getPrice(String name);
public native Order getOrder(String name, int amount);
public native Order getRamdomOrder();
public native void analyzeOrder(Order order);
public void notification(){
System.out.println("Got a notification.");
}
public static void notificationByStatic(){
System.out.println("Got a notification in a static method.");
}
}
这个类里面包含4个本地方法,一个静态初始化块加载将要生成的dll文件。剩下的方法都是很普通的java方法,等会在backend中回调这些方法。这个类需要一个名为Order的JavaBean。
Java代码
package com.chnic.bean;
public class Order {
private String name = "Fruit";
private double price;
private int amount = 30;
public Order(){
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
JavaBean中,我们为两个私有属性赋值,方便后面的例子演示。到此为止除了测试代码之外的Java端的代码就全部高调了,接下来进行生成.h 头文件、建立C++工程的工作,在这里就一笔带过,不熟悉的朋友请回头看第一篇。在工程里我们新建一个名为Foctory的C++ source file 文件,去实现那些native方法。具体的代码如下。
Cpp代码
#include <iostream.h>
#include <string.h>
#include "com_chnic_service_Business.h"
jobject getInstance(JNIEnv* env, jclass obj_class);
JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
jobject obj,
jstring name)
{
const char* pname = env->GetStringUTFChars(name, NULL);
cout << "Before release: " << pname << endl;
if (strcmp(pname, "Apple") == 0)
{
env->ReleaseStringUTFChars(name, pname);
cout << "After release: " << pname << endl;
return 1.2;
}
else
{
env->ReleaseStringUTFChars(name, pname);
cout << "After release: " << pname << endl;
return 2.1;
}
}
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
jobject obj,
jstring name,
jint amount)
{
jclass order_class = env->FindClass("com/chnic/bean/Order");
jobject order = getInstance(env, order_class);
jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V");
env->CallVoidMethod(order, setName_method, name);
jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V");
env->CallVoidMethod(order, setAmount_method, amount);
return order;
}
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
jobject obj)
{
jclass business_class = env->GetObjectClass(obj);
jobject business_obj = getInstance(env, business_class);
jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V");
env->CallVoidMethod(obj, notification_method);
jclass order_class = env->FindClass("com/chnic/bean/Order");
jobject order = getInstance(env, order_class);
jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
jint amount = env->GetIntField(order, amount_field);
cout << "amount: " << amount << endl;
return order;
}
JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
jclass cls,
jobject obj)
{
jclass order_class = env->GetObjectClass(obj);
jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;");
jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
const char* pname = env->GetStringUTFChars(name_str, NULL);
cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
env->CallStaticVoidMethod(cls, notification_method_static);
}
jobject getInstance(JNIEnv* env, jclass obj_class)
{
jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
jobject obj = env->NewObject(obj_class, construction_id);
return obj;
}
可以看到,在我Java中的四个本地方法在这里全部被实现,接下来针对这四个方法来解释下,一些JNI相关的API的使用方法。先从第一个方法讲起吧:
1.getPrice(String name)
这个方法是从foreground传递一个类型为string的参数到backend,然后backend判断返回相应的价格。在cpp的代码中, 我们用GetStringUTFChars这个方法来把传来的jstring变成一个UTF-8编码的char型字符串。因为jstring的实际类型是 jobject,所以无法直接比较。
GetStringUTFChars方法包含两个参数,第一参数是你要处理的jstring对象,第二个参数是否需要在内存中生成一个副本对象。将 jstring转换成为了一个const char*了之后,我们用string.h中带strcmp函数来比较这两个字符串,如果传来的字符串是“Apple”的话我们返回1.2。反之返回 2.1。在这里还要多说一下ReleaseStringUTFChars这个函数,这个函数从字面上不难理解,就是释放内存用的。有点像cpp里的析构函 数,只不过Sun帮我们已经封装好了。由于在JVM中有GC这个东东,所以多数java coder并没有写析构的习惯,不过在JNI里是必须的了,否则容易造成内存泄露。我们在这里在release之前和之后分别打出这个字符串来看一下效果。
粗略的解释完一些API之后,我们编写测试代码。
Java代码
Business b = new Business();
System.out.println(b.getPrice("Apple"));
运行这段测试代码,控制台上打出
Before release: Apple
After release:
发表评论
-
规则引擎的使用与实践-介绍与说明
2017-08-02 07:38 5721、说明 在这 ... -
java注释
2017-07-31 08:22 345要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定 ... -
java线程安全
2017-04-09 06:54 550在java中,有 ... -
JAVA_JNI编程
2015-10-10 15:58 498可能我们平时有遇到客户的需求是调用别人封装的DLL去得到相 ... -
java自带的Stack的运用
2014-04-28 16:16 695import java.util.Stack; class ... -
ArrayList和Vector之间的区别
2014-04-18 08:49 558ArrayList和Vector功能类似。整体上ArrayLi ... -
【转】Eclipse程序员要掌握的常用快捷键
2013-12-21 12:49 425判断一个人的编程水 ...
相关推荐
这篇博客“JNI编程(二) —— 让C++和Java相互调用(2)”显然深入探讨了如何利用JNI实现Java与C++之间的互调用。在Java应用程序中,有时为了性能优化或者利用已有的C/C++库,我们需要借助JNI来实现这种跨语言的通信。 ...
总结来说,Android调用C++代码和C++调用Java源码是通过JNI实现的,涉及到Android.mk或CMakeLists.txt的配置、JNI函数的编写以及内存管理等多个方面。正确理解和运用这些知识,能帮助开发者充分利用Android的本地层...
在这个“jni例子——使用int数组”的示例中,我们将深入探讨如何在Java和C/C++之间传递和操作int数组。 1. **JNI基础知识**: - JNI接口提供了Java与本地代码(如C/C++)通信的桥梁,使得开发者可以在Java应用中...
首先,Android调用C++代码主要通过JNI(Java Native Interface)来实现。JNI允许Java代码和其他语言写的代码进行交互。以下是一般步骤: 1. **创建JNI接口**:在Java源码中定义一个包含native关键字的方法,声明...
JNI编程中错误处理非常重要,因为Java层可能无法捕获C++的异常。使用`__android_log_print`进行日志输出有助于调试。 总结,本资料包通过示例详细展示了Android应用如何通过JNI调用C++代码以及C++代码如何反向调用...
### JNI编程指南书知识点概述 #### 一、JNI的基本概念及应用场景 JNI(Java Native Interface)作为Java平台的重要组成部分,其核心价值在于能够使Java程序有效地利用现有的C/C++代码,进而实现性能优化或功能扩展...
总的来说,Java调用C语言编写的本地库需要理解JNI和JNA的工作原理,以及它们如何处理数据类型转换、参数传递和本地库的加载。通过这些技术,开发者可以充分利用C/C++的性能优势,同时享受Java的平台独立性和高级抽象...
标题与描述均指向了一个具体的技术主题——通过JNI(Java Native Interface)在C语言环境中启动Java虚拟机并调用Java类。这一技术应用广泛,尤其是在需要跨语言编程或利用Java的特性(如垃圾回收、多线程)而底层...
1. **JNI (Java Native Interface)**:这是Java提供的一种标准接口,允许Java代码调用本地方法(如C/C++函数)。通过JNI,开发者可以在Java程序中嵌入C++代码以利用其高性能特性。 2. **JNA (Java Native Access)*...
本文将深入探讨“Java与C#相互调用的全新方式”,结合给定的标签“源码”和“工具”,我们将重点讨论如何通过中间件或接口实现这种交互,并提供一种可能的解决方案——AMFCall。 Java和C#的互操作性通常通过以下几...
Java中的JNI(Java Native Interface)是一种技术,允许Java代码与其他编程语言编写的代码进行交互,尤其是C和C++。JNI在许多场景下是必要的,比如访问操作系统特定的功能、提升性能或者利用已有的C/C++库。这篇博客...
Java Native Interface(JNI)是一种Java平台的标准接口,它允许Java代码和其他编程语言(如C/C++)编写的代码进行交互。在本示例中,Java程序通过JNI调用了C/C++实现的DLL(动态链接库)来执行特定的数学运算。以下...
Java编程语言虽然强大,但在某些特定情况下,如与操作系统底层交互或者使用已有的C/C++库时,我们可能需要借助JNI(Java Native Interface)来调用本地代码,比如DLL动态链接库。本教程将深入讲解如何使用Java通过...
总之,Java调用C++主要依赖于JNI技术,通过JNI,开发者可以在Java应用中充分利用C++的高效性能和丰富的库资源。这涉及到Java端的接口声明、C++端的实现以及动态库的加载。通过分析提供的示例工程,你可以更深入地...
- **JNI**: 编写 JNI 接口,让 Java 可以调用这个 C++ 函数。 #### 2. Java 代码示例 ```java public class MatrixInverse { static { System.loadLibrary("MatrixInverse"); } public native void ...
2. **JNI原理**:JNI是Java和本地代码之间的桥梁,通过JNI函数,Java代码可以调用C/C++实现的函数,反之亦然。理解JNI的工作机制,包括如何定义本地方法、如何加载动态链接库以及如何在Java和C++之间传递数据,是...
本资料“安卓Android源码——java调用C例子.zip”聚焦于如何在Android应用中通过Java调用C语言编写的原生代码,这一过程通常称为JNI(Java Native Interface)。 JNI是Java平台标准的一部分,它允许Java代码和其他...