零 绪论

今天看安卓的源码。发现很多东西都是jni的东西。所以我也要看看java是怎么和jni通信的。那么就涉及到了编译问题。这种情况下涉及到交叉编译,很烦。网络上也是哀嚎声一片。但是我没有遇到那么多的困难。

首先注意几个要点
1你要知道你运行java的程序的版本。我是64位的
2你要知道你gcc编译器编译出来的东西是多少位的
我想很多情况下都是可能gcc编译出来的是32位的东西,你拿到64位jre上运行。可能是这样导致的错误。
说一下我系统的配置情况。
我是采取全线64位策略
win7 64位
jdk 64位
jre64位
mingw64位

下面我一步一步讲解如何编译

一准备

工具准备 win7 jdk64 jre64都是很好弄到的。重点是mingw64.这个东西确保你使用gcc的时候编译出来的是64位的动态链接库,好给64位的jre使用。
其中mingw64很难再官网上下载,所以我是在一个
这里下载出来的

下载出来是个压缩包把压缩包里面的

我的世界JAVA版环境错误_java


解压出来。我放到了D:\Program Files里面

我的世界JAVA版环境错误_Java_02

至此你的mingw64已经安装完成。为了能在cmd里面找到你的gcc。你需要把

D:\Program Files\mingw64\bin

这个路径添加到你的path环境变量里面。如何添加环境变量另行百度。

这样你的gcc方面已经配置好。

二 编译

1准备测试代码

下面给出测试代码。这个代码随处可见

//HelloNative.java
class HelloNative
{
    public static native void greeting();
}

用javac编译这个产生HelloNative.class命令如下

javac HelloNative.java

如果找不到javac命令在哪里。那么说明你的jdk没有放到path变量里面。解决方法和把gcc放到path里面一样

你需要运行javah命令,根据HelloNative.class生成一个用于编写c的头文件

javah HelloNative

这时候会产生一个这样的文件HelloNative.h
内容是

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloNative */

#ifndef _Included_HelloNative
#define _Included_HelloNative
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloNative
 * Method:    greeting
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloNative_greeting
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

这个是自动生成的头文件。现在你需要自己编写一个c文件,实现这个native代码

//名字是HelloNative.c
#include<stdio.h>
#include"HelloNative.h"

JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env,jclass cl)
{
    printf("hello native");
}

到这里之后。你的c文件也准备好编译了。

2编译出dll

如果简单的运行下面的命令是无法编译出来的

gcc HelloNative.c

错误有几个方面
1我们要生成一个连接库,并不是一个exe文件。
所以要加上参数-shared -o helloNative.dll HelloNative.c
这里的-shared是说我们要一个动态链接库 -o helloNative.dll指定我输出出来的名字
现在命令是这个样子

gcc  -shared -o helloNative.dll HelloNative.c

2我们引用了jni这个头文件,但是标准的c里面没有这个头文件,我们需要指定说这个头文件去哪里找。我们要添加
-IC:\Java\jdk1.8.0_60\include
-IC:\Java\jdk1.8.0_60\include\win32
这两个参数可能你会有变化。因为我的jdk安装在C:\Java\jdk1.8.0_60\。所以自然我们就需要指定搜索路径是C:\Java\jdk1.8.0_60\include 。 -I表示头文件搜索路径
现在的编译命令是这样的

gcc -IC:\Java\jdk1.8.0_60\include -IC:\Java\jdk1.8.0_60\include\win32 -shared -o helloNative.dll HelloNative.c

当然还有很多奇怪的问题
我们要加参数
-Wl,–kill-at
其实-WI是告诉gcc后面的参数–kill-at是给链接器的
–kill-at告诉链接器功能如下。

–kill-at
If given, the stdcall suffixes (@nn) will be stripped from symbols before they are exported.
[This option is specific to the i386 PE targeted port of the linker]

下面是个网友的解释

On Microsoft systems (PE executable/DLL format), the calling convention stdcall generates mangled symbols of the form symbolname@number. From the description, –kill-at causes the names exported from DLLs to just be symbolname even if they use the stdcall calling convention.
就是说在链接库里面的函数名字表示方式要做改变。不然jvm不能识别。

-D_JNI_IMPLEMENTATION_

这个选项的意思是-D是告诉gcc后面的东西定义一个宏。

gcc -D选项在man中的说明如下:
-D name
Predefine name as a macro, with definition 1.

还有加上一个参数-Wall
这个参数说

-Wall
This enables all the warnings about constructions that some users consider questionable, and that are easy to avoid (or modify to prevent the warning), even in conjunction with macros. This also enables some language-specific warnings described in C++ Dialect Options and Objective-C and Objective-C++ Dialect Options.

就是打开所有警告。好像可以关闭的样子

现在我终极的编译命令是

gcc -Wall -D_JNI_IMPLEMENTATION_ -Wl,–kill-at -IC:\Java\jdk1.8.0_60\include -IC:\Java\jdk1.8.0_60\include\win32 -shared -o helloNative.dll HelloNative.c

总算完成了

编译之后会产生一个文件叫helloNative.dll

三测试

我们可以写一个测试文件

//test.java
public class test
{
    public static void main(String args[])
    {
        HelloNative.greeting();
    }

    static 
    {
        System.loadLibrary("helloNative");
    }
}

然后编译运行这个文件我们可以得到这样的结果

我的世界JAVA版环境错误_Java_03

备注

1我们所有的代码都是放到C的根目录下面的

2目录内容如下

我的世界JAVA版环境错误_Java_04