将uber的h3封装成jar及踩过的坑

张开慧
背景

随着公司业务的发展,原来的自然行政区以及GeoHash区域划分方法显得有些粗犷,我们需要更细粒度的区域划分方式。

开源的Google S2 和Uber h3进入我们视线,经过研究uber h3更适合我们的需求。

但是uber h3是用C语言实现的,而我们主要使用的语言是java和python。所以需要将C封装为Java使用的jar,本文主要讨论java的封装,未涉及python的封装。

过程
  • 1.下载uber h3源码并设计代码结构

uber h3的github地址:https://github.com/uber/h3

并且我们设计代码结构如下:

alt

  • 2.编写h3的java接口(静态方法)、jni文件和本地文件加载器,以及验证方法

本地文件加载器

然后smartLoad其实是System.loadLibrary(libName)

alt

java接口(静态方法)里面调用这个加载器

alt

jni方法

alt

验证方法

主要是为了验证这个jar的正确性。写一个main方法,调用java接口(静态方法),并打印结果到控制台。之后在pom中指定mainClass,生成jar之后就可以直接java -jar xxx.jar。

alt

  • 3.使用javah指令和jni文件生成C语言的头文件

先编译

javac com/dianwoba/xxx.java

再生成头文件

javah -classpath /xxx -d /xxx -jni com.dianwoba.xxx

生成的头文件里的函数

alt

  • 4.C实现头文件中的方法

jni实现

alt

  • 5.cmake指令将源码和自己的jni实现编译为动态库

可以通过gcc指令或cmake+make指令来编译。

gcc方式,需要显示指定jni.h等

gcc -I /usr/java/jdk1.8.0_73/include/ -I /usr/java/jdk1.8.0_73/include/linux/ xxx.c -fPIC -shared -o xxx.so

cmake+make方式,需要自己编写CMakeLists.txt

alt

  • 6.将动态库复制到h3-jvm并打成jar

使用maven命令打包

mvn clean pakcage -DskipTests

并在pom.xml中通过exec-maven-plugin插件执行shell,自动实现5和6

alt

create_jni如下:

alt

  • 7.验证这个jar

打出包之后再linux上执行以下命令,查看控制台输出。

java -jar xxx.jar

总结

整个其实就是一个jni的实现,由于对这块不熟悉,所以在这个过程中也踩了一些坑,在此记录一下。

1 C语言的堆栈,内存泄漏问题

栈是系统自动分配空间的,例如定义一个char a,系统会自动回收;堆是需要自己申请的,例如malloc(10),如果不手动释放,就可以一直访问,可能造成内存泄漏。这点和java不一样。

2 C语言函数不能返回局部对象或局部变量的指针或应用,否则编译不通过

原因是函数中的局部变量如果不声明为static,那么都是auto声明的(auto可以省略),存储在动态存储区,函数中的形参和变量都属于此类,函数结束时释放空间。

函数的返回值可以是数值或全局变量的指正或引用。

3 gcc 需要把所有的头文件copy过来或者显示指定,否则会报找不到头文件的错误

由于jni.h在java的include中,需要显示指定

gcc -I /usr/java/jdk1.8.0_73/include/ -I /usr/java/jdk1.8.0_73/include/linux/ xxx.c -fPIC -shared -o xxx.so

4 cmake+make 需要将前一次执行的中间文件删除,否则可能不会打出新的动态或静态库

例如将CMakeLists.txt中的BUILD_STATIC由ON改为OFF,再去build目录下执行cmake+make,不会生成.so动态库。这时需要先把build下的所有先删除再cmake+make。

5 maven编译过程中GBK字符映射问题,导致打包失败

原因是maven编译的时候默认使用操作系统的编码格式,只要在properties里面或者compile插件里面指定编码格式为utf8就可以了

alt

6 exec-maven-plugin插件执行的shell的格式问题,导致文件找不到

alt

原因是windos生成的是dos格式的文件,需要转化为unix格式,最简单的办法是在linux上vi 文件,然后 :set ff=unix ,保存退出。