-
Notifications
You must be signed in to change notification settings - Fork 45
jni cpp build
landon edited this page Dec 7, 2018
·
2 revisions
- 目的
- 了解一下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
- 对于上面的2者,如果使用命令行进行c++编译,建议将后者加入解压的bin加入环境变量
- eclipse-cpp
- https://www.eclipse.org/downloads/packages/eclipse-ide-cc-developers/oxygenr
- 为什么选择eclipse
- 最早想用vs,但是本地卸载了.netframework的一个版本,因为和本地一个软件不兼容,导致vs不能用
- 习惯了eclipse
- 创建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
- 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
- 报错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-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.