SparkDL / scala_native_demo

Demo for scala-c++ cross compile project using sbt and cmake (which could directly work with idea and clion).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Scala Native Demo

Demo for scala-c++ cross compile project using sbt and cmake (which could directly work with idea and clion).

Dependencies

  • jdk
  • sbt / idea内置sbt
  • scala / idea内置scala
  • cmake / clion内置cmake

Supported Platforms

MacOS, Linux(Ubuntu16.04), Windows10测试通过。

Structure

目录 详情
demo_scala 使用sbt构建的scala项目,可以直接使用idea打开并开发
demo_cpp 使用cmake构建的c++项目,可以直接使用clion打开并开发
lib demo_cpp生成的动态链接库

Workflow

1. 前置条件

idea+clion

idea需要装Scala语言插件,clion不需要额外配置。

本向导默认用户能够熟练使用idea开发sbt项目。

命令行用户

有sbt和cmake即可。

2. 创建含有native方法的scala类

在/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方法,并编译成动态库。

3. 编译demo_scala项目

在build.sbt内添加native类的名字,例如本项目给出的示例:

lazy val nativeClassNames = List(
  "NativeDemo"
)

然后构建项目。

对于idea用户

依次点击:

菜单栏->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})

4. 实现native方法并编译c++动态库

补充完整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官网或者其中文翻译。

写完之后编译即可:

clion用户

依次点击:菜单栏的Run->Build选项进行cpp工程的编译。

编译完成后,动态库会生成到lib目录内。

命令行用户

依次执行:

cmake .
make

编译完成后,动态库会生成到"lib"目录内。

5.运行

动态库编译完成后,查看"lib"目录,里面将包含动态库文件:

libNativeDemo.dylib(MacOS)/libNativeDemo.so(Linux)/libNativeDemo.dll(Windows)

我们只需要让jvm找到这个文件即可,方法是给jvm加启动参数-Djava.library.path=../lib(如果这里的"..lib"不管用的话那么改成绝对路径)。

怎么加呢?

idea用户

别忘了idea打开的是"demo_scala"目录。

1.点击运行按钮左边的下拉条,选择"Edit Configurations"

edit configuration

2.然后在VM options一栏里加入-Djava.library.path=../lib即可

vm option

命令行用户

给jvm启动参数直接加-Djava.library.path=../lib即可。

例如在"demo_scala"下使用sbt run:

sbt -Djava.library.path=../lib run

将运行程序的main函数。

About

Demo for scala-c++ cross compile project using sbt and cmake (which could directly work with idea and clion).

License:MIT License


Languages

Language:Scala 68.9%Language:C++ 20.1%Language:CMake 11.0%