IntelliJ IDEA平台下JNI编程—HelloWorld篇
转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53906237】
JNI(Java Native Interface),出于学习JNI的目的,为了能够更方便快速地运行程序。本文的是在IDEA中进行,而不在AndroidStudio,这样能够对NDK的工作过程有个更深刻的认识,同时也能对JNI的原理有更深的理解。虽然本文是HelloWorld篇,但是其中涉及到很多内容。博主将遇到的坑都记录下来了,希望能够帮到大家。这篇文章可能是2016年的最后一篇文章了,接下来JNI相关系列文章明年推出,欢迎大家关注。
1. 搭建GCC编译环境
既然使用的了JNI,那就不可避免地需要将C/C++文件编译成dll(windows)或so(Linux)文件。因为我是在Windows平台下开发,可以有如下选择:
- 使用VC(或VS)编译成dll
- 使用GCC编译成dll
因为开发Android应用肯定是需要编译成Linux平台的so文件,因此,为了后面开发Android程序的兼容,使用GCC编译器比较好。而Windows平台下的GCC又可以有如下选择:
- 使用
MinGW- 使用
Cygwin
这里我选择了MinGW,不管选择哪个,只要能让本地有GCC编译环境即可。
注意:搭建GCC编译环境时,一定要选择正确的GCC编译版本(32位和64位)。如果你本地安装的JDK是64位的,那么选择64位GCC,否则选择32位。这是为了使得编译后的库文件跟JVM的位一致,否则后面JVM无法调用dll(或so)。
1.1 安装MinGW
安装MinGW的方法很多,可以前往https://sourceforge.net/projects/mingw/files/MinGW/Base/gcc/ 中自己选择需要的包。老实说,我也不是很清楚哪些包需要下载,没花时间研究,感兴趣的自己研究一些各个包的功能。不过这里有个安装教程https://github.com/cpluspluscom/ChessPlusPlus/wiki/MinGW-Build-Tutorial按照这里的方法安装就可以。
另外,针对64位的,这里https://nuwen.net/mingw.html提供了完整的压缩包,直接下载https://nuwen.net/files/mingw/mingw-14.1.exe运行。如下:
其本质也是解压缩,把需要的MinGW库和程序解压到指定目录。如果懒的去看英文,我这里将我解压后的重新压缩了下,大家去下载并且解压即可直接使用。
链接:https://pan.baidu.com/s/1slpQrrJ
密码: fykw
解压完成后,刚才指定的解压目录中的bin加入到path环境变量中。例如上面图中解压到E:\MinGw,那么应当将E:\MinGw\bin加入到环境变量path中。注意,请确保bin目录确实在E:\MinGw中,如果不在,可能在更深一层目录中,自行确定bin的目录。
做完后,打开控制台,输入:gcc -v,如下:
C:\Users\NetLab\Desktop>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=e:/mingw/bin/../libexec/gcc/x86_64-w64-mingw32/6.3.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../src/configure --enable-languages=c,c++ --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --disable-multilib --prefix=/c/temp/gcc/dest --with-sysroot=/c/temp/gcc/dest --disable-libstdcxx-pch --disable-nls --disable-shared --disable-win32-registry --enable-checking=release --with-tune=haswell
Thread model: win32
gcc version 6.3.0 (GCC)
2. 开始编码
2.1 编写Java文件
新建一个Java Project,创建包com.huachao.java,如下:
在包com.huachao.java下编写HelloJNI类:
package com.huachao.java;
/**
* Created by HuaChao on 2016/12/29.
*/
public class HelloJNI {
static {
// hello.dll (Windows) or libhello.so (Unixes)
System.loadLibrary("hello");
}
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello(); // invoke the native method
}
}
函数System.loadLibrary()是加载dll(windows)或so(Linux)库,只需名称即可,无需加入文件名后缀(.dll或.so)。native关键字将函数sayHello()声明为本地函数,由C/C++实现。具体的实现就在hello.dll(Windows平台)或hello.so(Linux平台)中
2.2 生成JNI头文件
2.2.1 手动输入javah指令
JNI生成头文件是通过JDK中提供的javah来完成,javah在 {JDKHome}/bin目录中。用法如下:
javah -jni -classpath (搜寻类目录) -d (输出目录) (类名)
例如,将E:\Porject\out\com\huachao\java目录中的HelloJNI.class生成头文件,并放入到E:\Project\jni中:
javah -jni -classpath E:\Porject\out\com\huachao\java -d E:\Project\jni com.huachao.java.HelloJNI.java
需要注意的是,使用javah来生成头文件(.h)时,-classpath指定的是编译后的java文件(.class)的目录,而不是源文件(.java)的目录,因此在使用javah指令之前,先build一下项目(或直接运行一下)。此时会生称out目录,所有编译后的文件都会存放在这个目录中。
接下来,直接在IDEA的Terminal窗口运行javah:
此时在jni目录中生成了头文件com_huachao_java_HelloJNI.h
内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_huachao_java_HelloJNI */
#ifndef _Included_com_huachao_java_HelloJNI
#define _Included_com_huachao_java_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_huachao_java_HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_huachao_java_HelloJNI_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
接下来我们只需实现Java_com_huachao_java_HelloJNI_sayHello(JNIEnv *, jobject)即可。仔细观察就会发现这个函数名称是有规律的,即Java_<包>_<类名>_<函数名>,JNIEXPORT和JNICALL这两个宏定义暂时不用管。JNIEnv 和jobject后面系列文章会详细介绍,这里暂时不理会。
2.2.2 一键生成头文件
在2.2.1小节中,介绍了输入javah生成头文件方法。但是如果目录层次很深,或者是有多个需要生成头文件的class文件,这工作量太大了,当然你可以通过写个小程序来实现。但是这里有个更便捷的方法。点击File>Settings>Tools>External Tools:
添加一个先的External Tools:
在HelloJNI.java文件中点击右键>External Tools>Generate Header File,
点击生成,可以看到Terminal窗口会自动运行指令。跟2.2.1小节的指令一模一样。
3. 编写C文件并编译成dll(或so)文件
3.1 手动输入命令生成
在jni目录中新建HelloJNI.c文件,如下:
编辑HelloJNI.c如下:
#include<jni.h>
#include <stdio.h>
#include "com_huachao_java_HelloJNI.h"
JNIEXPORT void JNICALL Java_com_huachao_java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
接下来就是使用GCC对HelloJNI.c编译,在Terminal窗口输入如下:
E:\workspace\StudyJNI>gcc -c jni/HelloJNI.c
jni/HelloJNI.c:1:17: fatal error: jni.h: No such file or directory
#include <jni.h>
发现报错,找不到jni.h头文件,将JDK目录中的include目录加入,即为:
E:\workspace\StudyJNI>gcc -c -I"E:\JDK\include" jni/HelloJNI.c
In file included from jni/HelloJNI.c:1:0:
E:\JDK\include/jni.h:45:20: fatal error: jni_md.h: No such file or directory
compilation terminated.
又报找不到jni_md.h错误,继续将JDK目录中的include/win32加入,即:
gcc -c -I"E:\JDK\include" -I"E:\JDK\include\win32" jni/HelloJNI.c
完成编译。此时在项目中会生成HelloJNI.o文件。接下来是将HelloJNI.o转为HelloJNI.dll,即转为windows平台下的动态链接库。在Terminal中输入如下:
gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o
此时项目目录中生成了hello.dll文件:
3.2 一键生成dll
有了前面使用External Tools一键生成头文件的经验后,我们可以将编译成dll的过程命令也加入到External Tools中。前面将c文件编译链接成dll文件分了2个命令,这里我们直接通过一个命令来完成:
gcc -Wl,--add-stdcall-alias -I"E:\JDK\include" -I"E:\JDK\include\win32" -shared -o ./lib/hello.dll ./jni/HelloJNI.c这样就将c文件编译成了dll,在这里把生成的dll文件加入到了lib目录中,而不是像之前那直接放到项目底下。因此在java.library.path应该指定目录为lib。 有了上面的命令后,可以很轻松的加入到External Tools中了。按照前面的方法,点击File>Settings>Tools>External Tools>+,输入内容如下:name:Generate DLL
Program:<GCC路径>
Parameters:-Wl,--add-stdcall-alias -I"$JDKPath$\include" -I"$JDKPath$\include\win32" -shared -o ./lib/$FileNameWithoutExtension$.dll ./jni/$FileNameWithoutExtension$.c
Working Directory:$ProjectFileDir$在HelloJNI.c中点击右键,选择External Tools>Generate DLL。此时,在lib目录中会得到dll文件。
4. 运行
运行HelloJNI.java类后,如下:![]()
5 可能出现的错误
5.1 java.library.path找不到dll的错误
此时,点击直接运行HelloJNI.java类时,依然还会有错误:Exception in thread "main" java.lang.UnsatisfiedLinkError: no hello in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at com.huachao.java.HelloJNI.<clinit>(HelloJNI.java:8)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:123) Process finished with exit code 1即找不到我们生成的dll文件。因为在Windows中JVM的
java.library.path属性即为环境变量Path指定的目录,而我们生成的dll并未放入到Path指定的任何一个目录中,因此我们需要告诉JVM,dll文件在哪个目录中。点击Run > Edit Configurations...,如下:
在VM options中加入java.library.path,指定dll(或so)文件所在的目录,比如本文中dll放在项目目录中的lib中,如下:
-Djava.library.path=E:\workspace\StudyJNI\lib如下图所示:
5.2 无法识别
__int64类型错误错误如下:
error: unknown type name '__int64'
typedef __int64 jlong;
^出现这个错误的人一般是使用Cygwin GCC的人,这是因为Cygwin GCC不认识
__int64类型,找到<JDK_HOME>/include/win32/jni_md.h,找到typedef __int64 jlong;并修改为:#ifdef __GNUC__
typedef long long jlong;
#else
typedef __int64 jlong;
#endif或者是编译时将
__int64加入,如下:> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias
-I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c5.3 64-bit mode not compiled
错误如下:
HelloJNI.c:1:0: sorry, unimplemented: 64-bit mode not compiled in
#include <jni.h>出现这个错误是因为,JDK版本是64位,而GCC编译器编译出的dll(或so)是32位,只需换个64位版本的GCC即可。
参考资料
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-3.
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
IntelliJ IDEA平台下JNI编程—HelloWorld篇的更多相关文章
- IntelliJ IDEA平台下JNI编程(五)—本地C代码创建Java对象及引用
本文学习如何在C代码中创建Java对象和对象数组,前面我们学习了C代码中访问Java对象的属性和方法,其实在创建对象时本质上也就是调用构造函数,因此本文知识学习起来也很轻松.有了前面学习数组创建的方法 ...
- NET平台下的Excel编程|C#操作Excel|Application和ApplicationClass的联系和区别
NET平台下的Excel编程|C#操作Excel|Application和ApplicationClass的联系和区别 1. Interop含义Interop是互操作的含义.Microsoft.Off ...
- Android平台下OpenCV移植与使用---基于C/C++
在<Android Studio增加NDK代码编译支持--Mac环境>和<Mac平台下Opencv开发环境搭建>两篇文章中,介绍了如何使用NDK环境和Opencv环境搭建与测试 ...
- Android studio 使用NDK工具实现JNI编程
前言: Android开发中常常会使用到第三方的.so库.在使用.so库的时候就要用到JNI编程.JNI是Java Native Interface的缩写.它提供了若干的API实现了Java和其它语言 ...
- [转]Windows平台下Makefile学习笔记
Windows平台下Makefile学习笔记(一) 作者:朱金灿 来源:http://blog.csdn.net/clever101 决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译 ...
- windows平台下VLC2.0.5编译
windows平台下VLC2.0.5编译说明 时隔一年多,又要搞流媒体了,不过这次是要做流媒体服务器. 暂时决定使用vlc+ffmpeg+live555,虽然听有些前辈说这个组合的性能较差,只能作为学 ...
- 不同平台下int类型、指针类型的数据大小
不同平台下int类型.指针类型的数据大小 对于int类型数据和指针类型数据的大小,是非常基础的问题. 在一个具体的平台上,确定他们最好的办法就是使用sizeof(type)对其进行判断,返回当前数据类 ...
- Android平台下Dalvik层hook框架ddi的研究
通过adbi,可以对native层的所有代码进行hook.但对于Android系统来说,这还远远不够,因为很多应用都还是在Dalvik虚拟机中运行的. 那么,有没有什么办法可以对Dalvik虚拟机中跑 ...
- Mac平台与Windows平台下AndroidStudio增量升级
Android Studio增量升级什么情况下使用最合适呢? 比如现在的as版本是2.2版本,而你的as版本2.0版本,这个时候点Check For Updates就没有反应了,因为你已经2个有版本没 ...
随机推荐
- 获取一段HTML文本中的第一张图片与截取内容摘要
有时候我们获得到的数据是一段HTML文本,也许这段文本里面有许多图片,需要截取一张作为标题图片,这时就可以用到下面这个方法获取到第一张图片: #region 获取第一张图片 /// <summa ...
- flex和box兼容性写法
display: -webkit-box; /* Chrome 4+, Safari 3.1, iOS Safari 3.2+ */ display: -moz-box; /* Firefox 17- ...
- jQuery Validate自定义各种验证方法(转)
一.封装自定义验证方法-validate-methods.js /***************************************************************** j ...
- 在idea启动tomcat出现The JAVA_HOME environment variable is not defined correctly的解决
情况:某套代码是用jdk 1.6编译,然后电脑的JAVA_HOME系统变量配的是jdk1.7的,在tomcat启动时报错 The JAVA_HOME environment variable is n ...
- TigerGraph REST++API
简介 - 什么是REST ++? TigerGraph TM 系统使用着名的REpresentational State Transfer(REST)架构来管理与TigerGraph核心组件,图形处理 ...
- SEO 第六章
SEO第六章 本次课程目标: 1. 掌握网站的内链优化 2. 网站的URL优化 一. 网站地图 1. 什么是网站地图? 网站地图也叫站点地图,英文名叫sitemap,指的网站所有内 ...
- ElasticSearch的常用方法
关键词 cluster 集群 shards 索引分片 replicas 索引的副本 recovery 数据重新分布 gateway 索引的持久化方式 Transport 交互 ...
- QTreeWidgetItem封装
#include "qtreewighthelper.h" QTreeWidgetItem* AddQTreeWidgetItemChild(QTreeWidgetItem* pa ...
- CWnd::Updata的作用
CWnd::Updata的作用 CWnd::UpdateData 调用此成员函数以在对话框中初始化数据,或者取回和验证对话框数据. BOOL UpdateData(BOOL bSaveAndValid ...
- STL || HDU 1263 水果
map可以映射map…… 然后在map里面会自己排序 惊了 注意输出格式 回车的输出 #include <iostream> #include <cstdio> #includ ...