Skip to content

jni cpp build

landon edited this page Dec 7, 2018 · 2 revisions

JNI测试

  • 目的
    • 了解一下jni的基础原理
    • 知道如何写一个JNI基础库
    • 为后面的run#lua做一下底层技术调研
  • 环境
    • 本次环境相关为win7环境
  • 工具
    • eclipse-cpp + mingw
    • gcc/g++(mingw#gnu自带)

工具环境搭建

  • mingw
    • Minimalist GNU on Windows
    • mingw
    • 此工具是32bit,主要包括一些gnu工具如gcc,g++等
    • 可用来一些基础的c++测试,但是不能用作jni#load的library,因为os是64bit
    • 安装
      • mingw-get-setup.exe,安装
      • 进入MinGW Installation Manager
      • 选择需要的package(右键mark),如mingw32-base,mingw32-gcc-g++等
      • Installation#apply changes
      • 会在线下载安装,下载安装解压完毕
  • mingw-w64
    • 64bit,推荐版本
    • MinGW-w64
    • download
      • 这个下载是一个installer,安装后会下载比较慢
    • zip
      • 此页面选择x86_64-posix-seh(64bit)
      • 下载的installer也要下载这个文件(选择版本和arc),不过下载较慢
      • 解压即可
  • 对于上面的2者,如果使用命令行进行c++编译,建议将后者加入解压的bin加入环境变量
  • eclipse-cpp

eclipse c++工程创建

  • 创建c++工程
  • Executable 可选择Hello World C++ Project
  • Toolchains选择MinGW GCC
  • src目录生成tutorial.cpp(工程名字tutorial),可编辑代码
  • build
    • 右键工程->Build Project/Build Configurations->Build All/Build Selected
    • 或者点击工程,在菜单栏选择Project#Build All
    • Debug目录下生成tutorial.exe
  • run
    • 代码右键->Run as Local c/c++ application(需要build#否则会报Launch failed.Binary not found)
    • 控制台顺利输出hello world
    • 后面再修改代码后直接可run,不必再次Build
  • tutorial#build
Info: Internal Builder is used for build
g++ -O0 -g3 -Wall -c -fmessage-length=0 -o "src\\tutorial.o" "..\\src\\tutorial.cpp" 
g++ -o tutorial.exe "src\\tutorial.o" 
  • Type 'JNICALL' could not be resolved
    • build不会报错
    • 这个只是在eclipse编译报错,这两个宏定义在jni_md.h已经定义了
    • 因为你如果重定义,build的时候会提示如warning: "JNICALL" redefined
Well ususally JNI.h has defines for if WIN32 that basically assign JNICALL to __stdcall and
JNIIMPORT to __declspec(dllimport). Unix systems don't need these extra tags, so they define them away.
  • jni_md.h
#define JNIIMPORT __declspec(dllimport)
#define JNICALL __stdcall

jni代码编写

  • java工程
    • 编写JNHelloWorld.java,有两个native方法
    • javah -encoding utf-8 com.xx.mavs.test.JNHelloWorld
      • 在src/test/java目录执行 完整包名
      • 指定编码
      • 在该目录生成com_xx_mavs_test_JNHelloWorld.h
  • c++工程
    • 新建一个empty工程
    • 新建一个src sourcel folder
    • 将com_xx_mavs_test_JNHelloWorld.h拷贝到该目录
    • 编译报错:找不到jni.h(jni.h本身依赖jni_md.h)
      • 引入jdk的include目录,包括jni和jni_md.h
      • jni_md.h contains the [machine-dependent] typedefs for jbyte, jint and jlong
      • c++工程右键->Properties->c/c++ general->paths and symbals->Includes->添加->选中All languages
      • C:\Program Files\Java\jdk1.8.0_131\include
      • 两个包括分别包括jni.h和jni_md.h
      • 因为jni.h#include "jni_md.h"
      • 否则编译会报Cannot open include file: jni.h: No such file or directory错误
      • 添加后需要重新build
    • 如何生成dll
      • c++工程右键->Properties->c/c++ build->settings->Build Artifact->Type选择Shared Library,后缀为dll,前缀为lib,输入名字即可
      • build
      • 成功后会再Debug目录生成libjnhelloworld.dll
      • 本步骤也可以在新建c++工程的时候制定shared library而非executable
  • java测试
    • System.loadLibrary("libjnhelloworld");
    • 调用native方法
  • 出现的问题
    • 报错1: java.lang.UnsatisfiedLinkError: C:\Program Files\Java\jdk1.8.0_131\bin\libjnhelloworld.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
      • 原因:安装和下载的mingw是32bit,需要使用mingw-w64
      • 新建一个jnhelloworld_64 c++工程,建的时候可以指定为share library
      • 右键工程->Property->c/c++ build->environment->将mingw_home切换为mingw-w64的目录
      • 同之前的步骤 添加jdk的include(jni.h/jni_md.h)
      • 拷贝之前的.h和.cpp
      • build
      • Debug目录生成libjnhelloworld_64.dll
    • 报错2:Can't find dependent libraries
      • 工程右键property->c/c++ Build#settings#MinGw c++ Linker#Miscellaneous#linker flags
      • 增加:-Wl,--add-stdcall-alias(这两个参数貌似没什么卵用)
      • 尝试增加:-static-libgcc -static-libstdc++
      • 也可以最后只加一个static参数
      • 加了static参数之后,build的dll大了很多
      • 再次运行,顺利输出,不过在sayHello#native返回的时候crash了
    • 报错3:又出现了一个jvm crash的log:A fatal error has been detected by the Java Runtime Environment
      • 从日志的stack来看,是在C代码这块报错了,对应msvcrt.dll(Microsoft C runtime)
      • 不是c代码的问题,有两个问题,其实是c++代码的不熟悉(另外将string.h修改为了cstring)
      • 1.env->ReleaseStringUTFChars(name,str) 最好放在最后释放空间
      • 2.char desc[100] = "Hello,C++:";strcat(desc,str);// 新代码,无crash
      • 3.char* desc = "Hello,C++:";strcat(desc,str);// 旧代码,crash
      • 原因:因为strcat的第一个参数,错误的代码传入的是只读的desc
  • 总结
    • 首先要使用MinGW-w64,否则load的时候会出现Can't load IA 32-bit .dll on a AMD 64-bit platform
    • 用 MinGW-w64 工具链编译程序默认是动态链接 libgcc 和 libstdc++。这样客户机上必须有这两个 DLL 才能运行目标程序,否则默认load出现Can't find dependent libraries
    • 可以使用dependencywalker这个工具检查build出来的dll依赖哪些包
      • 使用了-static-libgcc -static-libstdc++ 这两个参数后,build出来的dll依然找不到依赖
      • 使用walker看了一下还依赖libgcc_s_seh-1.dll和libwinpthread-1.dll
      • -static-libgcc -static-libstdc++ -static // 用静态链接的方式解决(具体可了解一下动态库和静态库-文件较大)
    • 问题发现(2017.9.28#21:40)
      • C:\Program Files\mingw64\bin 下面是有libgcc_s_seh-1.dll和libwinpthread-1.dll
      • 按照之前的经验 加载了jnhelloworld后,依赖的库windows会自己去load的
      • 又再次在单元测试test中将java.library.path和path打印出来后,发现竟然没有C:\Program Files\mingw64\bin
      • 说明配置的path没生效,原因:有cmd控制台没有关闭;需要关闭所有的console并重启eclipse
      • path生效后,测试build,只加-static-libgcc -static-libstdc++参数
      • 测试OK 不再报找不到依赖库错误 顺利执行native方法
      • 这侧面也说明依赖库的加载需要在java的‘path'下找,即如上面的情况eclipse中path没有生效即使已经配置了path,也无法加载依赖库
    • 关于g++参数告一段落,不需要配置-static静态链接,jni#load dependent library的时候会去path中找
    • c/c++语言基础要好,否则很容易造成crash
    • 对于mac/linux,很多解决思路接近,可以参考,如编译命令
      • g++ -static-libgcc -static-libstdc++ -shared -o libjnhelloworld_64.dll "src\com_xx_mavs_test_JNHelloWorld.o"
  • 参考
  • JNHelloWorld.java
package com.xx.mavs.test;

/**
 * jni测试#包括一个native方法
 *
 * @date 2017-09-27
 * @author [email protected]
 */
public class JNHelloWorld {

    public native String sayHello(String name);

    public static native String getVersion();
}
  • com_xx_mavs_test_JNHelloWorld.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xx_mavs_test_JNHelloWorld */

#ifndef _Included_com_xx_mavs_test_JNHelloWorld
#define _Included_com_xx_mavs_test_JNHelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xx_mavs_test_JNHelloWorld
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_xx_mavs_test_JNHelloWorld_sayHello
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_xx_mavs_test_JNHelloWorld
 * Method:    getVersion
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_xx_mavs_test_JNHelloWorld_getVersion
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif
  • com_xx_mavs_test_JNHelloWorld.cpp#(旧代码会crash)
/*
 * com_xx_mavs_test_JNHelloWorld.cpp
 *
 *  Created on: 2017年9月27日
 *  Author: landon
 */
#include "com_xx_mavs_test_JNHelloWorld.h"
#include <iostream>
#include <cstring>

// 编译参数1 -static-libgcc -static-libstdc++ -static -lwinpthread
// 编译参数2(最终方案) g++ -static-libgcc -static-libstdc++ -shared -o libjnhelloworld_64.dll "src\\com_xx_mavs_test_JNHelloWorld.o"
// 因为libwinpthread-1.dll...在path中
JNIEXPORT jstring JNICALL Java_com_xx_mavs_test_JNHelloWorld_sayHello
  (JNIEnv * env, jobject obj, jstring name)
{
	// 将jstring转为本地数据结构
	const char * str = env->GetStringUTFChars(name,0);
	std::cout << "java#sayHello#name:" << str << std::endl;

	// 转为jstring返回
	// 旧代码会直接导出crash 因为desc是一个只读的literal,不能被strcat
	// char* desc = "Hello,C++:";
	char desc[100] = "Hello,C++:";
	strcat(desc,str);

	jstring result = env->NewStringUTF(desc);

	env->ReleaseStringUTFChars(name,str);

	return result;
}

JNIEXPORT jstring JNICALL Java_com_xx_mavs_test_JNHelloWorld_getVersion
  (JNIEnv * env, jclass clazz)
{
	char* version = "1.5";
	jstring result = env->NewStringUTF(version);

	return result;
}

  • build c++ project(mingw)
18:30:03 **** Incremental Build of configuration Debug for project jnhelloworld ****
Info: Internal Builder is used for build
g++ "-IC:\\Program Files\\Java\\jdk1.8.0_131\\include" "-IC:\\Program Files\\Java\\jdk1.8.0_131\\include\\win32" -O0 -g3 -Wall -c -fmessage-length=0 -o "src\\com_xx_mavs_test_JNHelloWorld.o" "..\\src\\com_xx_mavs_test_JNHelloWorld.cpp" 
..\src\com_xx_mavs_test_JNHelloWorld.cpp: In function '_jstring* Java_com_xx_mavs_test_JNHelloWorld_sayHello(JNIEnv*, jobject, jstring)':
..\src\com_xx_mavs_test_JNHelloWorld.cpp:20:15: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
  char* desc = "Hello,C++:";
               ^~~~~~~~~~~~
..\src\com_xx_mavs_test_JNHelloWorld.cpp: In function '_jstring* Java_com_xx_mavs_test_JNHelloWorld_getVersion(JNIEnv*, jclass)':
..\src\com_xx_mavs_test_JNHelloWorld.cpp:31:18: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
  char* version = "1.5";
                  ^~~~~
g++ -shared -o libjnhelloworld.dll "src\\com_xx_mavs_test_JNHelloWorld.o" 

18:30:05 Build Finished (took 1s.154ms)
  • java测试load#native#console
@Test
    public void test4() {
        // 本例是测试自己写的jni例子
        // Can't load IA 32-bit .dll on a AMD 64-bit platform
        // 因为下载的minggw是32bit的
        System.loadLibrary("libjnhelloworld_64");
        System.out.println("native#version:" + JNHelloWorld.getVersion());

        // 第二个问题 用了ming-w64编译了64位的dll
        // 但是运行报错 Can't find dependent libraries
        // g++参数问题(MinGW-w64 工具链编译程序默认是动态链接 libgcc 和 libstdc++)
        JNHelloWorld jn = new JNHelloWorld();
        String result = jn.sayHello("landon");
        System.out.println("native#sayHello:" + result);

        // 第三个问题 还是报错 jvm crash 原因是c++代码错了 strcat的第一个参数出错代码是只读的char*
    }
	
	#concole
	native#version:1.5
	java#sayHello#name:landon
	native#sayHello:Hello,C++:landon
  • mingw-w64#gcc
gcc version 7.1.0 (x86_64-posix-seh-rev2, Built by MinGW-W64 project)
  • linker flags with '-static-libgcc -static-libstdc++'
21:42:22 **** Incremental Build of configuration Debug for project jnhelloworld_64 ****
Info: Internal Builder is used for build
g++ -static-libgcc -static-libstdc++ -shared -o libjnhelloworld_64.dll "src\\com_xx_mavs_test_JNHelloWorld.o" 

21:42:23 Build Finished (took 943ms)
  • hs_err_log
Stack: [0x0000000002470000,0x0000000002570000],  sp=0x000000000256e428,  free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [msvcrt.dll+0x15735]
C  [libjnhelloworld_64.dll+0x151a]
C  0x0000000002587f74
  • stackoverflow#why occur jvm crash
you are using strcat wrong. It appends the second string to the end of the first string. The string it returns is just a convenience. You cannot alter a constant string (your s1), that is why it crashes. s1 points to read-only memory. 
Clone this wiki locally