Linux 环境中的 Java JNI 编程

作者:diaoyf  |  文章来源:http://programmerdigest.cn

 

    JNI(Java Native Interface)Java 本机接口,允许Java 代码使用以其它语言编写的代码和代码库。通常情况下,我们在Java中调用C或C++编写的动态库(Windows中的 .dll,Linux中的 .so),来扩展Java的能力。

    从Java程序员角度看,JNI非常底层、代码书写方式怪异,并且有许多“陷阱”。本文从实际运用出发,介绍如何在Linux环境中如何编写、运行 JNI程序,并揭示易犯的错误。

 

 

System V消息队列示例

    我不打算只介绍一个Hello World程序,因为太简单会遗漏很多问题现象,当然太复杂也不适合我们这个短篇文章。举的例子的是开源项目LAJP中的一段程序:Java调用 System V消息队列。

    消息队列是Linux内核提供的进程间通信机制,有个专有名词叫IPC,在Unix/Linux系统中IPC是非常重要的一个组成,我想Java是 因为跨平台的统一而没有将其纳入。

    消息队列可以看作是系统内核管理的一片内存区域,进程A可以向消息队列中发送一块或多块数据,进程B从消息队列中读取这些数据,从而完成数据的传 递。在Linux中提供了两套消息队列机制:POSIX消息队列和System V消息队列,在这里我们只演示System V的消息队列。

LAJP开源架构是提供PHP结合Java,共同构建WEB应用的技术,是有关LAJP开源项目的具体信息可访问LAJP 开发网站:http://code.google.com/p/lajp/

C函数

在实际运用中,一般通过调用系统提供的三个C函数来使用消息队列。

函数1: 创建一个新的消息队列或访问一个已存在的消息队列。

  1.  
  2. int msgget ( key_t key, int msgflg ) ;
  3.  
  4. 参数 1 (key): 用来命名一个消息队列,创建或获取(如果系统中已存在同名称的消息队列则获取,否则创建)。
  5. 参数 2 (msgflg): 用来定义权限等标志。
  6. 返回: 函数执行成功返回一个正值的消息ID,失败返回 -1
  7.  

函数2: 发送数据到消息队列。

  1.  
  2. int msgsnd ( int msgid, const void *msg_ptr, size_t msg_sz, int msgflg ) ;
  3.  
  4. 参数 1 (msgid): 由第一个函数msgget返回的消息队列ID。
  5. 参数 2 (msg_ptr): 要发送的数据块的指针。
  6. 参数 3 (msg_sz): 数据块的长度。
  7. 参数 4 (msgflg): 控制标志。可以是 0 ,也可以是IPC_NOWAIT ( 非阻塞 )
  8. 返回: 成功 0 ,失败 -1
  9.  
  10. msg_ptr是一个结构指针,该结构定义在<sys/msg. h > 中:
  11. struct msgbuf {
  12.     long mtype;     //消息类型,必须大于0
  13.     char mtext [ 1 ] ; //消息体
  14. }
  15.  

函数3: 从消息队列中接收数据。

  1.  
  2. size_t msgrcv ( int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg ) ;
  3.  
  4. 参数 1 (msgid): 由第一个函数msgget返回的消息队列ID。
  5. 参数 2 (msg_ptr): 接收缓冲区结构的指针。
  6. 参数 3 (msg_sz): 缓冲区的长度。
  7. 参数 4 (msgtype): 消息类型
  8. 返回: 成功时返回读入到缓冲区的字节数,失败返回 -1
  9.  

    由于文章篇幅的原因,上面的三个函数的语义并没有描述的非常清楚,但对于JNI的编程已经足够了,和Java的方法相比,C函数有不同的编程习惯:

1. 成功标志: C函数的调用成功和失败总是用-1,0等数字标记,而Java通常是判断是否抛出异常。

2. 数据传入和传出: 从C函数中获取数据或传入数据,通常使用2个参数(数据块指针和长度),而Java有很好的对象封装能力,只用一个byte[]就够了(数组对象的 length属性指定长度)。

 

JNI Java代码

首先根据上面3个函数的定义,创建3个Java方法与之对应:

  1.  
  2. package lajp;
  3.  
  4. /**
  5.  * System V 消息队列JNI
  6.  * @author diaoyf
  7.  *
  8.  */
  9. public class MsgQ
  10. {    
  11.     /**
  12.      * 创建或获得消息队列,返回消息队列标识符msqid.
  13.      * <br>
  14.      * 此方法对应System V消息队列函数 int msgget(key_t key,int msgflg)。
  15.      * Java参数msg_key对应C参数key,msgflag默认为八进制0666
  16.      *
  17.      * @param msg_key 消息队列KEY
  18.      * @return
  19.      * <li>执行成功返回正值int,表示msqid</li>
  20.      * <li>失败返回-1</li>
  21.      */
  22.     public native static int msgget ( int msg_key ) ;
  23.    
  24.     /**
  25.      * 发送消息(将消息加入消息队列)
  26.      * <br>
  27.      * 此方法对应System V消息队列函数 int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg)。
  28.      *
  29.      *
  30.      * @param msqid 消息队列标识符msqid
  31.      * @param type 消息类型
  32.      * @param msg 消息体
  33.      * @param len 消息体长度(在Java中仍冗余这个参数是为了在C中更方便的获取msg的长度)
  34.      * @return
  35.      * <li>0:执行成功</li>
  36.      * <li>-1:执行失败</li>
  37.      */
  38.     public native static int msgsnd ( int msqid, int type, byte [ ] msg, int len ) ;
  39.    
  40.     /**
  41.      * 从消息队列中接收消息,本方法为阻塞方法
  42.      * <br>
  43.      * 此方法对应System V消息队列函数 int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, int msgtype, int msgflg)。
  44.      *
  45.      * @param msqid 消息类型
  46.      * @param msg 消息接收缓冲区
  47.      * @param len 接收缓冲区长度(在Java中仍冗余这个参数是为了在C中更方便的获取msg的长度)
  48.      * @param type 消息类型
  49.      * @return
  50.      * <li>执行成功:接收消息的字节数</li>
  51.      * <li>执行失败:-1</li>
  52.      */
  53.     public native static int msgrcv ( int msqid, byte [ ] msg, int len, int type ) ;
  54. }
  55.  

  msgrcv方法的含义是读取,一般在Java方法的设计中,读取的数据是通过方法返回值传递给调用者,而msgrcv的返回数据放在msg 中,msg同时又作为方法的入参,这叫做“值–结果参数”,在C语言中这种参数很常见,而Java中比较少见。

提示一:
Java方法必须添加”native”修饰字,且无方法体,表示此方法的实现是通过内部(C语言)实现的。

JNI 编译

    JNI的编译需要两步,第一步和普通的Java编译一样,用javac命令从MsgQ.java编译出MsgQ.class,不论用哪种Java开 发环境,编译出的目录总是按照package路径分布的:

  1.  
  2. //假定编译的classpath的根路径在/tmp/msg/,编译后的 MsgQ.class的位置:
  3.  
  4. /tmp/msg/
  5.     |
  6.     |–lajp
  7.           |
  8.           |–MsgQ. class
  9.  

下来用javah命令编译出MsgQ.class对应的.h文件:

  1. $ cd /tmp/lajp
  2.  
  3. $ javah lajp.MsgQ

执行完javah命令后,会在/tmp/lajp/路径中生成文件”lajp_MsgQ.h”。

提示二:
Javah命令仍然是根据classpath和package路径做参照。

编写C代码

查看生成的lajp_MsgQ.h文件:

  1.  
  2. /* DO NOT EDIT THIS FILE – it is machine generated */
  3. #include <jni.h>
  4. /* Header for class lajp_MsgQ */
  5.  
  6. #ifndef _Included_lajp_MsgQ
  7. #define _Included_lajp_MsgQ
  8. #ifdef __cplusplus
  9. extern "C" {
  10. #endif
  11. /*
  12.  * Class:     lajp_MsgQ
  13.  * Method:    msgget
  14.  * Signature: (I)I
  15.  */
  16. JNIEXPORT jint JNICALL Java_lajp_MsgQ_msgget
  17.   ( JNIEnv *, jclass, jint ) ;
  18.  
  19. /*
  20.  * Class:     lajp_MsgQ
  21.  * Method:    msgsnd
  22.  * Signature: (II[BI)I
  23.  */
  24. JNIEXPORT jint JNICALL Java_lajp_MsgQ_msgsnd
  25.   ( JNIEnv *, jclass, jint, jint, jbyteArray, jint ) ;
  26.  
  27. /*
  28.  * Class:     lajp_MsgQ
  29.  * Method:    msgrcv
  30.  * Signature: (I[BII)I
  31.  */
  32. JNIEXPORT jint JNICALL Java_lajp_MsgQ_msgrcv
  33.   ( JNIEnv *, jclass, jint, jbyteArray, jint, jint ) ;
  34.  
  35. #ifdef __cplusplus
  36. }
  37. #endif
  38. #endif
  39.  

    注意在函数的参数列表中多了2个参数,JNIEnv和jclass。JNIEnv是JNI环境内置的函数数组指针,一会儿用到;jclass根据 Java类的变化而不同,在我们的程序中MsgQ.class是静态类,因此这类的jclass指的是类本身,如果是非静态类则指的是对象。

    下面根据lajp_MsgQ.h头文件,编写C的实现代码:

  1. /* 文件名: lajp_MsgQ.c */
  2. /* -------------------------------- */
  3. /*  System V Message queues         */
  4. /*  Java JNI                        */
  5. /* -------------------------------- */
  6. #include <sys/types.h>  
  7. #include <sys/ipc.h>  
  8. #include <sys/msg.h>
  9. #include <sys/sem.h>
  10. #include <sys/shm.h>
  11. #include <stdio.h>  
  12. #include <stdlib.h>  
  13. #include <unistd.h>  
  14. #include <string.h>
  15. #include "lajp_MsgQ.h"
  16.  
  17. #define MSG_MAX 8192 /** 消息最大长度 */
  18.  
  19. /*消息缓冲区*/  
  20. struct message  
  21. {  
  22.     long msg_type;          //消息标识符  
  23.     char msg_text [ MSG_MAX ]// 消息
  24. } ;
  25.  
  26. /*
  27.  * 创建或获得消息队列
  28.  * 参数 key: 消息队列key
  29.  * 返回: 消息队列ID(成功) -1(失败)
  30.  */
  31. JNIEXPORT jint JNICALL Java_lajp_MsgQ_msgget
  32.   ( JNIEnv *env, jclass obj, jint key )
  33. {
  34.     jint msqid;   /* 消息队列标识符 */
  35.     if ( ( msqid = msgget ( key, IPC_CREAT | 0666 ) ) == -1 )
  36.     {
  37.         perror ( "[JNI ERROR]msgget Error" ) ;
  38.     }
  39.  
  40.     return msqid;
  41. }
  42.  
  43. /*
  44.  * 发送消息到消息队列
  45.  * Class:     phpjava_MsgQ
  46.  * Method:    msgsnd
  47.  * Signature: (II[BI)I
  48.  */
  49. JNIEXPORT jint JNICALL Java_lajp_MsgQ_msgsnd
  50.   ( JNIEnv *env, jclass obj, jint msqid, jint mstype, jbyteArray msg, jint mslen )
  51. {
  52.     if ( MSG_MAX < mslen )
  53.     {
  54.         perror ( "[JNI ERROR]msgsnd Error: jbyteArray msg too big." ) ;
  55.     }
  56.  
  57.     /* 消息结构 */
  58.     struct message msgq;
  59.     /* 复制消息类型 */
  60.     msgq. msg_type = mstype;
  61.     /* 复制java传来的消息字节数组 */
  62.     ( *env ) ->GetByteArrayRegion ( env, msg, 0 , mslen, msgq. msg_text ) ;
  63.  
  64.     int ret; /* 函数返回值 */
  65.     /* 调用系统函数msgsnd */
  66.     if ( ( ret = msgsnd ( msqid, &msgq, mslen, 0 ) ) < 0 )  
  67.     {  
  68.         perror ( "[JNI ERROR]msgsnd Error" ) ;  
  69.     }
  70.     return ret;
  71. }
  72.  
  73. /*
  74.  * 从消息队列中获取消息(阻塞)
  75.  * Class:     phpjava_MsgQ
  76.  * Method:    msgrcv
  77.  * Signature: (I[BII)I
  78.  */
  79. JNIEXPORT jint JNICALL Java_lajp_MsgQ_msgrcv
  80.   ( JNIEnv *env, jclass obj, jint msqid, jbyteArray msg, jint mslen, jint mstype )
  81. {
  82.     /* 消息结构 */
  83.     struct message msgq;
  84.     /* 复制消息类型 */
  85.     msgq. msg_type = mstype;
  86.  
  87.     int readmslen;
  88.     /* 从消息队列读出消息,到msgq */  
  89.     if ( ( readmslen = msgrcv ( msqid, &msgq, MSG_MAX, mstype, 0 ) ) < 0 )  
  90.     {  
  91.         perror ( "[JNI ERROR]msgrcv Error" ) ;  
  92.     }
  93.  
  94.     if ( mslen < readmslen )
  95.     {
  96.         perror ( "[JNI ERROR]msgrcv Error: jbyteArray msg too small." ) ;
  97.     }
  98.  
  99.     /* 将msg中的消息复制到java字节数组中 */
  100.     ( *env ) ->SetByteArrayRegion ( env, msg, 0 , readmslen, msgq. msg_text ) ;
  101.    
  102.     /* 返回消息长度 */
  103.     return readmslen;
  104. }
  105.  

    关于Java方法的传参定义是这样的: 基本数据类型(int, float, byte等)传“值”,对象类型(包括String和数组)传“引用”。这条原则同样适用于JNI,Java中的对象数据通过JNI接口传递到C程序中, 或反过来从C程序传递到Java中,需要通过内置的JNI函数做转换。

  1.  
  2. ( *env ) ->GetByteArrayRegion ( env, msg, 0 , mslen, msgq. msg_text ) ;
  3. ( *env ) ->SetByteArrayRegion ( env, msg, 0 , readmslen, msgq. msg_text ) ;
  4.  
  5. //获得Java的byte[]类型参数使用 GetByteArrayRegion函数,此函数将Java传来的字节数组msg,复制0->mslen长度的数据到msgq结构中的 msg_text字段。
  6.  
  7. //SetByteArrayRegion正好相反,复制msgq结构中的 msg_text字段数据,按0->readmslen范围,到Java中的msg字节数组。
  8.  

编译动态库文件

将源文件 lajp_MsgQ.c 和 lajp_MsgQ.h 复制到某个目录中,进入这个目录执行下面的编译命令:

  1. gcc lajp_MsgQ.c --share -I. -I/usr/lib/jvm/java -6 -sun/include -I/usr/lib/jvm/java -6 -sun/include/linux -o liblajpmsgq.so
  2.  

gcc的参数:

# 目标文件: liblajpmsgq.so
# 编译参数:
# --share : 编译为动态库(前缀2个减号)
# -I : 搜索编译JNI需要的.h文件, 注意”/usr/lib/jvm/java-6-sun/”要换成编译环境中的JAVA_HOME路径

部署库文件

编译成功后,会生成库文件“liblajpmsgq.so”,将它复制到任一个”java.library.path”路径 中,”java.library.path”路径可通过一个小程序测出:

  1.  
  2. public class TestPath
  3. {
  4.     public static void main ( String [ ] args )
  5.     {
  6.         System . out . println ( System . getProperties ( ) . getProperty ( "java.library.path" ) ) ;
  7.     }
  8. }

在我的机器上(Ubuntu 8.04)的输出:/usr/lib/jvm/java-6-sun-1.6.0.17/jre/lib/i386/client:/usr/lib /jvm/java-6-sun-1.6.0.17/jre/lib/i386:/usr/lib/jvm/java-6-sun-1.6.0.17/jre/../lib/i386:/usr/lib/jvm/java-6-sun-1.6.0.17/jre/lib/i386/client:/usr/lib/jvm/java-6-sun-1.6.0.17/jre/lib/i386:/usr/lib/xulrunner-addons:/usr/lib/xulrunner-addons:/usr/java/packages/lib/i386:/lib:/usr/lib

 

在Java中调用

写两个Java小程序,一发一收,来测测这个JNI。

发送程序:

  1. import lajp.MsgQ;
  2.  
  3. public class Send
  4. {
  5.     static
  6.     {
  7.         //加载liblajpmsgq.so
  8.         System . loadLibrary ( "lajpmsgq" ) ;
  9.     }
  10.  
  11.     public static void main ( String [ ] args )
  12.     {
  13.         //获得消息id
  14.         int msqid = MsgQ. msgget ( 0xFF ) ;
  15.  
  16.         //消息
  17.         String ms = "通过消息队列发送的一句话!" ;
  18.         //转换为自结数组
  19.         byte [ ] msBytes = ms. getBytes ( ) ;
  20.        
  21.         //发送消息,发送类型定义为9527
  22.         int ret = MsgQ. msgsnd ( msqid, 9527 , msBytes, msBytes. length ) ;
  23.        
  24.         if ( ret == 0 )
  25.         {
  26.             System . out . println ( "发 送成功。" ) ;
  27.         }
  28.         else
  29.         {
  30.             System . out . println ( "发 送失败。" ) ;
  31.         }
  32.     }
  33. }

接收程序:

  1. import lajp.MsgQ;
  2.  
  3. public class Receive
  4. {
  5.     static
  6.     {
  7.         //加载liblajpmsgq.so
  8.         System . loadLibrary ( "lajpmsgq" ) ;
  9.     }
  10.  
  11.     public static void main ( String [ ] args )
  12.     {
  13.         //获得消息id
  14.         int msqid = MsgQ. msgget ( 0xFF ) ;
  15.  
  16.         //定义接收缓冲区
  17.         byte [ ] buf = new byte [ 1024 ] ;       
  18.         //接收消息,接收类型定义为9527
  19.         int len = MsgQ. msgrcv ( msqid, buf, 1024 , 9527 ) ;
  20.        
  21.         if ( len == -1 )
  22.         {
  23.             System . out . println ( "接 收失败。" ) ;
  24.             return ;
  25.         }
  26.        
  27.         String ms = new String ( buf, 0 , len ) ;
  28.         System . out . println ( "接 收到的消息:" + ms ) ;
  29.     }
  30. }

注意:JNI的库文件名是“liblajpmsgq.so”,loadLibrary加载时只写“lajpmsgq”。

消息队列传送消息具有异步性,所以先启动Send或Receive都可以进行消息发送。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值