目录
下文主要讲解通过 CLion 将 C++ V14 编码为 .dylib 或者 .so 文件并使用 Java 调用 C++ 库示例。
实现上述需求将用到如下组件,使用其他编译器的同学可能会和下述介绍方法有不同:
- CLion
基于 C++ 14标准生成 .dylib 文件或 .so 文件,前者应用于 MacOs 后者应用于 Linux
- IDEA
基于 Java 1.8 通过 jna 库实现 C++ 动态库的调用。
恰逢卡塔尔世界杯火热进行中,这里也希望总裁能够在这一届世界杯走的更远,取得更好的成绩!
在 .h 头文件中声明三维坐标类 Point,其中包含三个字段 x、y、z 定义坐标点。
- // inter.h
- #include
- struct Point
- {
- float x, y, z;
- };
-
- Point add(Point p);
.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 __cplusplus
- extern "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
- .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 文件,如果使用该类型文件大家可自行搜索一下。
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 配置即可。
- <dependency>
- <groupId>net.java.dev.jnagroupId>
- <artifactId>jnaartifactId>
- <version>5.3.1version>
- dependency>
该依赖主要用于 Java 调用 C++ 生成的动态库。
因为是测试,这里直接放置到 Java 项目根目录下,除此之外,也可以将文件放置在 src/main/resources/linux-x86-64 、 /usr/lcoal 等多个目录位置,Natice.load 方法会自动寻址。
这里也分为两部分分解,第一部分为 Jna 结构体,主要实现 C++ Point 类在 Java 中的重新定义,并实现 UserValue 继承 Point 实现静态类,其中元素类型 x、y、z 与之前的 float 对应。
- 通过 JnaLibrary 实现静态库的引入
- Jna_add 定义方法使用方式
- // java
- public 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++动态库。