zoukankan      html  css  js  c++  java
  • JNI编程(一) —— 编写一个最简单的JNI程序(转载)

        转自:http://chnic.iteye.com/blog/198745

       

    忙了好一段时间,总算得了几天的空闲。貌似很久没更新blog了,实在罪过。其实之前一直想把JNI的相关东西整理一下的,就从今天开始吧。Here we go.

    JNI其实是Java Native Interface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其他语言的通信(主要是C&C++)。也许不少人觉 得Java已经足够强大,为什么要需要JNI这种东西呢?我们知道Java是一种平台无关性的语言,平台对于上层的java代码来说是透明的,所以在多数 时间我们是不需要JNI的,但是假如你遇到了如下的三种情况之一呢?

    1. 你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
    2. 在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
    3. 你的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这个目录,然后输 入命令。

    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%includewin32下面找到那个头文件,放入到工程根目 录,F7编译成功。在Debug目录里会发现生成了HelloEnd.dll这个文件。

    这个时候后端的C++代码也已经完成,接下来的任务就是怎么把他们连接在一起了,要让前端的java程序“认识并找到”这个动态链接库,就必须把这个DLL放在windows path环境变量下面。有两种方法可以做到:

    1. 把这个DLL放到windows下面的sysytem32文件夹下面,这个是windows默认的path
    2. 复制你工程的Debug目录,我这里是C:Program FilesMicrosoft Visual StudioMyProjectsHelloEndDebug这个目录,把这个目录配置到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,我们以后再讲。在这里不得不多句嘴。

    1. 因为JNI有一个Native这个特点,一点有项目用了JNI,也就说明这个项目基本不能跨平台了。
    2. JNI调用是相当慢的,在实际使用的之前一定要先想明白是否有这个必要。
    3. 因为C++和C这样的语言非常灵活,一不小心就容易出错,比如我刚刚的代码就没有写析构字符串释放内存,对于java developer来说因为有了GC 垃圾回收机制,所以大多数人没有写析构函数这样的概念。所以JNI也会增加程序中的风险,增大程序的不稳定性。
  • 相关阅读:
    257. Binary Tree Paths
    324. Wiggle Sort II
    315. Count of Smaller Numbers After Self
    350. Intersection of Two Arrays II
    295. Find Median from Data Stream
    289. Game of Life
    287. Find the Duplicate Number
    279. Perfect Squares
    384. Shuffle an Array
    E
  • 原文地址:https://www.cnblogs.com/lance-ehf/p/4241569.html
Copyright © 2011-2022 走看看