JNI는 Java 영역에서 네이티브의 함수를 호출(call)하거나 네이티브 영역에서 Java 메소드를 호출(callback 또는 listener) 하는 인터페이스이다. 여기에서 네이티브는 C(++)이다. Java에서 네이티브의 함수를 호출할 때 넘어가는 인자(argument)는 call by value일까, call by reference일까. 궁금하다.

Java 영역에서 선언한 네이티브 메쏘드의 파라미터는 Java primitive type으로 채워진다. 네이티브 함수는 그런데 C(++)로 구현된다. 네이티브 영역에서는 Java를 위한 특별한 type으로 받는다. Java와 C는 언어도 다르고 언어를 해석하는 주체도 달라서 어떻게 동작을 할 것인지 확실하지 않았다. 간단히 테스트 프로그램을 작성해서 확인한다.

JNI에 대한 자세한 설명은 Java programming with JNIJNI programming examples for linux를 참고하였다.

테스트

간단히 테스트 프로그램을 꾸몄다. 네이티브 라이브러리를 쉽게 생성해주는 CMake 를 만들어 두었다. bash script로 네이티브와 Java 프로그램을 각자 빌드를 하고, 실행하도록 구성하였다.

  1. Java 영역에서 Int형 array를 네이티브 함수의 인자에 실어 내려보내면,
  2. 네이티브 영역에서는 실려온 인자를 뜯어내고 임의의 값을 할당한다. 네이티브의 역할은 여기가 끝이다.
  3. 다시 Java 영역에서 인자의 값을 출력한다.

네이티브 함수에 실어 보냈던 인자의 값이 네이티브에서 의해서 변경되는가? 미리 답을 말하면, ‘그렇다’

Hierarchy

|-CMakeLists.txt
|-HelloJni.cpp
|-HelloJni.h
|-HelloJni.java
|-preliminary.sh
`-run.sh

코드

CMakeLists.txt

message (STATUS "Hello Jni~" )
set(HELLOJNI hellojni)
set(JNI_INCLUDE_DIRS /usr/local/java/include )

include_directories(
    ${JNI_INCLUDE_DIRS}
    ${JNI_INCLUDE_DIRS}/linux
    )

set(SRCS
    HelloJni.cpp
    )

message (STATUS "JNI_INCLUDE_DIRS=${JNI_INCLUDE_DIRS}")

add_library( ${HELLOJNI} SHARED ${SRCS} )

install (TARGETS ${HELLOJNI}
    LIBRARY DESTINATION lib
    COMPONENT library
    )

HelloJni.java: int형 배열[10] labels을 선언 하고 아무런 값을 할당하지 않는다. 대신 네이티브 함수에 실어 보낸 후 다시 값을 출력한다. JNI에서도 Call by reference가 통한다면 네이티브에서 변경한 값이 출력될 것 이다.

public class HelloJni {

    private native void fillValues(int[] lables);
    static {
        System.loadLibrary("hellojni");
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        HelloJni caller = new HelloJni();
        int[] labels = new int[10];
        caller.fillValues(labels);

        for (int value: labels) {
            System.out.println("Value:" + value);
        }
    }
}                                                           

HelloJni.h: javah가 자동 생성 해준 헤더 파일

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

#ifndef _Included_HelloJni                                
#define _Included_HelloJni                                
#ifdef __cplusplus                                        
extern "C" {                                              
#endif                                                    
/*                                                        
 * Class:     HelloJni                                    
 * Method:    fillValues                                  
 * Signature: ([I)V                                       
 */                                                       
JNIEXPORT void JNICALL Java_HelloJni_fillValues           
  (JNIEnv *, jobject, jintArray);                         

#ifdef __cplusplus                                        
}                                                         
#endif                                                    
#endif                                                    

HelloJni.cpp: Java에서 넘어온 Int형 배열을 GetArrayElements로 얻어왔다. jint *body로 받는 것 보면 넘겨받은 인자의 주소값이다. 영략없는 Call by reference인데, JNI라는 특수한 인터페이스는 어떻게 처리를 할까.

#include <stdio.h>                                     
#include "HelloJni.h"                                  


JNIEXPORT void JNICALL Java_HelloJni_fillValues        
  (JNIEnv *env, jobject obj, jintArray array) {        

      jsize len = env->GetArrayLength(array);          
      jint *body = env->GetIntArrayElements(array, 0);

      for (int i=0; i<len; i++) {                      
          body[i] = i*100;                             
      }                                                

      env->ReleaseIntArrayElements(array, body, 0);    

}                                                      

preliminary.sh: 쉽게 빌드해보고 실행하도록 꾸민 스크립트이다.
run.sh

javac HelloJni.java         
javah HelloJni              
rm -rf build                
mkdir build                 
cd build                    
cmake ..                    
make                        
cd ..                       
cp ./build/libhellojni.so .
bash ./run.sh               

java -Djava.library.path=. HelloJni

실행결과

Call by reference로 확인이 되었다. Java에서 넘어온 인자의 reference를 네이티브에서 변경하면 Java 객체의 값도 변경된다. 동일한 대상을 가리키므로 변경되는 값도 동일하다.

$ bash run.sh
Value:0
Value:100
Value:200
Value:300
Value:400
Value:500
Value:600
Value:700
Value:800
Value:900