各位大神,使用gdb调试jni代码码编写问题求教

博客分类:
注:chap1~13,
JNI 函数编写教程,其中chap5讲得好;
Chap14~, JNIEnv和多线程,其中chap17讲得好。
Chap1:JNI完全手册
  最近在公司里做了一个手机的项目,需要JAVA程序在发送短信的时候和第三方的短信服务器连接。短信接口是用
写的。琢磨了三天,大致搞懂了JNI的主体部分。先将心得整理,希望各位朋友少走弯路。
  首先引用一篇文章,介绍一个简单的JNI的调用的过程。
  JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。
  JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
  简单介绍及应用如下:
  一、JAVA中所需要做的工作
  在JAVA程序中,首先需要在类中声明所调用的库名称,如下:
  static {
  System.loadLibrary(“goodluck”);
  在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。
  还需要对将要调用的方法做本地声明,关键字为native。并且只需要声明,而不需要具 体实现。如下:
  public native static void set(int i);
  public native static int get();
  然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/
的头文件。
  例如程序testdll.java,内容为:
  public class testdll
  static
  System.loadLibrary("goodluck");
  public native static int get();
  public native static void set(int i);
  public static void main(String[] args)
  testdll test = new testdll();
  test.set(10);
  System.out.println(test.get());
  用javac testdll.java编译它,会生成testdll.class。
  再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/
程序调用来生成所需的库文件。
  二、C/
中所需要做的工作
  对于已生成的.h头文件,C/
所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/
所实现的功能了。
  接上例子。我们先看一下testdll.h文件的内容:
  /* DO NOT EDIT THIS FILE - it is machine generated */
  #include
  /* Header for class testdll */
  #ifndef _Included_testdll
  #define _Included_testdll
  #ifdef __cplusplus
  extern "C" {
  #endif
  * Class: testdll
  * Method: get
  * Signature: ()I
  JNIEXPORT jint JNICALL
_testdll_get (JNIEnv *, jclass);
  * Class: testdll
  * Method: set
  * Signature: (I)V
  JNIEXPORT void JNICALL
_testdll_set (JNIEnv *, jclass, jint);
  #ifdef __cplusplus
  #endif
  #endif
  在具体实现的时候,我们只关心两个函数原型
  JNIEXPORT jint JNICALL
_testdll_get (JNIEnv *, jclass); 和
  JNIEXPORT void JNICALL
_testdll_set (JNIEnv *, jclass, jint);
 这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地
的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数
中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
  好,下面我们用testdll.cpp文件具体实现这两个函数:
  #include "testdll.h"
  int i = 0;
  JNIEXPORT jint JNICALL
_testdll_get (JNIEnv *, jclass)
  JNIEXPORT void JNICALL
_testdll_set (JNIEnv *, jclass, jint j)
 编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll
。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。
  我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。
  大体程序如下:
  public class SendSMS {
  static
  System.out.println(System.getProperty("java.library.path"));
  System.loadLibrary("sms");
  public native static int SmsInit();
  public native static int SmsSend(byte[] mobileNo, byte[] smContent);
  在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:
  java.lang.UnsatisfiedLinkError: no sms in java.library.path
  at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
  at java.lang.Runtime.loadLibrary0(Runtime.java:788)
  at java.lang.System.loadLibrary(System.java:834)
  at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)
  at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
  Exception in thread "main"
  指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:
  java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries
  at java.lang.ClassLoader$NativeLibrary.load(Native Method)
  at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
  at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
  at java.lang.Runtime.loadLibrary0(Runtime.java:788)
  at java.lang.System.loadLibrary(System.java:834)
  at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)
  at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
  Exception in thread "main"
  通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编译,操作比较简单!)这个头文件就是
和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。
  /* DO NOT EDIT THIS FILE - it is machine generated */
  #include
  /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */
  #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
  #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
  #ifdef __cplusplus
  extern "C" {
  #endif
  * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
  * Method: SmsInit
  * Signature: ()I
  JNIEXPORT jint JNICALL
_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit
  (JNIEnv *, jclass);
  * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
  * Method: SmsSend
  * Signature: ([B[B)I
  JNIEXPORT jint JNICALL
_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend
  (JNIEnv *, jclass, jbyteArray, jbyteArray);
  #ifdef __cplusplus
  #endif
  #endif
  对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。
  * SMS API
  * Author: yippit
  * Date:
  #ifndef MCS_SMS_H
  #define MCS_SMS_H
  #define DLLEXPORT __declspec(dllexport)
  /*sms storage*/
  #define SMS_SIM 0
  #define SMS_MT 1
  /*sms states*/
  #define SMS_UNREAD 0
  #define SMS_READ 1
  /*sms type*/
  #define SMS_NOPARSE -1
  #define SMS_NORMAL 0
  #define SMS_FLASH 1
  #define SMS_MMSNOTI 2
  typedef struct tagSmsEntry {
   /*index, start from 1*/
   /*read, unread*/
   /*-1-can't parser 0-normal, 1-flash, 2-mms*/
   /*SMS_SIM, SMS_MT*/
  char date[24];
  char number[32];
  char text[144];
  } SmsE
  DLLEXPORT int SmsInit(void);
  DLLEXPORT int SmsSend(char *phonenum, char *content);
  DLLEXPORT int SmsSetSCA(char *sca);
  DLLEXPORT int SmsGetSCA(char *sca);
  DLLEXPORT int SmsSetInd(int ind);
  DLLEXPORT int SmsGetInd(void);
  DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);
  DLLEXPORT int SmsSave
(int flag);
  DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);
  DLLEXPORT int SmsDelete(int storage, int index);
  DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -& read*/
  #endif
  在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。由于
和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。
  Sms.c的程序如下:
  #include "sms.h"
  #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"
  JNIEXPORT jint JNICALL
_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)
  return SmsInit();
  JNIEXPORT jint JNICALL
_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)
  char * pS
  //jsize theArrayLengthJ = (*env)-&GetArrayLength(env,mobileno);
  jbyte * arrayBody = (*env)-&GetByteArrayElements(env,mobileno,0);
  char * pMobileNo = (char *)arrayB
  printf("[%s]\n ", pMobileNo);
  //jsize size = (*env)-&GetArrayLength(env,smscontent);
  arrayBody = (*env)-&GetByteArrayElements(env,smscontent,0);
  pSmscontent = (char *)arrayB
  printf("
Chap2:JNI-百度百科
  JNI是Java Native
的缩写,中文为JAVA本地调用。从
开始,Java Native Interface
(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计
的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。
  使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java
  ·标准的java类库可能不支持你的程序所需的特性。
  ·或许你已经有了一个用其他语言写成的库或程序,而你希望在java程序中使用它。
·你可能需要用底层语言实现一个小型的时间敏感代码,比如汇编,然后在你的java程序中调用这些功能。
  ·编写带有native声明的方法的java类
  ·使用
命令编译所编写的java类
  ·使用 “ javah -jni java类名”
生成扩展名为h的头文件
  ·使用C/C++实现本地方法
  ·将C/C++编写的文件生成动态连接库
  1) 编写java程序:这里以HelloWorld为例。
  代码1:
  class HelloWorld {
  public native void displayHelloWorld();
  static {
  System.loadLibrary("hello");
  public static void main(String[] args) {
  new HelloWorld().displayHelloWorld();
 声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明改方法为native的,并且不能实现。其中方法的参数和返回值在后
面讲述。 Load动态库:System.loadLibrary("hello");加载动态库(我们可以这样理解:我们的方法
displayHelloWorld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载
的。同时需要注意的是
.loadLibrary();的参数“hello”是动态库的名字。
  2) 编译
  没有什么好说的了 javac HelloWorld.java
3) 生成扩展名为h的头文件
javah -jni HelloWorld
头文件的内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
  1. include
  /* Header for class HelloWorld */
  1. ifndef _Included_HelloWorld
  2. define _Included_HelloWorld
  3. ifdef __cplusplus
  extern "C" {
  1. endif
  * Class: HelloWorld
  * Method: displayHelloWorld
  * Signature: ()V
  JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);
  1. ifdef __cplusplus
  1. endif
  2. endif
  (这里我们可以这样理解:这个h文件相当于我们在java里面的接口,这里声明了一个 Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写
程序的时候所使用的方法名必须和这里的一致)。
  4) 编写本地方法实现和由javah命令生成的头文件里面声明的方法名相同的方法。
  代码2:
  1 #include "jni.h"
  2 #include "HelloWorld.h"
  3 //#include other headers
  4 JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)
  printf("Hello world!\n");
 注意代码2中的第1行,需要将jni.h(该文件可以在%JAVA_HOME%/include文件夹下面找到)文件引入,因为在程序中的
jobject等类型都是在该头文件中定义的;另外在第2行需要将HelloWorld.h头文件引入(我是这么理解的:相当于我们在编写java程序的
时候,实现一个接口的话需要声明才可以,这里就是将HelloWorld.h头文件里面声明的方法加以实现。当然不一定是这样)。然后保存为
HelloWorldImpl.c就ok了。
  5) 生成动态库
  这里以在
Windows中为例,需要生成dll文件。在保存HelloWorldImpl.c文件夹下面,使用VC的编译器cl成。 cl
-I%java_home%\include -I%java_home%\include\win32 -LD HelloWorldImp.c
-Fehello.dll
注意:生成的dll文件名在选项-Fe后面配置,这里是hello,因为在HelloWorld.java文件中我们loadLibary的时候使用的名
字是hello。当然这里修改之后那里也需要修改。另外需要将-I%java_home%\include
-I%java_home%\include\win32参数加上,因为在第四步里面编写本地方法的时候引入了jni.h文件。
6) 运行程序 java HelloWorld就ok.
简要使用例子
  下面是一个简单的例子实现打印一句话的功能,但是用的c的printf最终实现。一般提供给java的jni接口包括一个so文件(封装了c函数的实现)和一个java文件(需要调用path的类)。
  1. JNI的目的是使java方法中能够调用c实现的一些函数,比如以下的java类,就需要调用一个本地函数testjni(一般声明为private native类型),首先需要创建文件weiqiong.java,内容如下:
class weiqiong {
static { System.loadLibrary("testjni");//载入静态库,test函数在其中实现
private native void testjni(); //声明本地调用
public void test()
testjni();
public static void main(String args[])
weiqiong haha = new weiqiong(); haha.test();
  2.然后执行javac weiqiong.java,如果没有报错,会生成一个weiqiong.class。
 3.然后设置classpath为你当前的工作目录,如直接输入命令行:set classpath =
weiqiong.class所在的完整目录(如 c:\test)再执行javah
weiqiong,会生成一个文件weiqiong.h文件,其中有一个函数的声明如下:
  JNIEXPORT void JNICALL Java_weiqiong_testjni (JNIEnv *, jobject);
  4.创建文件testjni.c将上面那个函数实现,内容如下:
  1. include
  2. include
  JNIEXPORT void JNICALL Java_weiqiong_testjni (JNIEnv *env, jobject obj) { printf("haha---------go into c!!!\n"); }
  5.为了生成.so文件,创建makefile文件如下:
 libtestjni.so:testjni.o makefile gcc -Wall -rdynamic -shared -o
libtestjni.so testjni.o testjni.o:testjni.c weiqiong.h gcc -Wall -c
testjni.c -I./ -I/usr/java/j2sdk1.4.0/include
-I/usr/java/j2sdk1.4.0/include/linux cl: rm -rf *.o *.so
注意:gcc前面是tab空,j2sdk的目录根据自己装的j2sdk的具体版本来写,生成的so文件的名字必须是loadLibrary的参数名前加
  6.export LD_LIBRARY_PATH=.,由此设置library路径为当前目录,这样java文件才能找到so文件。一般的做法是将so文件copy到本机的LD_LIBRARY_PATH目录下。
7.执行java weiqiong,打印出结果:“haha---------go into c!!!”
调用中考虑的问题
  在首次使用JNI的时候有些疑问,后来在使用中一一解决,下面就是这些问题的备忘:
  1。 java和c是如何互通的?
  其实不能互通的原因主要是数据类型的问题,jni解决了这个问题,例如那个c文件中的jstring数据类型就是java传入的String对象,经过jni函数的转化就能成为c的char*。
  对应数据类型关系如下表:
 Java 类型 本地c类型 说明 boolean jboolean 无符号,8 位 byte jbyte 无符号,8 位 char jchar
无符号,16 位 short jshort 有符号,16 位 int jint 有符号,32 位 long jlong 有符号,64 位
float jfloat 32 位 double jdouble 64 位 void void N/A
  JNI 还包含了很多对应于不同 Java 对象的引用类型如下图:
  2. 如何将java传入的String参数转换为c的char*,然后使用?
 java传入的String参数,在c文件中被jni转换为jstring的数据类型,在c文件中声明char* test,然后test =
(char*)(*env)-&GetStringUTFChars(env, jstring,
NULL);注意:test使用完后,通知虚拟机平台相关代码无需再访问:(*env)-&ReleaseStringUTFChars(env,
jstring, test);
  3. 将c中获取的一个char*的buffer传递给java?
  这个char*如果是一般的字符串的话,作为string传回去就可以了。如果是含有’\0’的buffer,最好作为bytearray传出,因为可以制定copy的length,如果copy到string,可能到’\0’就截断了。
  有两种方式传递得到的数据:
 一种是在jni中直接new一个byte数组,然后调用函数(*env)-&SetByteArrayRegion(env,
bytearray, 0, len, buffer);将buffer的值copy到bytearray中,函数直接return
bytearray就可以了。
  一种是return错误号,数据作为参数传出,但是java的基本数据类型是传值,对象是传递的引用,所以将这个需要传出的byte数组用某个类包一下,如下:
class RetObj { public byte[] } 这个对象作为函数的参数retobj传出,通过如下函数将retobj中的byte数组赋值便于传出。代码如下:
bytearray = (*env)-&NewByteArray(env,len);
(*env)-&SetByteArrayRegion(env, bytearray, 0, len, buffer);
cls = (*env)-&GetObjectClass(env, retobj);
fid = (*env)-&GetFieldID(env, cls, "retbytes", "[B"]);
(*env)-&SetObjectField(env, retobj, fid, bytearray);
  4. 不知道占用多少空间的buffer,如何传递出去呢?
在jni的c文件中new出空间,传递出去。java的数据不初始化,指向传递出去的空间即可。
对JAVA传入数据的处理
  1. 如果传入的是bytearray的话,作如下处理得到buffer:
char *tmpdata = (char*)(*env)-&GetByteArrayElements(env, bytearray, NULL);
(*env)-&ReleaseByteArrayElements(env, bytearray, tmpdata, 0);
Chap 3:javah命令帮助信息
D:\Program Files\Java\jdk1.6.0_12\bin&javah
用法:javah [选项] &类&
其中 [选项] 包括:
输出此帮助消息并退出
-classpath &路径&
用于装入类的路径
-bootclasspath &路径& 用于装入引导类的路径
输出文件(只能使用 -d 或 -o 中的一个)
生成 JNI样式的头文件(默认)
输出版本信息
启用详细输出
Chap 4:用javah产生一个.h文件
不是完善的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接造访到操作体系底层(如系统硬件等),为此
Java使用native法子来扩大Java程序的功效。   可以将native法子比作Java程序同C程序的接口,其实现步骤:
  1、在Java中声明native()方式,然后编译;
  2、用javah发生一个.h文件;
  3、写一个.cpp文件实现native导出方式,其中须要包括第二步发生的.h文件(注意其中又包孕了JDK带的jni.h文件)
  4、将第三步的.cpp文件编译成动态链接库文件;
  5、在Java中用System.loadLibrary()法子加载第四步发生的动态链接库文件,这个native()办法就可以在Java中被拜访了。
  JAVA本地办法实用的情形
  1.为了使用底层的主机平台的某个特性,而这个特性不能通过JAVA API拜访
  2.为了拜访一个老的体系或者使用一个已有的库,而这个体系或这个库不是用JAVA编写的
  3.为了加快程序的性能,而将一段时光敏感的代码作为本地方式实现。
  首先写好JAVA文件
 * Created on
Author shaoqi
package com.hode.hodeframework.modelupdate,
public class CheckFile
   public native void displayHelloWorld();
   static
 System.loadLibrary("test");
   public static void main(String[] args) {
    new CheckFile().displayHelloWorld(); 
然后依据写好的文件编译成CLASS文件
   然后在classes或bin之类的class根目录下(其中有已经生成的*.class文件)
执行javah -jni com.hode.hodeframework.modelupdate.CheckFile,就会在class根目录下得到一个 com_hode_hodeframework_modelupdate_CheckFile.h的文件
然后依据头文件的内容编写com_hode_hodeframework_modelupdate_CheckFile.c文件
#include "CheckFile.h"
#include 
#include 
JNIEXPORT void JNICALL Java_com_hode_hodeframework_modelupdate_CheckFile_displayHelloWorld(
JNIEnv *env, jobject obj)
   printf("Hello world!
之后编译生成DLL文件如“test.dll”,名称与System.loadLibrary("test")中的名称一致
  vc的编译办法:cl -I%java_home%include -I%java_home%includewin32 -LD com_hode_hodeframework_modelupdate_CheckFile.c -Fetest.dll
  最后在运行时加参数-Djava.library.path=[dll寄存的路径]
Chap5:jni教程(very very good)
本文来源:
摘自IBM DW,如有转载,请声明!
Java 本机接口(Java Native Interface (JNI))是一个本机
接口,它是 Java 软件开发工具箱(Java Software Development Kit (SDK))的一部分。
JNI 允许 Java 代码使用以其它语言(譬如 C 和 C++)编写的代码和代码库。Invocation API(JNI 的一部分)可以用来将 Java 虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用 Java 代码。
涉及 JNI 最常见的两个应用:从 Java 程序调用 C/C++,以及从 C/C++ 程序调用 Java 代码。我们将讨论 Java 本机接口的这两个基本部分以及可能出现的一些更高级的
将带您去了解使用 Java 本机接口的所有步骤。您将学习如何从 Java 应用程序内部调用本机 C/C++ 代码以及如何从本机 C/C++ 应用程序内部调用 Java 代码。
所有示例都是使用 Java、C 和 C++ 代码编写的,并可以移植到 Windows 和基于 UNIX 的平台上。要完全理解这些示例,您必须有一些 Java 语言
经验。此外,您还需要一些 C 或 C++
经验。严格来说,JNI 解决方案可以分成 Java
任务和 C/C++ 编程任务,由不同的程序员完成每项任务。然而,要完全理解 JNI 是如何在两种编程环境中工作的,您必须能够理解 Java 和 C/C++ 代码。
我们还将讲述一些高级主题,包括本机方法的异常处理和多线程。要充分理解本教程,您应该熟悉 Java 平台的安全性模型,并有一些多线程应用程序开发的经验。
这里将关于高级主题的节从较基本的循序渐进 JNI 简介中划分出来。现在,初级 Java 程序员可以先学习本教程的前两部分,掌握之后再开始学习高级主题。
要运行本教程中的示例,您需要下列工具与组件:
Java 编译器
:随 SDK 一起提供的 javac.exe。
Java 虚拟机(JVM)
:随 SDK 一起提供的 java.exe。
本机方法 C 文件生成器
:随 SDK 一起提供的 javah.exe。
定义 JNI 的库文件和本机头文件
。jni.h C 头文件、jvm.lib 和 jvm.dll 或 jvm.so 文件,这些文件都是随 SDK 一起提供的。
能够创建共享库的 C 和 C++ 编译器
。最常见的两个 C 编译器是用于 Windows 的 Visual C++ 和用于基于 UNIX 系统的 cc。
然您可以使用自己喜欢的任何开发环境,但我们将在本教程中使用示例是用随 SDK 一起提供的标准工具和组件编写的。请参阅参考资料来下载
SDK、完整的源文件以及对于完成本教程不可缺少的其它工具。本教程具体地解释了 Sun 的 JNI 实现,该实现被认为是 JNI
解决方案的标准。本教程中没有讨论其它 JNI 实现的详细信息。
在 Java 2 SDK 中,JVM
和运行时支持位于名为 jvm.dll(Windows)或 libjvm.so(UNIX)的共享库文件中。在 Java 1.1 JDK 中,JVM
和运行时支持位于名为 javai.dll(Windows)或 libjava.so(UNIX)的共享库文件中。版本 1.1
的共享库包含运行时以及类库的一些本机方法,但在版本 1.2 中已经不包含运行时,并且本机方法被放在 java.dll 和 libjava.so
中。对于以下 Java 代码,这一变化很重要:
代码是用非 JNI 本机方法编写的(因为使用了 JDK 1.0 中旧的本机方法接口)
通过 JNI Invocation 接口使用了嵌入式 JVM
在两种情况下,在您的本机库能与版本 1.2 一起使用之前,都必须重新链接它们。注:这个变化应该不影响 JNI 程序员实现本机方法 — 只有通过 Invocation API调用 JVM 的 JNI 代码才会受到影响。
果使用随 SDK/JDK 一起提供的 jni.h 文件,则头文件将使用 SDK/JDK 安装目录中的缺省 JVM(jvm.dll 或
libjvm.so)。支持 JNI 的 Java 平台的任何实现都会这么做,或允许您指定 JVM 共享库;然而,完成这方面操作的细节可能会因具体
Java 平台/JVM 实现而有所不同。实际上,许多 JVM 实现根本不支持 JNI。
用Java调用C/C++代码
当无法用 Java 语言编写整个应用程序时,JNI 允许您使用本机代码。在下列典型情况下,您可能决定使用本机代码:
希望用更低级、更快的编程语言去实现对时间有严格要求的代码。
希望从 Java 程序访问旧代码或代码库。
需要标准 Java 类库中不支持的依赖于平台的特性。
从 Java 代码调用 C/C++ 的六个步骤
从 Java 程序调用 C 或 C ++ 代码的过程由六个步骤组成。我们将在下面几页中深入讨论每个步骤,但还是先让我们迅速地浏览一下它们。
编写 Java 代码
。我们将从编写 Java 类开始,这些类执行三个任务:声明将要调用的本机方法;装入包含本机代码的共享库;然后调用该本机方法。
编译 Java 代码
。在使用 Java 类之前,必须成功地将它们编译成字节码。
创建 C/C++ 头文件
。C/C++ 头文件将声明想要调用的本机函数说明。然后,这个头文件与 C/C++ 函数实现(请参阅步骤 4)一起来创建共享库(请参阅步骤 5)。
编写 C/C++ 代码
。这一步实现 C 或 C++ 源代码文件中的函数。C/C++ 源文件必须包含步骤 3 中创建的头文件。
创建共享库文件
。从步骤 4 中创建的 C 源代码文件来创建共享库文件。
运行 Java 程序
。运行该代码,并查看它是否有用。我们还将讨论一些用于解决常见错误的技巧。
步骤 1:编写 Java 代码
我们从编写 Java 源代码文件开始,它将声明本机方法(或方法),装入包含本机代码的共享库,然后实际调用本机方法。
这里是名为 Sample1.java 的 Java 源代码文件的示例:
com.ibm.course.
public class
public native int
intMethod(int
public native boolean
booleanMethod(boolean
public native
String stringMethod(String text);
public native int
intArrayMethod(int
[] intArray);
public static void
main(String[] args) {
System.loadLibrary
("Sample1");
Sample1 sample = new
Sample1();
square = sample.intMethod(5);
bool = sample.booleanMethod(true
String text = sample.stringMethod("JAVA");
sum = sample.intArrayMethod(new int
[] { 1, 1, 2, 3, 5, 8, 13 });
System.out
.println("intMethod: " + square);
System.out
.println("booleanMethod: " + bool);
System.out
.println("stringMethod: " + text);
System.out
.println("intArrayMethod: " + sum);
这段代码做了些什么?
首先,请注意对 native 关键字的使用,它只能随方法一起使用。native 关键字告诉 Java 编译器:方法是用 Java 类之外的本机代码实现的,但其声明却在 Java 中。只能在 Java 类中声明
本机方法,而不能实现它
(但是不能声明为抽象的方法,使用native关键字即可),所以本机方法不能拥有方法主体。
现在,让我们逐行研究一下代码:
从第 3 行到第 6 行,我们声明了四个 native 方法。
在第 10 行,我们装入了包含这些本机方法的实现的共享库文件。(到步骤 5 时,我们将创建该共享库文件。)
最终,从第 12 行到第 15 行,我们调用了本机方法。注:这个操作和调用非本机 Java 方法的操作没有差异。
:基于 UNIX 的平台上的共享库文件通常含有前缀“lib”。在本例中,第 10 行可能是 System.loadLibrary("libSample1");。请一定要注意您在步骤 5:创建共享库文件中生成的共享库文件名。
步骤 2:编译 Java 代码
接下来,我们需要将 Java 代码编译成字节码。完成这一步的方法之一是使用随 SDK 一起提供的 Java 编译器 javac。用来将 Java 代码编译成字节码的命令是:
C:\eclipse\workspace\IBMJNI\src\com\ibm\course\jni&javac Sample1.java
步骤 3:创建 C/C++ 头文件
三步是创建 C/C++ 头文件,它定义本机函数说明。完成这一步的方法之一是使用 javah.exe,它是随 SDK 一起提供的本机方法 C
存根生成器工具。这个工具被设计成用来创建头文件,该头文件为在 Java 源代码文件中所找到的每个 native 方法定义 C
风格的函数。这里使用的命令是:
C:\eclipse\workspace\IBMJNI\bin&javah –classpath ./ –jni com.ibm.course.jni.Sample1
javah工具帮助
Usage: javah [options] &classes&
where [options] include:
Print this help message and exit
-classpath &path&
Path from which to load classes
-bootclasspath &path& Path from which to load bootstrap classes
Output directory
Output file (only one of -d or -o may be used)
Generate JNI-style header file (default)
Print version information
Enable verbose output
Always write output files
&classes& are specified with their fully qualified names (for
instance, java.lang.Object).
在 Sample1.java 上运行 javah.exe 的结果
下面的 Sample1.h 是对我们的 Java 代码运行 javah 工具所生成的 C/C++ 头文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include &jni.h&
/* Header for class com_ibm_course_jni_Sample1 */
#ifndef _Included_com_ibm_course_jni_Sample1
#define _Included_com_ibm_course_jni_Sample1
#ifdef __cplusplus
extern "C" {
com_ibm_course_jni_Sample1
* Signature: (I)I
JNIEXPORT jint JNICALL Java_com_ibm_course_jni_Sample1_intMethod
(JNIEnv *, jobject, jint);
com_ibm_course_jni_Sample1
booleanMethod
* Signature: (Z)Z
JNIEXPORT jboolean JNICALL Java_com_ibm_course_jni_Sample1_booleanMethod
(JNIEnv *, jobject, jboolean);
com_ibm_course_jni_Sample1
stringMethod
* Signature: (Ljava/lang/S)Ljava/lang/S
JNIEXPORT jstring JNICALL Java_com_ibm_course_jni_Sample1_stringMethod
(JNIEnv *, jobject, jstring);
com_ibm_course_jni_Sample1
intArrayMethod
* Signature: ([I)I
JNIEXPORT jint JNICALL Java_com_ibm_course_jni_Sample1_intArrayMethod
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
关于 C/C++ 头文件
如您可能已经注意到的那样,Sample1.h 中的 C/C++ 函数说明和 Sample1.java 中的 Java native
方法声明有很大差异。JNIEXPORT 和 JNICALL 是用于导出函数的、依赖于编译器的指示符。返回类型是映射到 Java 类型的
C/C++ 类型。附录 A:JNI 类型中完整地说明了这些类型。
声明中的一般参数以外,所有这些函数的参数表中都有一个指向 JNIEnv 和 jobject 的指针。指向 JNIEnv
的指针实际上是一个指向函数指针表的指针。正如将要在步骤 4 中看到的,这些函数提供各种用来在 C 和 C++ 中操作 Java 数据的能力。
jobject 参数引用当前对象。因此,如果 C 或 C++ 代码需要引用 Java 函数,则这个 jobject 充当引用或指针,返回调用的 Java 对象。函数名本身是由前缀“Java_”加全限定类名,再加下划线和方法名构成的。
JNI 使用几种映射到 Java 类型的本机定义的 C 类型。这些类型可以分成两类:原始类型和伪类(pseudo-classes)。在 C 中,伪类作为结构实现,而在 C++ 中它们是真正的类。
Java 原始类型直接映射到 C 依赖于平台的类型,如下所示:
类型 jarray 表示通用数组。在 C 中,所有的数组类型实际上只是 jobject 的同义类型。但是,在 C++
中,所有的数组类型都继承了 jarray,jarray 又依次继承了 jobject。下列表显示了 Java 数组类型是如何映射到 JNI C
数组类型的。
这里是一棵对象树,它显示了 JNI 伪类是如何相关的。
步骤 4:编写 C/C++ 代码
当谈到编写 C/C++ 函数实现时,有一点需要牢记:说明必须和 Sample1.h 的函数声明完全一样。我们将研究用于 C 实现和 C++ 实现的完整代码,然后讨论两者之间的差异。
以下是 Sample1.c,它是用 C 编写的实现:
#include "com_ibm_course_jni_Sample1.h"
#include &string.h&
JNIEXPORT jint JNICALL Java_com_ibm_course_jni_Sample1_intMethod
(JNIEnv *env, jobject obj, jint num) {
return num *
JNIEXPORT jboolean JNICALL Java_com_ibm_course_jni_Sample1_booleanMethod
(JNIEnv *env, jobject obj, jboolean boolean) {
JNIEXPORT jstring JNICALL Java_com_ibm_course_jni_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);
return (*env)-&NewStringUTF(env, strupr(cap));
JNIEXPORT jint JNICALL Java_com_ibm_course_jni_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& i++)
sum += body[i];
(*env)-&ReleaseIntArrayElements(env, array, body, 0);
void main(){}
C++ 函数实现
以下是 Sample1.cpp(C++ 实现)
#include "com_ibm_course_jni_Sample1.h"
#include &string.h&
JNIEXPORT jint JNICALL Java_Sample1_intMethod
(JNIEnv *env, jobject obj, jint num) {
return num *
JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
(JNIEnv *env, jobject obj, jboolean 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, str);
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& i++)
sum += body[i];
env-&ReleaseIntArrayElements(array, body, 0);
void main(){}
C 和 C++ 函数实现的比较
一的差异在于用来访问 JNI 函数的方法。在 C 中,JNI 函数调用由“(*env)-&”作前缀,目的是为了取出函数指针所引用的值。在
C++ 中,JNIEnv
类拥有处理函数指针查找的内联成员函数。下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。
jsize len = (*env)-&GetArrayLength(env,array);
C++ 语法:
jsize len =env-&GetArrayLength(array);
步骤 5:创建共享库文件
接下来,我们创建包含本机代码的共享库文件。大多数 C 和 C++ 编译器除了可以创建机器代码可执行文件以外,也可以创建共享库文件。用来创建共享库文件的命令取决于您使用的编译器。下面是在 Windows 和 Solaris 系统上执行的命令。
cl -Ic:\jdk\include -Ic:\jdk\include\win32 -LD Sample1.c -FeSample1.dll
cc -G -I/usr/local/jdk/include -I/user/local/jdk/include/solaris Sample1.c -o Sample1.so
步骤 6:运行 Java 程序
最后一步是运行 Java 程序,并确保代码正确工作。因为必须在 Java 虚拟机中执行所有 Java 代码,所以需要使用 Java 运行时环境。完成这一步的方法之一是使用 java,它是随 SDK 一起提供的 Java 解释器。所使用的命令是:
java com.ibm.course.jni.Sample1
当运行 Sample1.class 程序时,应该获得下列结果:
PROMPT&java Sample1
intMethod: 25
booleanMethod: false
stringMethod: JAVA
intArrayMethod: 33
当使用 JNI 从 Java 程序访问本机代码时,您会遇到许多问题。您会遇到的三个最常见的错误是:
无法找到动态链接
。它所产生的错误消息是:java.lang.UnsatisfiedLinkError。这通常指无法找到共享库,或者无法找到共享库内特定的本机方法。
无法找到共享库文件
。当用 System.loadLibrary(String libname) 方法(参数是文件名)装入库文件时,请确保文件名拼写正确以及没有
指定扩展名。还有,确保库文件的位置在类路径中,从而确保 JVM 可以访问该库文件。
无法找到具有指定说明的方法
。确保您的 C/C++ 函数实现拥有与头文件中的函数说明相同的说明。
从 Java 调用 C 或 C++ 本机代码(虽然不简单)是 Java 平台中一种良好集成的功能。虽然 JNI 支持 C 和 C++,但 C++ 接口更清晰一些并且通常比 C 接口更可取。
如您已经看到的,调用 C 或 C++
本机代码需要赋予函数特殊的名称,并创建共享库文件。当利用现有代码库时,更改代码通常是不可取的。要避免这一点,在 C++
中,通常创建代理代码或代理类,它们有专门的 JNI 所需的命名函数。然后,这些函数可以调用底层库函数,这些库函数的说明和实现保持不变。
Chap6: JNI传递返回值
作为主调方的Java源程序TestJNI.java如下。
代码清单15-4 在Linux平台上调用C函数的例程——TestJNI.java
public class TestJNI
System.loadLibrary("testjni");//载入静态库,test函数在其中实现
private native void testjni(); //声明本地调用
public void test()
testjni();
public static void main(String args[])
TestJNI haha = new TestJNI();
haha.test();
TestJNI.java声明从libtestjni.so(注意Linux平台的动态链接库文件的扩展名是.so)中调用函数testjni()。
在Linux平台上,遵循JNI
规范的动态链接库文件名必须以“lib”开头。例如在上面的Java程序中指定的库文件名为“testjni”,则实际的库文件应该命名为“libtestjni.so”。
编译TestJNI.java,并为C程序生成头文件:
javac TestJNI.java
javah TestJNI
提供testjni()函数的testjni.c源文件如下。
代码清单15-5 在Linux平台上调用C函数的例程——testjni.c
#include &stdio.h&
#include &TestJNI.h&
JNIEXPORT void JNICALL Java_TestJNI_testjni(JNIEnv *env, jobject obj){
printf("haha---------go into c!!!\n");
编写Makefile文件如下,JDK安装的位置请读者自行调整:
libtestjni.so:testjni.o
gcc -rdynamic -shared -o libtestjni.so testjni.o
testjni.o:testjni.c TestJNI.h
gcc -c testjni.c -I./ -I/usr/java/jdk1.6.0_00/include -I/usr/java/jdk1.6.0_00/include/linux
Makefile文件中,我们描述了最终的
libtestjin.so依赖于目标文件testjni.o,而testjni.o则依赖于testjni.c源文件和TestJNI.h头文件。请注
意,我们在将testjni.o连接成动态链接库文件时使用了“-rdynamic”选项。
执行make命令编译testjni.c。Linux平台和在Windows平台上类似,有3种方法可以让Java程序找到并装载动态链接库文件。
— 将动态链接库文件放置在当前路径下。
— 将动态链接库文件放置在LD_LIBRARY_PATH环境变量所指向的路径下。注意这一点和Windows平台稍有区别,Windows平台参考PATH环境变量。
— 在启动JVM时指定选项“-Djava.library.path”,将动态链接库文件放置在该选项所指向的路径下。
从下一节开始,我们开始接触到在JNI
框架内Java调用C程序的一些高级话题,包括如何传递参数
、如何传递数组
、如何传递对象等。
各种类型数据的传递是跨平台、跨语言互操作的永恒话题,更复杂的操作其实都可以分解为各种 基本数据类型的操作。只有掌握了基于各种数据类型的互操作,才能称得上掌握了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);
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.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;
jintA//定义数组对象
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& i++)
printf("ELEMENT %d IS %d\n", i, elems[i]);
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.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);
char* sa[] = { "Hello,", "world!", "JNI
", "很", "好玩" };
for(;i&ARRAY_LENGTH;i++)
jstr = (*env)-&NewStringUTF( env, sa[i] );
(*env)-&SetObjectArrayElement(env, texts, i, jstr);//必须放入jstring
第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 getTopic()
return this.
public void setTopic(String topic)
this.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.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);
objectClass = (*env)-&FindClass(env, "MailInfo");
jfieldID topicFieldId = (*env)-&GetFieldID(env, objectClass,
"topic", "Ljava/lang/S");
for(;i&ARRAY_LENGTH;i++)
(*env)-&SetObjectField(env, obj, topicFieldId, string);
(*env)-&SetObjectArrayElement(env, mails, i, obj);
程序的第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++中有这么一个结构,它用来描述硬盘信息:
//硬盘信息
char name[256];
那么我们需要在Java中定义一个类来与之匹配,声明可以写成这样:
class DiskInfo {
在这个类中,申明一些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();
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调用操作。
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数组看成参数,返回一个指向对应的本地类型的数组的指针。
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 使用对象
提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set
静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或
方法的ID是任何处理域和方法的函数的必须参数。
表C列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
GetFieldID 得到一个实例的域的ID
GetStaticFieldID 得到一个静态的域的ID
GetMethodID 得到一个实例的方法的ID
GetStaticMethodID 得到一个静态方法的ID
※域和方法的函数
如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。
objects对象 Lfully-qualified-class-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;
//数组大小
//获取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/S");
jfieldID ival = (env)-&GetFieldID(objectClass,"serial","I");
//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i & 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数组
全部的C/C++方法实现代码如下:
* 一缕阳光(sundy)版权所有,保留所有权利。
* TODO Jni 中一个从Java到C/C++参数传递测试类
* @author 刘正伟(sundy)
* @see http://www.cnweblog.com/sundy
* @see mailto:
* @version 1.0
* 修改记录:
* ----------------------------------------------------------------------------------------------
// JniManage.cpp : 定义 DLL 应用程序的入口点。
package com.sundy.
#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")
//硬盘信息
char name[256];
/**//*BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
LPTSTR strName = new CHAR[256] ;
(*GetHostName)(strName);
printf("%s\n",strName);
delete [] strN
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)
/**/////输入一个数组,这里输入的是一个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);
// change even array elements
for( i=0; i & 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)
jobjectArray args = 0;
sa[] = { "Hello,", "world!", "JNI", "is", "fun" };
args = (env)-&NewObjectArray(len,(env)-&FindClass("java/lang/String"),0);
for( i=0; i & i++ )
str = (env)-&NewStringUTF(sa[i] );
(env)-&SetObjectArrayElement(args, i, str);
//返回一个结构,这里返回一个硬盘信息的简单结构类型
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/S");
jfieldID ival = (env)-&GetFieldID(objectClass,"serial","I");
//给每一个实例的变量付值
(env)-&SetObjectField(obj,str,(env)-&NewStringUTF("my name is D:"));
(env)-&SetShortField(obj,ival,10);
//返回一个结构数组,返回一个硬盘信息的结构数组
JNIEXPORT jobjectArray JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray
(JNIEnv *env, jobject _obj)
//申明一个object数组
jobjectArray args = 0;
//数组大小
//获取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/S");
jfieldID ival = (env)-&GetFieldID(objectClass,"serial","I");
//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i & 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数组
//将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;
//将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 );
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 );
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. 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. i++) {
System.out.println("name:" + diskList[i].name);
System.out.println("Serial:" + diskList[i].serial);
注:本程序在VS2003,eclipse (jse5.0) winxp sp2编译通过
20:22 sundy 阅读(4406) 评论(21) 编辑 收藏 所属分类: Java
# re: Jni中C++和Java的参数传递
14:35 张磊
请问如果想返回byte[]类型该怎么做 回复 更多评论
# re: Jni中C++和Java的参数传递
08:37 sundy
byte[] jbyteArray 比特型数组
所以你将byte[] 作为一个jbyteArray数组传递就可以了
回复 更多评论
# re: Jni中C++和Java的参数传递
14:46 小影
请问如果我想把在C++里面计算好的一个二维数组传回给java程序接受,该怎么写代码呢?我找了很多这方面的书和资料,都没有关于传递二维数组的介绍,请您给予指导,多谢啦^_^ 回复 更多评论
# re: Jni中C++和Java的参数传递
17:47 sundy
我没有直接传递过二维数组
但我想你可以把试一试二维数组转换成为一个Hashmap的数组传出来。
请参考"如何在Jni中传递出Hashmap的数组?"的一些代码
回复 更多评论
# re: Jni中C++和Java的参数传递
16:32 wangjian
返回一个结构数组时,为什么每个对象的数据都是一样的?即5个Diskinfo的成员值都相同,能不能不相同? 回复 更多评论
# re: Jni中C++和Java的参数传递
16:55 wangjian
我把5个DiskInfo对象的成员serial分别设置为1、2、3、4、5,可是传递到java后5个对象的serial成员值都是5,为什么这样阿?盼回复,多谢! 回复 更多评论
# re: Jni中C++和Java的参数传递
21:51 sundy
//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i & i++ )
//添加到objcet数组中
(env)-&SetObjectArrayElement(args, i, _obj);
你看看设置的_Obj是不是都是同一个??
回复 更多评论
# re: Jni中C++和Java的参数传递
13:32 wangjian
如下所示,我就是把你程序中(env)-&SetShortField(_obj,ival,10)的参数10换成i,结果每个对象都是对象的serial成员值都是4,请问怎样实现多个不同对象的传递?
for(int i=0; i & 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的参数传递
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的参数传递
20:42 wangjian
这样作不对,不过我找到正确的方法了,要用构造函数生成新的对象。 回复 更多评论
# re: Jni中C++和Java的参数传递
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的参数传递
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的参数传递
17:31 Hefe
WideCharToMultiByte();
MultiByteToWideChar();
请问这两个函数实现什么功能,请作者给出代码,多谢!
回复 更多评论
# re: Jni中C++和Java的参数传递
08:47 sundy
@Hefe look here: http://www.google.com/search?hl=zh-CN&lr=lang_zh-CN&q=WideCharToMultiByte
回复 更多评论
# re: Jni中C++和Java的参数传递
17:40 dijk
要在c函数中调用java类的类成员的方法,比如调用JEditorPane类型成员的setText方法,该怎么办? 回复 更多评论
# re: Jni中C++和Java的参数传递
21:33 陈世雄
java中函数的处理中,对于对象类型(非基本类型int,long...)的输入参数,函数中是可以修改输入参数的内容,函数执行完毕,修改仍然是有效的。
jni中 是否也是这样呢?
回复 更多评论
# re: Jni中C++和Java的参数传递
17:50 王文波
向你请教一个问题:我想用jini来调用dll。我在jbuilder中新建的简单的project调用jini运行正常。但是,我现在要对一个工程软件进行二次开发,该软件的
开发也使用jbuilder生成一个project,然后放在指定的路径下就可以了,该软件在运行的时候会自动读取该project。我在这个软件二次开发的project中使用
jini,则总是报错:unsatisfiedlinkError get()。其中get()方法名。请问该怎么解决这个问题?
我的邮箱: 回复 更多评论
# re: Jni中C++和Java的参数传递
21:25 single
# re: Jni中C++和Java的参数传递
20:42 wangjian
这样作不对,不过我找到正确的方法了,要用构造函数生成新的对象。 回复
---------------------------------------------------
能说说方法吗? 回复 更多评论
# re: Jni中C++和Java的参数传递
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的参数传递
15:27 vampire
c的结构提里写有一个**p,指针的指针,在java中该如何封装??? 回复 更多评论
# re: Jni中C++和Java的参数传递
13:13 Focus
for(int i=0; i & 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 *)arrayB
Chap5:使用JNI技术实现java程序调用第三方dll(c/c++)文件的功能
的跨平台的特性深受java
序员们的喜爱,但正是由于它为了实现跨平台的目的,使得它和本地机器的各种内部联系变得很少,大大约束了它的功能,比如与一些硬件设备通信,往往要花费很
大的精力去设计流程编写代码去管理设备端口,而且有一些设备厂商提供的硬件接口已经经过一定的封装和处理,不能直接使用java
程序通过端口和设备通信,这种情况下就得考虑使用java
程序去调用比较擅长同系统打交道的第三方程序,从1.1版本开始的JDK提供了解决这个问题的技术标准:JNI
Native Interface(Java
本地接口)的缩写,本地是相对于java
程序来说的,指直接运行在操作系统之上,与操作系统直接交互的程序.从1.1版本的JDK开始,JNI
就作为标准平台的一部分发行.在JNI
出现的初期是为了Java
程序与本地已编译语言,尤其是C和C++的互操作而设计的,后来经过扩展也可以与c和c++之外的语言编写的程序交互,例如Delphi程序.
技术固然增强了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文件中的方法提供的功能就行了;
的规则使用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方法去调用它,同时要在这个类中把本地文件加载进来,最终的代码如下:
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 );
System.loadLibrary( "TestDll" );//如果执行环境是linux这里加载的是SO文件,如果是windows环境这里加载的是dll文件
使用JDK的javah命令为这个类生成一个包含类中的方法定义的.h文件,可进入到class文件包的根目录下(只要是在
classpath参数中的路径即可),使用javah命令的时候要加上包名javah
test.LinkDll,命令成功后生成一个名为test_LinkDll.h的头文件.
文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated*/
#include &jni
/* Header for class test_LinkDll */
#ifndef _Included_test_LinkDll #define
Included_test_LinkDll
#ifdef __cplusplus extern "C" { #endif
test_LinkDll
readDataTemp
* Signature: (III)Ljava/lang/S
JNIEXPORT jstring JNICALL Java
_test_LinkDll_readDataTemp(JNIEnv *, jobject, jint, jint, jint);
#ifdef __cplusplus } #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
C/C++8位整型
C/C++带符号的8位整型
C/C++无符号的16位整型
C/C++带符号的16位整型
C/C++带符号的32位整型
C/C++带符号的64位

我要回帖

更多关于 jni调用java代码 的文章

 

随机推荐