JNI快速入门

JNI快速入门

JNIJava Native Interface Java本地接口,使用此种机制可以实现Java和C/C++互相调用.本文对该机制进行快速入门,

并记录了我在整个过程中遇到的问题及解决方法。


Java调用C++代码

Java调用C++代码本质上是对C++生成的动态库进行调用而不是直接对C/C++代码进行调用

第一步:设计Java端接口;

[java]  view plain copy
  1. public class HelloJNI {  
  2.   
  3.     /** 
  4.      * @param args 
  5.          Native关键字 
  6.      */  
  7.     private native void printMsg();  
  8.        
  9.     public static void main(String[] args) {  
  10.         // TODO Auto-generated method stub  
  11.          new HelloJNI().printMsg();  
  12.     }  
  13.   
  14.      static {  
  15.            //静态代码块中的代码加载dll,保证该类的dll文件只被加载一次  
  16.          System.loadLibrary("HelloJNI");  
  17.             
  18.      }  
  19. }  

命令行编译:javac HelloWorld.java

第二步:使用javah工具生成C++接口文件.

Javah HelloJNI  生成文件HelloJNI.h文件

[cpp]  view plain copy
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class HelloJNI */  
  4.   
  5. #ifndef _Included_HelloJNI  
  6. #define _Included_HelloJNI  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     HelloJNI 
  12.  * Method:    printMsg 
  13.  * Signature: ()V    
  14. 方法签名:用来映射C++function Java_HelloJNI_printMsg和Java function  printMsg的参数和返回值 
  15.  
  16. 可以使用命令:[ javap -s -private 类名]   来查看方法签名 
  17.  
  18. 参数说明: 
  19. JNIEnv* :JNI环境指针.该指针为JVM当前中当前线程的句柄,包含一些映射信息和管理信息 
  20. Jobject :调用该本地方法的方法的引用,如果调用方法(本地方法调用者)是静态方法,该参数将是jclass类型 
  21.  
  22.  */  
  23. JNIEXPORT void JNICALL Java_HelloJNI_printMsg  
  24.   (JNIEnv *, jobject);  
  25.   
  26. #ifdef __cplusplus  
  27. }  
  28. #endif  
  29. #endif  

参数说明:

JNIEnv* JNI环境指针.该指针为JVM当前中当前线程的句柄,包含一些映射信息和管理信息

Jobject :调用该本地方法的方法的引用,如果调用方法(本地方法调用者)是静态方法,该参数将是jclass类型

关于方法签名多说几句:

这时HelloJNI的方法签名,其意义见下表:


类型签名的意义:



第三步:根据HelloJNI.h头文件创建动态链接库。具体步骤为:

新建VC6.0动态链接库工程HelloJNI

HelloJNI.h头文件copy到工程目录下,这是比较偷懒的做法,比较好的做法是创建一个文件来存放自己外部的头文件,

在项目中指定路径projetct->setting->C++->processor->additional include directoryes:

在工程中HelloJNI.cpp文件中导入必须的头文件

#include "jni.h"

#include "HelloJNI.h"

Jin.h头文件在jdk安装目录下的include文件夹,如果找不到,使用2中的方式包含该路径。

OkHelloJNI.h的实现文件为HelloJNI.cpp 


[cpp]  view plain copy
  1. #include "jni.h"  //这里不要用#include <jin.h>  
  2. #include "HelloJNI.h"  
  3.   
  4. BOOL APIENTRY DllMain( HANDLE hModule,   
  5.                        DWORD  ul_reason_for_call,   
  6.                        LPVOID lpReserved  
  7.                      )  
  8. {  
  9.     return TRUE;  
  10. }  
  11.   
  12.   
  13. JNIEXPORT void JNICALL Java_HelloJNI_printMsg (JNIEnv * env, jobject obj){  
  14.         //简单的测试语句,打印jdk版本,打印出的是一个Long值  
  15.         jint versionid = env->GetVersion();  
  16.   
  17.         printf("jdk version:%d",versionid);  
  18.   
  19. }  

第四步:编译HelloJNI工程,生成HelloJNI.dll动态链接库文件,生成的dll文件会在debug文件夹或者release文件夹中;

第五步:继续用偷懒的办法来看运行效果,把第四步生成的HelloJNI.dll文件copy到和HelloJNI.class同一目录下

命令行执行:java HelloJNI



C++调用Java代码

C++调用Java代码的原理是在C++代码中创建JVM,加载class文件然后执行。

下面来简单入门。


第一步:创建HelloCPP.java文件.

[java]  view plain copy
  1. class HelloCPP{  
  2.   
  3. public void printHello(){  
  4.     System.out.println("hello C++");  
  5. }  
  6. }  

注意,这个类现在是裸奔的,没有加马甲(package.

命令行执行命令:javac HelloCPP.java 

生成HelloCPP.class

第二步:VC6.0创建Win32 console工程CppCallJavaDemo

[cpp]  view plain copy
  1. // CppCallJavaDemo.cpp : Defines the entry point for the console application.  
  2. //  
  3. #include "jni.h"  
  4.   
  5. // 环境变量PATH在windows下和linux下的分割符定义  
  6. #ifdef _WIN32  
  7. #define PATH_SEPARATOR ';'  
  8. #else  
  9. #define PATH_SEPARATOR ':'  
  10. #endif  
  11.   
  12.   
  13. int main(int argc, char* argv[])  
  14. {  
  15.     JavaVMOption options[1];  
  16.     JNIEnv *env;  
  17.     JavaVM *jvm;  
  18.     JavaVMInitArgs vm_args;  
  19.   
  20.     long status;  
  21.     jclass jcls;  
  22.     jmethodID mid;  
  23.   
  24.     jobject obj;  
  25.   
  26.   
  27.     options[0].optionString = "-Djava.class.path=.";    
  28.     //这里声明类的寻找路径,可以有过个路径    
  29.     //"."表示工程工程目录  
  30. //  options[1].optionString = "-Djava.class.path=H:\\project\\CPPCallJava";    
  31. //  memset(&vm_args,0,sizeof(vm_args));  
  32.     vm_args.version=JNI_VERSION_1_6;  
  33.     vm_args.nOptions=1;  
  34.     vm_args.options = options;  
  35.   
  36.     //获得虚拟机,从返回值判断是否成功 我在测试过程中遇到的返回参数是 0 -1 -3  
  37.     /* 
  38.     #define JNI_OK           0              success     成功 
  39.     #define JNI_ERR          (-1)           unknown error   未知错误,返回这个参数就难以判断问题所在了 
  40.     #define JNI_EDETACHED    (-2)           thread detached from the VM    JVM的派生线程 
  41.     #define JNI_EVERSION     (-3)           JNI version error    JNI版本不一致,返回这个参数就需要判断jvm与JNI是否版本一致 
  42.     #define JNI_ENOMEM       (-4)           not enough memory    内存不够 
  43.     #define JNI_EEXIST       (-5)           VM already created   已经创建了JVM 
  44.     #define JNI_EINVAL       (-6)           invalid arguments    参数不正确 
  45.     */  
  46.     status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);  
  47.   
  48.     if(!env){  
  49.         printf("env is null!\r\n");  
  50.     }  
  51.           
  52.     if(status != JNI_ERR){  
  53.   
  54.         //根据类名找到类  
  55.         jcls =  env->FindClass("HelloCPP");  
  56.          //类加了package需要这样查找,包名以"/"分开  
  57.       //  jcls =    env->FindClass("test/HelloCPP");  
  58.   
  59.         if(jcls != NULL){   
  60.   
  61.             //获得methodid 参数分别为 jclass 调用方法名 方法签名 signature   
  62.             //使用javap -s -private 类名  查看方法签名  
  63.             mid = env->GetMethodID(jcls,"printHello","()V");           
  64.   
  65.   
  66.             //创建对象  
  67.     //      obj = env->AllocObject(jcls);  
  68.   
  69.             //获得默认构造方法的id  
  70.             jmethodID constid =env->GetMethodID(jcls,"<init>","()V");  
  71.             //根据默认构造方法创建对象  
  72.             obj = env->NewObject(jcls,constid);  
  73.   
  74.             printf("method id:%d\r\n",mid);  
  75.               
  76.             if(mid != 0){  
  77.                   
  78.                 env->CallVoidMethod(obj,mid);  
  79.             }  
  80.             printf("get object!!\r\n");  
  81.         }else{  
  82.             printf("not get object!!\r\n");  
  83.         }  
  84.     }  
  85.     jvm->DestroyJavaVM();  
  86.     return 0;  
  87. }  

运行结果:



好了,要是这些代码跑起来还有一些工作要做!

首先为了保证编译通过,需要配置jin.h文件路径

该文件在jdk安装目录下的Include包中 

然后需要配置jin.Lib文件路径,该路径在jdk安装目录下的lib包中

Project->setting->link->input->additional library path:

还有就是我们需要的jvm.dll文件了,在jre/bin/server/目录下可以找到它,把它配到环境变量中。

OK,这样就可以运行exe了!

在整个过程中遇到了两个问题。

Q1:创建JVM失败,无赖的是返回的Statue-1,表示是未知错误,多方检查才发现是JVM.dll用错了,刚开始配的是/jre/bin/client目录下的jvm.dll

改正之后成功创建JVM.

Q2:找不到类。

这个问题困扰了我好一阵子。

首先是一遍遍检查了配置,没问题。依然找不到类。直觉告诉我肯定是类的路径不对。

于是我尝试了多个路径,把编译生成的HelloCPP.class文件置于各个目录下,依然找不到了。

后来我把类中的马甲(package test;刚开始是有这个马甲的)去掉了,把class文件放到工程目录下,bingo!!

如果给类加了马甲,那么在工程目录下需要创建实体目录才行。

如马甲为packate test;

那么在工程目录下新建文件夹test,然后将class文件置于其中,也可以找到类。


到此做了简单入门。网上有一些参考资料,这里可以下载本文中的演示例子以及一些JNI相关资料:demo下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值