`
chnic
  • 浏览: 227948 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JNI编程(一) —— 编写一个最简单的JNI程序

    博客分类:
  • Java
阅读更多

忙了好一段时间,总算得了几天的空闲。貌似很久没更新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工程,然后编写下面的代码。

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,把刚刚生成的那个头文件拷贝到这个工程的根目录里。随便用什么文本编辑器打开这个头文件,发现有一个如下的方法签名。

/*
 * 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来实现这个方法。

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

 

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

 

  1. 把这个DLL放到windows下面的sysytem32文件夹下面,这个是windows默认的path
  2. 复制你工程的Debug目录,我这里是C:\Program Files\Microsoft Visual Studio\MyProjects\HelloEnd\Debug这个目录,把这个目录配置到User variable的Path下面。重启eclipse,让eclipse在启动的时候重新读取这个path变量。

 

比较起来,第二种方法比较灵活,在开发的时候不用来回copy dll文件了,节省了很多工作量,所以在开发的时候推荐用第二种方法。在这里我们使用的也是第二种,eclipse重启之后打开SayHellotoCPP这个类。其实我们上面做的那些是不是是让JVM能找到那些DLL文件,接下来我们要让我们自己的java代码“认识”这个动态链接库。加入System.loadLibrary("HelloEnd");这句到静态初始化块里。

 

package com.chnic.jni;

public class SayHellotoCPP {
	
	static{
		System.loadLibrary("HelloEnd");
	}
	public SayHellotoCPP(){
	}
	public native void sayHello(String name);
	
}

 

这样我们的代码就能认识并加载这个动态链接库文件了。万事俱备,只欠测试代码了,接下来编写测试代码。

		SayHellotoCPP shp = new SayHellotoCPP();
		shp.sayHello("World");

 

我们不让他直接Hello,World。我们把World传进去,执行代码。发现控制台打印出来Hello, World这句话。就此一个最简单的JNI程序已经开发完成。也许有朋友会对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也会增加程序中的风险,增大程序的不稳定性。
分享到:
评论
1 楼 zhongaili520 2011-08-01  
调用第三方提供的dll库,怎么实现

相关推荐

    java通过JNI调用dll的事例 附说明

    本教程将深入讲解如何使用Java通过JNI来调用DLL,并提供一个具体的实例——调用MessageBox函数。 首先,理解JNI的概念是至关重要的。JNI是Java平台标准的一部分,它为Java程序员提供了一种安全、高效的方式来调用...

    Android系统开发入门_胡章焱驱动的编写

    1. **创建C可执行程序**:使用C或C++语言编写一个简单的应用程序,该程序可以直接与硬件驱动程序交互。 2. **编译和安装**:将编写的C可执行程序编译成可以在Android设备上运行的格式,并将其安装到系统中。 3. **...

    一个简单的Java虚拟机实现(v032)

    本文将详细介绍一个用C++编写的简单Java虚拟机实现——"一个简单的Java虚拟机实现(v032)"。 首先,我们要理解JVM的工作原理。JVM是一个抽象的计算机,它遵循Java语言规范中的“Java虚拟机规范”。当Java程序被编译...

    openGL入门参考资料

    OpenGL是一个跨语言、跨平台的编程接口,用于渲染2D、3D矢量图形,广泛应用于游戏开发、科学可视化、工程设计等领域。 在配合的文章《OpenGL学习笔记——JNI篇》中,作者通过JNI(Java Native Interface)来演示...

    java程序的运行机制详细分析

    为了更好地理解Java程序的执行流程,我们来看一个简单的例子——HelloWorld程序。 ```java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } } ``` ...

    Android从驱动到JAVA

    为了深入理解Android硬件抽象层及其工作原理,本文档提供了一个全面的学习计划,旨在帮助开发者掌握从硬件驱动到Java应用程序的整个流程。以下是该计划的主要组成部分: 1. **编写内核驱动程序**:首先需要了解如何...

    新版Android开发教程.rar

    也有分析认为,谷歌并不想做一个简单的手机终端制造商或者软件平台开发商,而意在一统传统互联网和 移 动互联网。----------------------------------- Android 编程基础 4 Android Android Android Android 手机新...

    berkeley-nachos-java.gz.zip_Java操作系统

    Nachos-Java将展示如何在Java中构建一个简单的TCP/IP协议栈,包括网络层的IP协议、传输层的TCP和UDP协议。这将帮助我们理解网络通信的基本原理,以及如何处理数据包的传输和错误检测。 最后,设备驱动程序的编写也...

    罗杰的时钟-您使用MoSync的第一个应用程序

    《罗杰的时钟——您使用MoSync的第一个应用程序》 MoSync是一个开源的跨平台开发框架,它允许开发者使用多种编程语言,如C、C++,来创建可以在多个移动平台上运行的应用程序,包括Android、Blackberry和Windows ...

    jna-platform-4.4.0.jar

    Java Native Access(JNA)是Java平台上的一个开源库,它允许Java代码直接调用本地库(即C和C++编写的动态链接库或静态库)。JNA通过Java接口描述语言(Interface Definition Language, IDL)来定义本地函数,这些...

    Java发展史概述

    Java的第一个版本,JDK 1.0,于1996年发布,引入了面向对象编程的概念,包括类、接口和异常处理等核心特性。随后的JDK 1.1在1997年增加了枚举、内部类和JNI(Java Native Interface),进一步完善了平台。 2000年,...

    PowerBuilder Native Interface

    在PowerBuilder 9版本中,推出了一个重要的技术突破——PowerBuilder Native Interface(简称PBNI),该技术极大地增强了PowerBuilder与其他编程语言(如C/C++、Java)之间的互操作性。 #### 二、PBNI技术的关键...

    Android NDK Demo

    自1.5版本起,Android Studio开始支持NDK集成,使得开发者能够在同一个环境中进行Java和C/C++代码的编写与调试,极大地提高了开发效率。 **NDK Demo——HelloWorld** 在Android NDK的开发过程中,"HelloWorld"程序...

    操作系统软件框架.pdf

    为了能够在设备上运行这些字节码,Android系统内置了一个虚拟机——Android Runtime(ART)。在Android 5.0(API级别21)之前,使用的是Dalvik虚拟机,它通过解释执行的方式运行字节码,这种方式存在一定的性能瓶颈...

    相关说明_盲人端源码1

    一个合理且规范的项目结构不仅有助于团队成员之间的协作,还能提高开发效率,并使后期维护变得更加简单。 1. **易于理解**:标准的项目结构使得新加入的开发者能够快速理解项目的组织方式和文件的位置,从而更快地...

    基于Android平台手机图形编辑软件的设计与实现

    - **Android的起源**:Android项目起始于2003年,最初是由Andy Rubin、Rich Miner、Nick Sears和Chris White四人创建的一个独立开发团队。2005年,Google收购了Android Inc.,并在此基础上继续发展Android平台。 - *...

    pencv参考手册

    - Microsoft Visual Studio 是一个功能强大的集成开发环境,支持多种编程语言。 - **步骤:** 1. **创建新项目:** 在 Visual Studio 中创建新的 C/C++ 项目。 2. **添加 OpenCV 库:** 将 OpenCV 的库文件添加...

    吉他英雄

    例如,你可以编写一个JNI模块来处理来自控制器的输入事件,并将其转化为游戏内的动作。 最后,Kotlin还提供了强大的工具链,如Kotlin编译器和Gradle插件,可以优化构建过程,提高开发效率。此外,Kotlin的互操作性...

Global site tag (gtag.js) - Google Analytics