Demo for scala-c++ cross compile project using sbt and cmake (which could directly work with idea and clion).
- jdk
- sbt / idea内置sbt
- scala / idea内置scala
- cmake / clion内置cmake
MacOS, Linux(Ubuntu16.04), Windows10测试通过。
目录 | 详情 |
---|---|
demo_scala | 使用sbt构建的scala项目,可以直接使用idea打开并开发 |
demo_cpp | 使用cmake构建的c++项目,可以直接使用clion打开并开发 |
lib | demo_cpp生成的动态链接库 |
idea需要装Scala语言插件,clion不需要额外配置。
本向导默认用户能够熟练使用idea开发sbt项目。
有sbt和cmake即可。
在/src/main/scala/文件夹下创建一个含有native方法的scala类:
例如本项目给出的示例:
class NativeDemo {
@native def add(a: Double, b: Double): Double
@native def distance(left: Array[Double], right: Array[Double]): Double
}
这时候已经可以在scala内使用这个类并且可以通过编译,因为编译器只关心方法的类型签名。例如本项目的main方法:
object Main {
def main(args: Array[String]): Unit = {
// 加载动态库,一定要保证jvm能找到此动态库
System.loadLibrary("NativeDemo")
val demo = new NativeDemo
val a = Array(1.0, 2.0, 3.0, 4.0)
val b = Array(3.0, 4.0, 5.0, 6.0)
println(demo.add(123.0, 123.0))
println(demo.distance(a, b))
}
}
使用了System.loadLibrary("NativeDemo")
来加载动态库。
但是这时候的动态库还没有实现,jvm并不能找到这个库,运行到这一行代码时会报错。
所以我们接下来使用c++来实现我们的native方法,并编译成动态库。
在build.sbt内添加native类的名字,例如本项目给出的示例:
lazy val nativeClassNames = List(
"NativeDemo"
)
然后构建项目。
依次点击:
菜单栏->Build->Build Project
直接:
sbt compile`
build.sbt在编译时会做三件事:
1.调用javah
命令native类相应应的.h文件到“demo_cpp”目录内,比如这里的"demo_cpp/NativeDemo.h"
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeDemo */
#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: NativeDemo
* Method: add
* Signature: (DD)D
*/
JNIEXPORT jdouble JNICALL Java_NativeDemo_add
(JNIEnv *, jobject, jdouble, jdouble);
/*
* Class: NativeDemo
* Method: distance
* Signature: ([D[D)D
*/
JNIEXPORT jdouble JNICALL Java_NativeDemo_distance
(JNIEnv *, jobject, jdoubleArray, jdoubleArray);
#ifdef __cplusplus
}
#endif
#endif
2.拷贝"demo_cpp/NativeDemo.h"内的函数声明到"demo_cpp/NativeDemo.cpp"内(如果cpp内已经有的话则跳过),刚生成的cpp文件是这样的:
#include "NativeDemo.h"
/*
* Class: NativeDemo
* Method: add
* Signature: (DD)D
*/
JNIEXPORT jdouble JNICALL Java_NativeDemo_add
(JNIEnv *, jobject, jdouble, jdouble);
/*
* Class: NativeDemo
* Method: distance
* Signature: ([D[D)D
*/
JNIEXPORT jdouble JNICALL Java_NativeDemo_distance
(JNIEnv *, jobject, jdoubleArray, jdoubleArray);
你需要把它补充完整,提供具体的实现
3.在"demo_cpp/CMakeList.txt"内添加一个动态库的target(如果已经有的话则跳过),这里就是添加这两行:
set(NativeDemo_SOURCE_FILES NativeDemo.h NativeDemo.cpp)
add_library(NativeDemo SHARED ${NativeDemo_SOURCE_FILES})
补充完整native方法对应的c++方法,例如"demo_cpp/NativeDemo.cpp"补充完整后的内容:
#include "NativeDemo.h"
#include <string.h>
/*
* Class: NativeDemo
* Method: add
* Signature: (DD)D
*/
JNIEXPORT jdouble JNICALL Java_NativeDemo_add
(JNIEnv *env, jobject obj, jdouble a, jdouble b) {
return a + b;
}
/*
* Class: NativeDemo
* Method: distance
* Signature: ([D[D)D
*/
JNIEXPORT jdouble JNICALL Java_NativeDemo_distance
(JNIEnv *env, jobject obj, jdoubleArray lhs, jdoubleArray rhs) {
jsize lLength = env->GetArrayLength(lhs);
jsize rLength = env->GetArrayLength(rhs);
if (lLength != rLength) {
env->FatalError("the length must be same");
}
jdouble *plhs = env->GetDoubleArrayElements(lhs, 0);
jdouble *prhs = env->GetDoubleArrayElements(rhs, 0);
jdouble result = 0;
for (int i = 0; i < lLength; i++) {
result += (plhs[i] - prhs[i]) * (plhs[i] - prhs[i]);
}
return result;
}
这里是使用JDK提供的JNI接口进行编程,具体的JNI编程规范可以参考JNI官网或者其中文翻译。
写完之后编译即可:
依次点击:菜单栏的Run->Build选项进行cpp工程的编译。
编译完成后,动态库会生成到lib
目录内。
依次执行:
cmake .
make
编译完成后,动态库会生成到"lib"目录内。
动态库编译完成后,查看"lib"目录,里面将包含动态库文件:
libNativeDemo.dylib(MacOS)/libNativeDemo.so(Linux)/libNativeDemo.dll(Windows)
我们只需要让jvm找到这个文件即可,方法是给jvm加启动参数-Djava.library.path=../lib
(如果这里的"..lib"不管用的话那么改成绝对路径)。
怎么加呢?
别忘了idea打开的是"demo_scala"目录。
1.点击运行按钮左边的下拉条,选择"Edit Configurations"
2.然后在VM options一栏里加入-Djava.library.path=../lib
即可
给jvm启动参数直接加-Djava.library.path=../lib
即可。
例如在"demo_scala"下使用sbt run:
sbt -Djava.library.path=../lib run
将运行程序的main函数。