在Java中调用C/C++本地库

JNI是Java Native Interface的英文缩写, 中文翻译为本地调用, 自从Java 1.1开始就成为了Java标准的一部分.

C/C++是系统级的编程语言, 可以用来开发任何和系统相关的程序和类库, 但是Java本身编写底层的应用比较难实现, 使用JNI可以调用现有的本地库, 极大地灵活了Java的开发.

C/C++的效率是目前最好的语言, 可以使用C/C++来实现一些实时性非常高的部分. C/C++和Java本身都是非常流行的编程语言, 一些大型软件中经常使用语言之间的混合编程.

鉴于目前网络上JNI的文章不是特别多, 我将自己的一些总结写在这里. 如有错漏, 欢迎指正!

Java调用C/C++大概有这样几个步骤

  1. 编写带有native方法的Java类, 使用javac工具编译Java类
  2. 使用javah来生成与native方法对应的头文件
  3. 实现相应的头文件, 并编译为动态链接库(windows下是.dll, linux下是.so)

下面就完整的介绍一个简单的Java调用C/C++的例子, 这个例子是来自http://www.ibm.com/developerworks/cn/education/java/j-jni/index.html, 不过其中有一些错误, 这个文章是非常不错的JNI学习资料, 但是非常古老.

编写Java类

我们来编写一个Sample1的java类

 1 public class Sample1 {
 2     public native int intMethod(int n);
 3     public native boolean booleanMethod(boolean bool);
 4     public native String stringMethod(String text);
 5     public native int intArrayMethod(int[] intArray);
 6
 7     public static void main(String[] args) {
 8         System.loadLibrary("Sample1");//没有后缀名
 9         Sample1 sample = new Sample1();
10         int square = sample.intMethod(5);
11         boolean bool = sample.booleanMethod(true);
12         String text = sample.stringMethod("Java");
13         int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});
14
15         System.out.println("intMethod: " + square);
16         System.out.println("booleanMethod: " + bool);
17         System.out.println("stringMethod: " + text);
18         System.out.println("intArrayMethod: " + sum);
19     }
20 } 

Java Sample1

上面有4个native方法, 分别是4种类型的参数, int, boolean, String, int[].

其中有一句比较重要, 这句话加载了动态类库

System.loadLibrary("Sample1");

在windows下加载的就是Sample1.dll, 在linux下加载的就是Sample1.so.

本文使用的windowws, 所以后面使用Sample1.dll来表示Sample1动态链接库.

注意: 不可以在代码中写上后缀dll或so. 还要保证Sample1.dll在path路径中. 这个Sample1.dll是我们后面需要编译出来的东西.

4个native方法就是我们需要用C来实现的方法.

编译Sample1.java, 使用命令行(windows是cmd, linux下一般是bash)

>javac Sample1.java

可以看到Sample1.class文件

使用javah生成头文件

在命令行中运行

>javah Sample1

可以在目录下看到一个新文件Sample1.h

 1 /* DO NOT EDIT THIS FILE - it is machine generated */
 2 #include <jni.h>
 3 /* Header for class Sample1 */
 4
 5 #ifndef _Included_Sample1
 6 #define _Included_Sample1
 7 #ifdef __cplusplus
 8 extern "C" {
 9 #endif
10 /*
11  * Class:     Sample1
12  * Method:    intMethod
13  * Signature: (I)I
14  */
15 JNIEXPORT jint JNICALL Java_Sample1_intMethod
16   (JNIEnv *, jobject, jint);
17
18 /*
19  * Class:     Sample1
20  * Method:    booleanMethod
21  * Signature: (Z)Z
22  */
23 JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
24   (JNIEnv *, jobject, jboolean);
25
26 /*
27  * Class:     Sample1
28  * Method:    stringMethod
29  * Signature: (Ljava/lang/String;)Ljava/lang/String;
30  */
31 JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
32   (JNIEnv *, jobject, jstring);
33
34 /*
35  * Class:     Sample1
36  * Method:    intArrayMethod
37  * Signature: ([I)I
38  */
39 JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
40   (JNIEnv *, jobject, jintArray);
41
42 #ifdef __cplusplus
43 }
44 #endif
45 #endif

我们可以看到其中有四个函数声明, Java_完整类名_方法名, 完整类名包括了包名, 例如demo.Sample1是完整类名, 对应的这里就是demo_Sample1.

在注释中我们可以看到这样一个东西 Signature, 这个是方法的签名. 关于Signature, 下面通过一个表格来说明.

java类型 Signature 备注
boolean Z  
byte B  
char C  
short S  
int I  
long L  
float F  
double D  
void V  
object L用/分割的完整类名 例如: Ljava/lang/String表示String类型
Array [签名 例如: [I表示int数组, [Ljava/lang/String表示String数组
Method (参数签名)返回类型签名 例如: ([I)I表示参数类型为int数组, 返回int类型的方法

上面头文件的第一个函数声明

JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint);

注释中的签名是 Signature: (I)I

在每个函数的参数列表中都有JNIEnv *和 jobject两个参数, 这两个参数稍候说明.

实现头文件中的函数

可以使用C语言来实现, 也可以使用C++来实现, 下面先说说C语言的实现.

#include "Sample1.h"
#include <string.h>

JNIEXPORT jint JNICALL Java_Sample1_intMethod
  (JNIEnv *env, jobject obj, jint num)
{
    return num * num;
}

JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
  (JNIEnv *env, jobject obj, jboolean boolean)
{
    return !boolean;
}

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, 0);
    return (*env)->NewStringUTF(env, strupr(cap));
}

JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
  (JNIEnv *env, jobject obj, jintArray array)
{
    int i, sum = 0;
    jsize len = (*env)->GetArrayLength(env, array);
    jint *body = (*env)->GetIntArrayElements(env, array, 0);

    for (i = 0; i < len; ++i)
    {
        sum += body[i];
    }
    (*env)->ReleaseIntArrayElements(env, array, body, 0);
    return sum;
}

(*env)->GetStringUTFChars()这个方法, 是用来在Java和C之间转换字符串的, 因为Java本身都使用了双字节的字符, 而C语言本身都是单字节的字符, 所以需要进行转换.

JNIEnv *是每个函数都有的参数, 它包含了很多有用的方法, 使用起来类似Java的反射, 也提供了这样一个编码转换的函数.

GetStringUTFChars()和NewStringUTF(), 第一个是从UTF8转换为C的编码格式, 第二个是根据C的字符串返回一个UTF8字符串.

ReleaseStringUTFChars()是用来释放对象的, 在Java中有虚拟机进行垃圾回收, 但是在C语言中, 这些对象必须手动回收. 否则可能造成内存泄漏.

函数的名字一眼看到就可以猜出功能, jni.h中的大部分函数名都是这样.

如果是C++的话, 这段代码该怎么写?

下面是C++的代码

#include "Sample1.h"
#include <string.h>

JNIEXPORT jint JNICALL Java_Sample1_intMethod
  (JNIEnv *env, jobject obj, jint num)
{
    return num * num;
}

JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
  (JNIEnv *env, jobject obj, jboolean boolean)
{
    return !boolean;
}

JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
  (JNIEnv *env, jobject obj, jstring string)
{
    const char* str = env->GetStringUTFChars(string, 0);
    char cap[128];
    strcpy(cap, str);
    env->ReleaseStringUTFChars(string, 0);
    return env->NewStringUTF(_strupr(cap));
}

JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
  (JNIEnv *env, jobject obj, jintArray array)
{
    int i, sum = 0;
    jsize len = env->GetArrayLength(array);
    jint *body = env->GetIntArrayElements(array, 0);

    for (i = 0; i < len; ++i)
    {
        sum += body[i];
    }
    env->ReleaseIntArrayElements(array, body, 0);
    return sum;
}

C++实现

上述两端代码非常相似, 只有一个不同点

C代码: (*env)->GetStringUTFChars(env, string, 0);

C++代码: env->GetStringUTFChars(string, 0);

C语言中使用的是结构体的函数指针, 而在C++中使用的还是struct, 我们知道struct在C++中和class的功能是几乎一样的, struct也可以用来定义类, 所以env在C++中是个类对象的指针.

编译和运行

方法一、使用的是微软编译器, 编译C语言版的dll

>cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.c -FeSample1.dll

编译C++版本的dll

>cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.cpp -FeSample1.dll

方法二、用vs新建dll项目

1.新建dll空项目:文件-》新建项目-》Win32控制台应用程序-》应用程序类型为DLL-》附加选项为空项目-》完成

2.项目-》属性-》c/c++ -》常规-》附加包含目录是%JAVA_HOME%\include和%JAVA_HOME%\include\win32对应的路径。

3.有的版本的vs可能需要在预处理器中加入_CRT_SECURE_NO_WARNINGS选项。

运行

>java Sample1

注意: 64位版本的JDK可能会在运行时报错:

java.lang.UnsatisfiedLinkError: ...Sample1.dll: Can‘t load IA 32-bit .dll on a AMD 64-bit platform

如果您有这样的错误, 请使用32位的JDK来重新运行.

运行结果如下:

intMethod: 25

booleanMethod: false

stringMethod: JAVA

intArrayMethod: 36

原作者:icejoywoo

出处:http://www.cnblogs.com/icejoywoo/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 短网址: http://goo.gl/ZiZCi

时间: 2024-11-05 22:31:47

在Java中调用C/C++本地库的相关文章

在java中调用Oracle存储过程

在java中调用Oracle存储过程 本文介绍如何通过java来调用Oracle的存储过程 1. 编写存储过程 CREATE OR REPLACE PROCEDURE sp_pro3(sp_name VARCHAR2,sp_sal NUMBER ) IS BEGIN --根据用户名修改工资 UPDATE emp SET sal=sp_sal WHERE ename=sp_name; END; 引入jdbc6.jar,编写测试类Test.java package testOraclePro; im

java中调用kettle转换文件

java中调用kettle转换文件 通过命令行也可以调用,然后java中调用命令行代码也可以:这样没有和java代码逻辑无缝集成.本文说明kettle5.1中如果通过其他API和java代码无缝集成:网上大多数资料都是低版本的,在kettle5.x中已经不能运行. 1.       需要哪些jar文件 以kettle开头的是必须,上图最下面三个也要:红色框中的两个是我测试转换用到的,分别是生成UUID和文件. 要是少了jar文件,运行程序一定报错,大家根据错误到kettle安装目录LIB中找相应

在java中调用python方法

1.http://sourceforge.net/projects/jython/下载jython包,把其中的jython.jar添加到工程目录 示例: 1.摘自:http://blog.csdn.net/anbo724/article/details/6608632 1.在java类中直接执行python语句 import javax.script.*; import org.python.util.PythonInterpreter; import java.io.*; import sta

oracle pl/sql之在java中调用带有输入输出参数的oracle存储过程

只需注意一点输出参数是用CallableStatement里面的registerOutParameter方法注册进去 存储过程: import java.sql.CallableStatement;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.SQLException;import java.sql.Types; //在java中

Java中调用c/c++语言出现Exception in thread &quot;main&quot; java.lang.UnsatisfiedLinkError: Test.testPrint(Ljava/lang/String;)V...错误

错误: Exception in thread "main" java.lang.UnsatisfiedLinkError: Test.testPrint(Ljava/lang/String;)V at Test.testPrint(Native Method) at Test.main(Test.java:8) 解决方法: 昨天一天都在想办法解决这个问题,操作步骤按照教程来的,怎么会出错呢?想了半天,觉得还是dll文件生成的不对.所以,最后决定把codeblocks换成visual

oracle pl/sql之在java中调用无参的oracle存储过程

在java 中调用oracle的存储过程和jdbc的操作类似都是分以下几个步骤 1:加载驱动 2:链接数据库并获得一个数据库链接对象 3:执行语句 4:操作结果集 5:关闭资源 前提是:存储过程已写好 create or replace procedure my_procedure is begin insert into emp(empno,ename) values(9527,'唐伯虎'); end; java程序演示: import java.sql.CallableStatement;i

oracle pl/sql之java中调用oracle有参存储过程

存储过程: create or replace procedure my_procedure(in_no in number,in_name in varchar2) is begin insert into emp(empno,ename) values(in_no,in_name); end; java程序演示: import java.sql.CallableStatement;import java.sql.Connection;import java.sql.DriverManager

Java中调用MatLab返回值

当在Java中使用MatLab函数时,由于语言语法的不同,Matlab返回多个数据时,想在Java中获取到并进行使用.查阅了网上资料,翻箱倒柜加上自己实战,得出方法如下: 如MatLab函数返回的是N个变量值,其中有单个的数值.一维数组.二维数组等. 在Java中,使用Object[] rs=cmatlab.matlabfun(n , ....);接受返回的参数.其中rs[k]代表返回列表中第k个返回的变量的值. 如果rs[k]是单个数值,比如Double型.则使用下面的方法转换为Java中的D

JAVA中调用CMD命令,并输出执行结果

package com.wzw.util; import java.io.BufferedReader; import java.io.InputStreamReader; public class CmdDemo { public static void main(String[] args) { BufferedReader br = null; try { Process p = Runtime.getRuntime().exec("net user"); br = new Bu