본문 바로가기

00. Functional Programming/Scala

Scala에서 JNI 사용하기

 Scala는 기본적으로 JVM을 기반으로 사용되기 때문에 속도가 erlang에 비해서도 빠릅니다. 그러나, 영상처리와 같은 Heavy한 작업과 같은것은 C나, C++을 사용하는것이 좋을 것 같습니다. 그래서, 이번에는 Scala에서 JNI를 사용하는 방법을 알아보도록 합시다.
이 포스팅은 http://hohonuuli.blogspot.kr/2013/08/a-simple-java-native-interface-jni.html를 기반으로 제작되었습니다. (사실 이 포스팅이 JNI도 나와서 자세하긴 합니다.)

일단 먼저, Scala Class를 만들어 봅시다.

class Sample1 {
  // --- Native methods
  @native def intMethod(n: Int): Int
  @native def booleanMethod(b: Boolean): Boolean
  @native def stringMethod(s: String): String
  @native def intArrayMethod(a: Array[Int]): Int
}

// --- Code in App body will get wrapped in a main method on compilation
object Sample1 extends App {

  // --- Main method to test our native library
  System.loadLibrary("Sample1")
  val sample = new Sample1
  val square = sample.intMethod(5)
  val bool = sample.booleanMethod(true)
  val text = sample.stringMethod("java")
  val sum = sample.intArrayMethod(Array(1, 1, 2, 3, 5, 8, 13))

  println(s"intMethod: $square")
  println(s"booleanMethod: $bool")
  println(s"stringMethod: $text")
  println(s"intArrayMethod: $sum")
}

여기서, @native는 java에서와 같이 Native 함수를 선언하는 것입니다. 여기서는 "intMethod, booleanMethod, stringMethod, intArrayMethod"가 Native Method로 설정이 되어있습니다. 일단, Scala Class를 컴파일 해 봅시다.


$ scalac Sample1.scala


컴파일을 한 후, "Sample1$.class  Sample1$delayedInit$body.class  Sample1.class"등을 얻을 수 있을 것 입니다.

그렇다면, 이제 필요한 함수에 대한 Native Method를 작성해 봅시다. 일단, javah를 이용하여, 해더 파일을 만들어야 할 것입니다. 그전에, javah에서 참조가 필요한 Scala Library에 대한 Classpath설정을 한 후에 시작해야 합니다.


$ SCALA_LIB_HOME=/usr/local/Cellar/scala/2.10.2/libexec/lib/

$ SCALA_CP=$SCALA_LIB_HOME/scala-library.jar

$ javah -cp $SCALA_CP:. Sample1

위의 SCALA_LIB나, SCAPA_CP는 ~/.bashrc에다가 등록하면 편합니다.


이렇게 되면 다음과 같은 해더 파일이 나옵니다.

Sample1.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Sample1 */

#ifndef _Included_Sample1
#define _Included_Sample1
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Sample1
 * Method:    intMethod
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_Sample1_intMethod
  (JNIEnv *, jobject, jint);

/*
 * Class:     Sample1
 * Method:    booleanMethod
 * Signature: (Z)Z
 */
JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
  (JNIEnv *, jobject, jboolean);

/*
 * Class:     Sample1
 * Method:    stringMethod
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
  (JNIEnv *, jobject, jstring);

/*
 * Class:     Sample1
 * Method:    intArrayMethod
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif


이후, 이 해더파일에 맞는 cpp 파일을 만듭시다.

Sample1.cpp

#include "Sample1.h"
#include <ctype.h>
#include <string.h>

// Mutate array to uppercase
void uppercase(char* str) {
    size_t n = strlen(str);
    for (size_t i = 0; i < n; i++) {
        str[i] = toupper(str[i]);
    }
}

JNIEXPORT jint JNICALL Java_Sample1_intMethod
(JNIEnv* env, jobject obj, jint num) {
    return num * num;
}


JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
(JNIEnv* env, jobject obj, jboolean boolean) {
    return !boolean;
}

JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
(JNIEnv* env, jobject obj, jstring string) {
    const char* str = env->GetStringUTFChars(string, 0);
    char cap[128];
    strcpy(cap, str);
    env->ReleaseStringUTFChars(string, str);
    uppercase(cap);
    return env->NewStringUTF(cap);
}

JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
(JNIEnv* env, jobject obj, jintArray array) {
    int sum = 0;
    jsize len = env->GetArrayLength(array);
    jint* body = env->GetIntArrayElements(array, 0);
    for (int i = 0; i < len; i++) {
        sum += body[i];
    }
    env->ReleaseIntArrayElements(array, body, 0);
    return sum;
}


이렇게 되면 scala에서 쓰이는 libSample1.so파일을 만들 준비가 완료 되었습니다.

Java뿐만이 아니라, 기본적으로 so 파일을 링크할때 lib{라이브러리 이름}.so를 사용합니다.


그렇다면, 이제 libSample1.so파일로 컴파일 해봅시다.


$ g++ -shared -fPIC -Wall -O3 -I/usr/include/ -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ Sample1.cpp -o libSample1.so


컴파일이 완료되면, libSample1.so가 나올 것 입니다. 그, 후 so파일과 함깨, 실행해 봅시다.


$ scala -Djava.library.path=$(pwd) -cp . Sample1

intMethod: 25

booleanMethod: false

stringMethod: JAVA

intArrayMethod: 33


다음과 같은 실행 결과를 볼 수 있습니다.