目录
一.引言
二.CLion
1.inter.h
2.test.cpp
3.编译 .dylib / .so
4.可能遇到的坑
三.IDEA
1.添加 jna 依赖
2.添加 libtest.dylib 到项目
3.Java 项目测试
四.总结
一.引言
下文主要讲解通过 CLion 将 C++ V14 编码为 .dylib 或者 .so 文件并使用 Java 调用 C++ 库示例。
实现上述需求将用到如下组件,使用其他编译器的同学可能会和下述介绍方法有不同:
-CLion
基于 C++ 14标准生成 .dylib 文件或 .so 文件,前者应用于 MacOs 后者应用于 Linux
– IDEA
基于 Java 1.8 通过 jna 库实现 C++ 动态库的调用。
恰逢卡塔尔世界杯火热进行中,这里也希望总裁能够在这一届世界杯走的更远,取得更好的成绩!
二.CLion
1.inter.h
在 .h 头文件中声明三维坐标类 Point,其中包含三个字段 x、y、z 定义坐标点。
// inter.h#includestruct Point{float x, y, z;};Point add(Point p);
2.test.cpp
.cpp 源文件中主要包含两部分内容:
第一部分为 Point add 方法,该方法将 Point 的 x、y、z 坐标均增加 1
// test.cpp#include#include"inter.h"Point add(Point point) {point.x += 1;point.y += 1;point.z += 1;return point;}
第二部分为 __cplusplus,_cplusplus 翻译过来其实就是 C++,其常与 extern “C” 搭配使用,其目的是标记一部分代码并指示编译器,这部分代码按 C 语言的格式进行编译,而不是 C++ 的。这是使用 C 扩展是因为 C 的函数名不会变,如果使用 C++ 变量名会发生乱码现象,导致我们在 Java 调用时方法名乱码无法调用。
这里Jna_add 方法很简单,对原始 Point 执行 add +1 操作,最终通过 ans 返回。阅读代码也可以看到,由于 ans 的类型为 Points *,其中 * 号代表指针,所以最后返回的是第一个结构体的指针。如果返回的为一个数组,需要返回长度信息才能输出结果。
#ifdef __cplusplusextern "C" {#endif__declspec(dllexport) Point* Jna_add(Point point) {Point a = add(point);Point* ans = new Point[1];for (int i = 0; i < 1; ++i) {ans[i].x = a.x;ans[i].y = a.y;ans[i].z = a.z;}return ans;}#ifdef __cplusplus}#endif
3.编译 .dylib / .so
– .dylib For MacOs
dylibs 文件应用于 MacOs 环境下的 Java 调用:
执行 Build Project 方法后生成 libXXX.dylib 文件,这里 XXX 与 CMakeLists.txt 中配置相关。由于下面的 Java 测试在 Mac 本机测试,所以博主测试样例使用 .dylib 文件。
– .so For Linux
在项目目录下执行:
g++ test.cpp -fPIC -shared -o libadd.so
-fPIC表示生成位置无关代码
-shared表示生成一个动态链接库
动态编译库名称为libXXX.so,其中 XXX 标识动态库名称。
Tips:
Window 环境下为 .dll 文件,如果使用该类型文件大家可自行搜索一下。
4.可能遇到的坑
A.__declspec attributes are not enabled
未启用 __declspec 属性,解决方法也很简单,在 CMakeLists.txt 中添加如下配置重新 ReLoad 配置即可:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fms-extensions")
修改后报错异常消失:
B.ninja: no work to do.
该异常出现在 Build 项目生成 .dylib 文件时,如果要生成对应文件,需要在 CMakeLists.txt 中添加要编译的信息与 cpp 源文件:
add_library(test SHARED test.cpp)
添加后 Reload 配置即可。
三.IDEA
1.添加 jna 依赖
net.java.dev.jnajna5.3.1
该依赖主要用于 Java 调用 C++ 生成的动态库。
2.添加 libtest.dylib 到项目
因为是测试,这里直接放置到 Java 项目根目录下,除此之外,也可以将文件放置在src/main/resources/linux-x86-64 、/usr/lcoal 等多个目录位置,Natice.load 方法会自动寻址。
3.Java 项目测试
这里也分为两部分分解,第一部分为 Jna 结构体,主要实现 C++ Point 类在 Java 中的重新定义,并实现 UserValue 继承 Point 实现静态类,其中元素类型 x、y、z 与之前的 float 对应。
– 通过 JnaLibrary 实现静态库的引入
– Jna_add 定义方法使用方式
// javapublic interface JnaLibrary extends Library {JnaLibrary INSTANCE = Native.load("test", JnaLibrary.class); // 引入 C++ 动态库Point Jna_add(Point.ByValue point); // 定义使用方式@Structure.FieldOrder({"x", "y", "z"}) // 构建结构体class Point extends Structure {public Point() {}public static class UserValue extends Point implements Structure.ByValue {public UserValue(float x, float y, float z) {super(x, y, z);}}public Point(float x, float y, float z) {this.x = x;this.y = y;this.z = z;}public float x;public float y;public float z;}}
第二部分为 main 主函数:
首先初始化一个静态类,随后调用 INSTANCE.Jna_add 方法并打印:
public static void main(String[] args) {JnaLibrary.Point.UserValue startPoint = new JnaLibrary.Point.UserValue(1, 2, 3);JnaLibrary.Point a = JnaLibrary.INSTANCE.Jna_add(startPoint);System.out.println(a.x);System.out.println(a.y);System.out.println(a.z);}
Tips:
这里注意方法名要匹配,jna_add、Jna_Add 等有大小写差异的都会异常报错。
运行后得到下述结果代表调用成功:
四.总结
上述方法实现了在 Java 中调用 C++ 库的简易方法,后续更多更复杂的操作还带进一步尝试。
参考链接:使用Java调取C++动态库。