2010년 2월 2일 화요일

JNI(Java Native Interface) 1편


JavaTM 플랫폼은 상대적으로 새롭다고 할 수 있는데, 이는 자바 언어로 작성된 프로그램들
과 기존의 자바가 아닌 언어 서비스, API 툴킷, 그리고 프로그램들과의 통합이 필요할 때를 의미하는 것이다. 자바 플랫폼은 이러한 형태의 통합을 쉽게 할 수 있도록 돕기 위해 JNI(Java Native Interface)를 제공한다.

JNI는 이름을 짓고 호출하는데 대한 표준 규약(convention)을 정의함으로써 자바1 가상 머신이 원시 메소드(native method)를 적재(locate)하고 수행(invoke)할 수 있도록 한다. 사실, JNI가 자바 가상 머신 내에 포함됨으로써, 자바 가상 머신이 호스트 운영체제 상의 입출력, 그래픽스, 네트워킹, 그리고 스레드와 같은 기능들을 작동하기 위한 로컬 시스템 호출(local system calls)을 수행할 수 있도록 한다.

이 장에서는 자바 언어로 작성된 프로그램 내에서 로컬 머신(local machine) 상의 어떤 라이브러리를 어떻게 호출하고, 원시 코드(native code) 내로부터 자바 언어 메소드를 어떻게 호출하며, 그리고 자바 가상 머신(Java VM) 인스턴스를 어떻게 생성하고 실행하는 지에 대하여 설명한다. 여러분이 JNI를 사용하기 위해 어떻게 다루어야 하는지를 보여주기 위해, 이 장에서는 JNI Xbase C++ 데이터베이스 API(Xbase C++ database API)와 통합하는 예제, 수학 함수를 호출 하는 방법에 대한 예제 등을 포함하고 있다Xbase에는 여러분이 다운로드 받을 수 있는 소스들이 있다.

이번 섹션에서는 ReadFile 예제 프로그램에 대하여 설명한다. 이 예제는 여러분이 JNI(JavaTM Native Interface)를 이용하여 파일을 메모리로 읽어들이기 위한 C 함수를 호출하는 원시 메소드를 어떻게 수행(invoke)하는지를 보여준다.

예제에 대하여(About the Example)
여러분은 원시 자바 메소드를 선언하고, 원시 코드를 포함하고 있는 라이브러리를 적재(loading)하고,그리고 나서  원시 메소드를 호출함으로써, 자바 언어로 작성된 프로그램에서 다른 어떤 프로그래밍 언어로 작성된 코드도 호출할 수 있다. 아래에 주어진 ReadFile 소스 코드는 이러한 작업을 정확하게 수행한다.

그러나, 프로그램을 성공적으로 실행하기 위해서는 자바 언어로 작성된 소스 파일을 컴파일 하는 것 외에 몇 가지 추가적인 과정들이 필요하다. 여러분이 소스 프로그램을 컴파일 한 후, 예제를 실행하기 전에 헤더 파일을 반드시 생성해 주어야 한다. 원시 코드는 생성된 헤더 파일 내에 포함되어 정의되어 있는 함수들을 구현하고 마찬가지로 비지니스 로직(business logic)을 구현한다다음에 나와있는 섹션은 그러한 모든 과정을 보여준다.

import java.util.*;

class ReadFile {
//
원시 메소드 선언
(Native method declaration)
  native byte[] loadFile(String name);
// 라이브러리 적재(Load the library)
  static {
    System.loadLibrary("nativelib");
  }

  public static void main(String args[]) {
    byte buf[];
// 클래스 인스턴스 생성(Create class instance)
    ReadFile mappedFile=new ReadFile();
// ReadFile.java 파일을 적재하기 위한 원세 메소드 호출(Call native method to load ReadFile.java)
    buf=mappedFile.loadFile("ReadFile.java");
// ReadFile.java 파일의 내용을 출력(Print contents of ReadFile.java)
    for(int i=0;i<buf.length;i++) {
      System.out.print((char)buf[i]);
    }
  }
}

원시 메소드 선언(Native Method Declaration)
native
선언은 자바1 가상 머신 내에서 원시 함수를 호출할 수 있는 브릿지(교량역할)을 제공해 준다. 이 예제에서, loadFile 함수는 호출되는 C 함수인 Java_ReadFile_loadFile와 연결된다. 함수 구현은 파일 이름을 나타내는 String을 받아들이고, 그 파일의 내용을 메모리에 적재한 후, 바이트 배열로 되돌려 준다.

  native byte[] loadFile(String name);

라이브러리 적재(Load the Library)
원시 코드 구현을 포함하고 있는 라이브러리는 System.loadLibrary()를 호출함으로써 적재된다. 정적 초기화 구문(static initializer ensures) 내에서 이 호출을 함으로써, 클래스 당 단 한번만 적재되도록 한다. (역자주: 클래스 내에 포함되어 있는 멤버에는 두 가지가 있습니다. 클래스에 대해 공유되는 멤버들을 static을 이용하여 선언한 클래스 멤버, new 구문에 의해 클래스의 인스턴스가 생성될 때마다 각 인스턴스에 각각 할당되는 인스턴스 멤버입니다.

클래스에 대한 초기화 수행도 두 가지 방식으로 할 수 있습니다. 기본적으로 인스턴스에 대한 초기화를 수행하는 객체 생성자, 클래스에 대해 단 한 번 초기화를 위한 클래스 초기화 블럭을 이용한 초기화입니다. 이 때, 후자의 클래스에 대한 초기화는 클래스의 멤버를 선언하는 위치에 static { ... }와 같이 함으로써 클래스 초기화 블럭을 정의할 수 있습니다.

위의 예를 다시 한 번 더 참조하시기 바랍니다.) 만약, 여러분의 애플리케이션이 요구한다면, 해당 라이브러리는 정적 블럭(static block)의 밖에서 적재될 수도 있습니다. 여러분은 loadLibrary 메소드가 여러분의 원시 코드 라이브러리를 찾을 수 있도록 하기 위해 여러분의 환경을 설정(configuration)해야 할 필요가 있을 것입니다.

  static {
    System.loadLibrary("nativelib");
  }

프로그램의 컴파일(Compile the Program)
프로그램을 컴파일하기 위해서는, 여러분이 일반적으로 하듯이 javac 컴파일러 명령을 다음과 같이 실행하기만 하면 됩니다.

  javac ReadFile.java

다음으로, 여러분은 원시 메소드 선언을 갖고 있는 헤더 파일을 생성하고 파일을 적재하고 읽기 위한 C 함수를 호출하기 위한 원시 메소드를 구현해야 합니다.

헤더 파일의 생성(Generate the Header File)
헤더 파일을 생성하기 위해서는, ReadFile 클래스에 대해 javah 명령을 실행합니다. 이 예제에서는 ReadFile.h라는 이름의 헤더 파일이 생성됩니다. 헤더 파일은 여러분이 loadfile 원시 함수를 구현할 때, 사용해야 할 메소드 서명(method signature)을 제공해 줍니다.

  javah -jni ReadFile

메소드 서명(Method Signature)
ReadFile.h
헤더 파일은 자바 메소드를 원시 C 함수로 연결해 주는 인터페이스를 정의하고 있습니다. 이 인터페이스는 자바 언어 mappedfile.loadFile 메소드의 매개변수(arguments)와 리턴값(return value) nativelib 라이브러리 내의 loadFile이라는 원시 메소드로 연결하기 위한 메소드 서명을 사용합니다. 다음은 loadFile 원시 메소드 매핑을 위한 메소드 서명입니다.

  /*
   * Class:     ReadFile
   * Method:    loadFile
   * Signature: (Ljava/lang/String;)[B
   */
  JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile
    (JNIEnv *, jobject, jstring);

메소드 서명(method signature) 매개변수들의 기능은 다음과 같습니다.

JNIEnv *: JNI
환경에 대한 포인터입니다. 이 포인터는 자바 가상 머신 내에 있는 현재 스레드에 대한 핸들이고, 매핑 및 다른 살림살이(housekeeping) 정보를 포함하고 있습니다.

jobject: 이 원시 코드를 호출한 메소드에 대한 참조(reference)입니다. 만약, 호출한 메소드가 클래스 메소드(static으로 선언된 메소드)이면, 이 매개변수(parameter)  jobject   대신 jclass 형이 될 것입니다.

jstring: 이 매개변수는(parameter) 원시 메소드에 제공됩니다. 이 예에서는, 읽기 위한 파일의 이름이 되겠지요.

원시 메소드의 구현(Implement the Native Method)
이 원시 C 소스 파일에서, loadFile 정의는 ReadFile.h 헤더 파일 내에 포함되어 있는 C 선언을 그대로 복사하여 붙혀넣기(copy and paste) 한 것입니다. 이 정의 바로 다음에 원시 메소드 구현이 오면 됩니다. JNI는 기본적으로 C C++ 모두를 위한 매핑을 제공해 줍니다.

JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile
  (JNIEnv * env, jobject jobj, jstring name) {
    caddr_t m;
    jbyteArray jb;
    jboolean iscopy;
    struct stat finfo;
    const char *mfile = (*env)->GetStringUTFChars(
                env, name, &iscopy);
    int fd = open(mfile, O_RDONLY);
    if (fd == -1) {
      printf("Could not open %s\n", mfile);
    }
    lstat(mfile, &finfo);
    m = mmap((caddr_t) 0, finfo.st_size,
                PROT_READ, MAP_PRIVATE, fd, 0);
    if (m == (caddr_t)-1) {
      printf("Could not mmap %s\n", mfile);
      return(0);
    }
    jb=(*env)->NewByteArray(env, finfo.st_size);
    (*env)->SetByteArrayRegion(env, jb, 0,
 finfo.st_size, (jbyte *)m);
    close(fd);
    (*env)->ReleaseStringUTFChars(env, name, mfile);
    return (jb);
}

여러분은 직접 구현하는 대신 다음의 두 가지 방법 중 하나를 사용하여 기존의 C 함수를 호출하는 접근방법을 사용할 수 있습니다.

JNI 의해 생성된 이름을 기존의 C 함수 이름으로 매핑합니다. 언어적 이슈(Language Issues) 섹션에서는 Xbase 데이터베이스 함수들과 자바 언어 코드들 사이에 어떻게 연결하는지를 보여줍니다.

java.sun.com 웹 사이트 상의 JNI 페이지(JNI page)에서 얻을 수 있는 공용 스터브 코드(shared stubs code)들을 사용합니다.

동적 및 공용 객체 라이브러리의 컴파일(Compile the Dynamic or Shared Object Library)
라이브러리는 동적(dynamic) 또는 공용(shared) 객체 라이브러리로 컴파일되어 런타임 시에 적재될 필요가 있습니다. 정적(static) 또는 집적(archive) 라이브러리들은 컴파일되어 실행파일 내로 폼함되기 때문에 런타임 시에 적재될 수 없습니다. loadFile 예제를 위한 공용 객체 또는 동적 객체 라이브러리는  다음과 같이 컴파일 됩니다.

Gnu C/Linux:
gcc  -o libnativelib.so -shared -Wl,-soname,libnative.so 
  -I/export/home/jdk1.2/
include -I/export/home/jdk1.2/include/linux nativelib.c 
  -static -lc

Gnu C++/Linux with Xbase
g++ -o libdbmaplib.so -shared -Wl,-soname,libdbmap.so 
  -I/export/home/jdk1.2/include
  -I/export/home/jdk1.2/include/linux
  dbmaplib.cc -static -lc -lxbase

Win32/WinNT/Win2000
cl -Ic:/jdk1.2/include
  -Ic:/jdk1.2/include/win32
  -LD nativelib.c -Felibnative.dll

예제의 실행(Run the Example)
이 예제를 실행하기 위해서는, 자바 가상 머신이 원시 라이브러리를 찾을 수 있어야 합니다. 이를 위해, 라이브러리 경로를 다음과 같이 현재 디렉토리로 설정해 주어야 합니다.

Unix or Linux:
  LD_LIBRARY_PATH=`pwd`
  export LD_LIBRARY_PATH

Windows NT/2000/95:
  set PATH=%path%;.

여러분의 플랫폼을 위해 적당하게 주어진 라이브러리 경로를 가지고, 다음과 같이 인터프리터 명령어를 가지고 일반적으로 하듯이 해당 프로그램을 실행하면 됩니다.

  java ReadFile



이 섹션에서는 자바 프로그래밍 언어로 작성된 프로그램과 다른 언어로 작성된 프로그램 간에 문자열과 배열 데이터를 어떻게 전달하는지에 대하여 설명한다.

스트링의 전달(Passing Strings)
자바 언어의 String 객체는 JNI에서 jstring으로 표현되며, 16 비트 유니코드 문자열이다. C에서, 문자열은 기본적으로 8비트 문자들로부터 만들어진다. 그래서, C/C++ 함수에 전달된 자바 언어의 String 객체를 참조하거나, 또는 자바 언어 메소드로 리턴된 C/C++ 문자열을 참조하기 위해서는 원시 메소드 구현에 포함되어 있는 JNI 변환(conversion) 함수들을 사용해야만 한다.

GetStringUTFChar
함수는 UTF(Unicode Transformation Format)를 이용하여 16 비트 jstring으로부터 8 비트 문자들을 얻어낸다. UTF는 유니코드를 어떤 정보의 손실도 없이 8 비트 또는 16 비트 문자들로 표현한다. 세 번째 매개변수는 GetStringUTFChar 는 만약 jstring 의 지역 복사본을 만들었을 경우에는 JNI_TRUE를 결과로 가지고, 그렇지 않을 경우에는 JNI_FALSE를 그 결과로 한다.

C Version:
  (*env)->GetStringUTFChars(env, name, iscopy)

C++ Version:
  env->GetStringUTFChars(name, iscopy)

다음에 나오는 C JNI 함수는 C 문자 배열을  jstring으로 변환한다.

  (*env)->NewStringUTF(env, lastfile)

아래에 나오는 예제는 lastfile[80] C 문자 배열을 jstring으로 변환하여, 호출한 자바 언어 메소드에 리턴해 준다.

  static char lastfile[80];
  JNIEXPORT jstring JNICALL Java_ReadFile_lastFile
    (JNIEnv *env, jobject jobj) {
     return((*env)->NewStringUTF(env, lastfile));
  }

UTF 표현을 가지고 작업했다는 것이 끝났다는 것을 자바1 가상 머신이 알 수 있도록 하기 위해서는, 아래에 나타난 것처럼 ReleaseStringUTFChars 변환 함수를 호출하면 된다. 두 번째 매개변수는 UTF 표현을 구성하기 위해 사용된 원래의 jstring 값이고, 세 번째 매개변수는 그 String의 지역 표현(local representation)에 대한 참조이다

 (*env)->ReleaseStringUTFChars(env, name, mfile);

만약, 원시 코드가 중간적인(intermediate) UTF 표현 없이도 유니코드를 다루 수 있다면, 유니코드 문자열을 얻기 위해 GetStringChars 함수를 호출하고, ReleaseStringChars 함수를 호출하여 그 참조를 해제할 수 있다

  JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile
    (JNIEnv * env, jobject jobj, jstring name) {
      caddr_t m;
      jbyteArray jb;
      struct stat finfo;
      jboolean iscopy;
      const jchar *mfile = (*env)->GetStringChars(env,
  name, &iscopy);
  //...
      (*env)->ReleaseStringChars(env, name, mfile);

배열의 전달(Passing Arrays)
가장 마지막 섹션에서 보여주었던 예제에서, loadFile 원시 메소드는 바이트 배열로 파일의 내용을 리턴하고, 이는 자바 프로그래밍 언어에서는 기본형이다. 자바 언어에서 적당한 TypeArray 함수를 호출함으로써 기본형을 얻고(retrieve) 생성(create)할 수 있다.

예를 들어, 새로운 float 배열을 생성하기 위해서는, NewFloatArray 함수를 호출하거나, 또는 새로운 바이트 배열을 생성하기 위해서는 NewByteArray 함수를 호출하면 된다. 이름을 이런 방식으로 짓은 것은 배열에서 원소를 얻고, 원소를 추가하고, 배열 내의 원소를 변경하는 등에 대해서도 확장할 수 있다. 새로운 바이트 배열을 얻기 위해서는 GetByteArrayElements 함수를 호출하면 된다. 배열에 원소를 추가하거나 또는 배열 내의 원소를 변경하기 위해서는 Set<type>ArrayElements 함수를 호출하면 된다.

GetByteArrayElements 함수는 전체 배열에 영향을 미친다. 배열의 일부분에 대해 작업을 하기 위해서는 이 함수 대신 GetByteArrayRegion 함수를 사용할 수 있다. 그리고, 부분에 대해 배열의 원소를 변경하기 위한 Set<type>ArrayRegion 함수만 있다. 그러나, 부분(region)의 크기가 1일 수도 있고, 이는 존재하지 않는 Sete<type>ArrayElements 함수와 같다.

Native
Code Type
Functions used
jboolean
NewBooleanArray
GetBooleanArrayElements
GetBooleanArrayRegion/SetBooleanArrayRegion
ReleaseBooleanArrayRegion
jbyte
NewByteArray
GetByteArrayElements
GetByteArrayRegion/SetByteArrayRegion
ReleaseByteArrayRegion
jchar
NewCharArray
GetCharArrayElements
GetCharArrayRegion/SetCharArrayRegion
ReleaseCharArrayRegion
jdouble
NewDoubleArray
GetDoubleArrayElements
GetDoubleArrayRegion/SetDoubleArrayRegion
ReleaseDoubleArrayRegion
jfloat
NewFloatArray
GetFloatArrayElements
GetFloatArrayRegion/SetFloatArrayRegion
ReleaseFloatArrayRegion
jint
NewIntArray
GetIntArrayElements
GetIntArrayRegion/SetIntArrayRegion
ReleaseIntArrayRegion
jlong
NewLongArray
GetLongArrayElements
GetLongArrayRegion/SetLongArrayRegion
ReleaseLongArrayRegion
jobject
NewObjectArray
GetObjectArrayElement/SetObjectArrayElement
jshort
NewShortArray
GetShortArrayElements
GetShortArrayRegion/SetShortArrayRegion
ReleaseShortArrayRegion

이전 섹션의 예제에 있는 loadFile 원시 메소드에서는, 배열의 부분을 읽고 있는 파일의 크기로 명세함으로써 전체 배열이 갱신된다.

  jbyteArray jb;
  jb=(*env)->NewByteArray(env, finfo.st_size);
  (*env)->SetByteArrayRegion(env, jb, 0,
  finfo.st_size, (jbyte *)m);
  close(fd);

배열은 호출한 자바 언어 메소드로 리턴되고, 계속해서 더 이상 사용되지 않을 때, 배열에 대한 참조를 쓰레기 수집된다. 이 배열은 다음과 같이 호출함으로써 명시적으로 할당 해제 될 수도 있다

  (*env)-> ReleaseByteArrayElements(env, jb, (jbyte *)m, 0);

위에서 ReleaseByteArrayElements 함수에 대한 마지막 매개변수는 다음과 같은 값을 가질 수 있다.

0: C
코드 내로부터의 배열에 대한 갱신이 자바 언어 복사본에 반영된다.

JNI_COMMIT: 자바 언어 복사본은 갱신되지만, 지역 변수 jbyteArray는 해제되지 않는다.

JNI_ABORT: 변경이 다시 복사되지는 않지만,  jbyteArray는 해제된다. 만약 배열이 복사본임을 의미하는  JNI_TRUE 얻기 모드(get mode)로 이 배열이 얻어진 경우에만 이 값이 사용될 수 있다

배열 고정시키기(Pinning Array)
배열을 참조(retrieving)할 때, 이 배열이 복사본인지(JNI_TRUE) 또는 자바 언어 프로그램 내에 존재하는 배열에 대한 참조인지(JNI_FALSE)를 나타낼 수 있다. 만약, 배열에 대한 참조를 사용한다면, 배열이 자바 힙(Java heap) 내에 있고, 쓰레기 수집기가 힙 메모리를 최적으로 정리할 때 옮겨지지 않기를 원할 것이다. 배열 참조가 옮겨지지 않도록 하기 위해, 자바 가상 머신은 배열을 메모리 내에 고정시킨다(pinning; 못박는다). 배열을 고정시키는 것은 배열이 할당 해제될 때, 자바 가상 머신 내에서 정확한 요소들이 갱신되도록 하는 것을 보장한다.

이전 섹션의 loadfile 원시 메소드 예제에서는, 배열이 명시적으로 해제되지 않았다. 배열이 더 이상 필요없을 때, 배열이 쓰레기 수집되도록 하는 확실한 하나의 방법은 자바 언어 메소드를 호출하고, 대신 바이트 배열을 전달하고, 그리고 나서 지역 배열 복사본을 해제하는 것이다. 이러한 기법은 Multi-Dimensional Arrays에 대한 섹션에서 보여질 것이다.

객체 배열(Object Arrays)
NewObjectArray
함수와 SetObjectArrayElement 함수를 호출함으로써 배열 내에 어떤 자바 언어 객체도 저장할 수 있다. 객체 배열과 기본형 배열의 주요 차이점은 jobjectarray 형을 생성할 때, 자바 언어 클래스가 매개변수로서 사용된다는 것이다

다음에 나오는 C++ 예제는 String 객체 배열을 생성하기 위해 NewObjectArray 함수를 어떻게 호출하는지를 보여주고 있다. 배열의 크기는 5(five)로 설정되고, FindClass 함수 호출에서는 클래스 정의 리턴되고, 배열의 요소들은 공백 문자열로 초기화 된다. 배열 내에 저장할 위치(position)와 값을 가지고  SetObjectArrayElement 함수를 호출함으로써 배열의 요소들을 갱신할 수 있다

  #include <jni.h>
  #include "ArrayHandler.h"
  JNIEXPORT jobjectArray JNICALL
               Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){
    jobjectArray ret;
    int i;
    char *message[5]= {"first",
 "second",
 "third",
 "fourth",
 "fifth"};
    ret= (jobjectArray)env->NewObjectArray(5,
         env->FindClass("java/lang/String"),
         env->NewStringUTF(""));
    for(i=0;i<5;i++) {
        env->SetObjectArrayElement(
  ret,i,env->NewStringUTF(message[i]));
    }
    return(ret);
  }

이 원시 메소드를 호출하는 자바 클래스는 다음과 같다.

  public class ArrayHandler {
    public native String[] returnArray();
    static{
        System.loadLibrary("nativelib");
    }
    public static void main(String args[]) {
        String ar[];
        ArrayHandler ah= new ArrayHandler();
        ar = ah.returnArray();
        for (int i=0; i<5; i++) {
           System.out.println("array element"+i+
                                 "=" + ar[i]);
        }
    }
  }

다차원 배열(Multi-Dimensional Arrays)
자바 언어 프로그램에서 원시 메소드를 이용하여 기존의 알제브라 라이브러리 CLAPACK/LAPACK과 같은 수학적 라이브러리 또는 행렬 연산 프로그램 등을 호출할 필요가 있을 것이다. 이러한 라이브러리와 프로그램들 대부분은 2차원 또는 다차원 배열을 사용한다.

자바 프로그래밍 언어에서, 1차원 이상의 배열은 배열에 대한 배열로 취급된다. 예를 들어, 2차원 정수 배열(two-dimensional integer array)은 정수 배열에 대한 배열로 다루어진다. 배열은 수평적으로 읽히고, 이러한 방식을 행 우선(row order)이라 한다.

FORTRAN과 같은 다른 언어에서는 열 우선(column ordering) 방식을 사용하기 때문에, 만약 프로그램이 언어 배열을 FORTRAN 함수에 건네주게 될 때는 추가적인 주의가 필요하다. 또한, 자바 프로그래밍 언어로 작성된 애플리케이션 내에서 배열 요소들이 메모리 상에서 연속적일 것이라고 보장할 수 없다. 몇몇 수학 라이브러리들은 속도 최적화(speed optimizations)를 수행하기 위해 배열 요소들이 메모리 내에서 연속적으로 저장되어 있다고 생각하기 때문에, 그러한 함수에 배열을 전달하기 위해서는 추가적인 지역 복사본을 만들어 주어야 한다

다음에 나오는 예제는 요소들을 추려낸 후, 연산을 수행하고, 그리고 그 결과를 되돌려 주기 위해 자바 언어를 호출하는 원시 메소드에게 2차원 배열을 전달하고 있다.

이 배열은 jints 배열을 포함하고 있는 객체 배열로서 전달된다. GetObjectArrayElement 함수를 호출함으로써 객체 배열로부터 jintArray 인스턴스를 먼저 가져오고, 그리고 나서 jintArray 행으로부터 요소들을 추출함으로써 각 요소들이 추출된다.

이 예제는 고정 크기의 행렬을 사용한다. 만약, 사용되고 있는 배열의 크기를 모를 경우에는, 가장 바깥쪽 배열의 크기를 되돌려 주는 GetArrayLength(array) 함수를 사용하면 된다. 배열의 전체 크기를 알기 위해서는 배열의 각 차원에 대해 GetArrayLength(array) 함수를 호출하면 된다.

자바 언어로 작성된 프로그램으로 되돌려진 새로운 배열은 역으로 생성된다. 먼저, jintArray 인스턴스가 생성되고, 이 인스턴스는 SetObjectArrayElement 함수를 호출함으로써 객체 배열 내에 설정된다.

public class ArrayManipulation {
  private int arrayResults[][];
  Boolean lock=new Boolean(true);
  int arraySize=-1;
  public native void manipulateArray(
  int[][] multiplier, Boolean lock);
  static{
    System.loadLibrary("nativelib");
  }

  public void sendArrayResults(int results[][]) {
    arraySize=results.length;
    arrayResults=new int[results.length][];
    System.arraycopy(results,0,arrayResults,
                       0,arraySize);
  }

  public void displayArray() {
    for (int i=0; i<arraySize; i++) {
      for(int j=0; j <arrayResults[i].length;j++) {
        System.out.println("array element "+i+","+j+
          "= "  + arrayResults[i][j]);
      }
    }
  }

  public static void main(String args[]) {
    int[][] ar = new int[3][3];
    int count=3;
    for(int i=0;i<3;i++) {
      for(int j=0;j<3;j++) {
        ar[i][j]=count;
      }
      count++;
    }
    ArrayManipulation am= new ArrayManipulation();
    am.manipulateArray(ar, am.lock);
    am.displayArray();
  }
}

#include <jni.h>
#include <iostream.h>

#include "ArrayManipulation.h"
JNIEXPORT void
     JNICALL Java_ArrayManipulation_manipulateArray
(JNIEnv *env, jobject jobj, jobjectArray elements,

                            jobject lock){
  jobjectArray ret;
  int i,j;
  jint arraysize;
  int asize;
  jclass cls;
  jmethodID mid;
  jfieldID fid;
  long localArrayCopy[3][3];
  long localMatrix[3]={4,4,4};
  for(i=0; i<3; i++) {
     jintArray oneDim=
 (jintArray)env->GetObjectArrayElement(
                      elements, i);
     jint *element=env->GetIntArrayElements(oneDim, 0);
     for(j=0; j<3; j++) {
        localArrayCopy[i][j]= element[j];
     }
  }
// With the C++ copy of the array,
// process the array with LAPACK, BLAS, etc.
  for (i=0;i<3;i++) {
    for (j=0; j<3 ; j++) {
      localArrayCopy[i][j]=
        localArrayCopy[i][j]*localMatrix[i];
     }
  }
// Create array to send back
  jintArray row= (jintArray)env->NewIntArray(3);
  ret=(jobjectArray)env->NewObjectArray(
 3, env->GetObjectClass(row), 0);
  for(i=0;i<3;i++) {
    row= (jintArray)env->NewIntArray(3);
    env->SetIntArrayRegion((jintArray)row,(
 jsize)0,3,(jint *)localArrayCopy[i]);
    env->SetObjectArrayElement(ret,i,row);
  }
  cls=env->GetObjectClass(jobj);
  mid=env->GetMethodID(cls, "sendArrayResults",
                            "([[I)V");
  if (mid == 0) {
    cout <<"Can't find method sendArrayResults";
    return;
  }
  env->ExceptionClear();
  env->MonitorEnter(lock);
  env->CallVoidMethod(jobj, mid, ret);
  env->MonitorExit(lock);
  if(env->ExceptionOccurred()) {
    cout << "error occured copying array back" << endl;
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  fid=env->GetFieldID(cls, "arraySize",  "I");
  if (fid == 0) {
    cout <<"Can't find field arraySize";
    return;
  }
  asize=env->GetIntField(jobj,fid);
  if(!env->ExceptionOccurred()) {
    cout<< "Java array size=" << asize << endl;
  } else {
    env->ExceptionClear();
  }
  return;
}



이 섹션은 클래스, 메소드, 그리고 필드 등을 참조하기 위한 정보를 보여주고, 스레딩, 메모리 그리고 자바가상머신 등과 같은 이슈들을 전반적으로 포함합니다

언어적 이슈(Language issues)
지금까지, 원시 메소드 예제들은 결과를 리턴하고 함수에 전달된 매개변수들을 변경하는 독립형(standalone) C/C++ 함수들을 호출하는 것에 대한 예제들이었다. 그러나, 자바 언어처럼 C++는 클래스의 인스턴스를 사용한다. 만약, 여러분이 하나의 원시 메소드 내에서 클래스를 생성하였다면, 이 클래스에 대한 참조는 자바 언어 내에서는 그에 해당하는 클래스를 갖지 않는다. 이는 처음에 생성되었던 C++ 클래스 상의 함수를 호출하는 것을 다르게 만들 수도 있다.

이러한 상황을 해결하기 위한 한 가지 방법은 C++ 클래스 참조에 대한 레코드를 유지하고, 이를 호출 프로그램 또는 프록시(proxy)에게 되돌려 주는 것이다. C++ 클래스가 원시 메소드 호출 내내 영속하도록하기 위해, C++ new 연산자가 C++ 객체에 대한 참조를 스택상에 생성하도록 하는 것이다.

다음에 나오는 코드는 Xbase 데이터베이스와 자바 언어 코드 간의 매핑을 제공해 준다. Xbase 데이터베이스는 C++ API를 가지며, 이어서 일어나는 데이터베이스 연산을 수행하기 위한 초기화 클래스를 사용한다. 클래스 객체가 생성될 때, 이 객체에 대한 포인터는 자바 언어 상의 int 값으로 리턴된다. 여러분은 32비트 이상의 머신에서는 long 또는 보다 큰 값을 사용할 수 있다.

public class CallDB {
  public native int initdb();
  public native short opendb(String name, int ptr);
  public native short GetFieldNo(String fieldname, int ptr);
  static {
    System.loadLibrary("dbmaplib");
  }
  public static void main(String args[]) {
    String prefix=null;
    CallDB db=new CallDB();
    int res=db.initdb();
    if(args.length>=1) {
      prefix=args[0];
    }
    System.out.println(db.opendb("MYFILE.DBF", res));
    System.out.println(db.GetFieldNo("LASTNAME", res));
    System.out.println(db.GetFieldNo("FIRSTNAME", res));
   }
}

initdb 원시 메소드에 대한 호출로부터 되돌려 받은 int 값은 연속되는 원시 메소드 호출에 전달된다. dbmaplib.cc 라이브러리 내에 포함되어 있는 원시 코드는 매개변수로 전달된 자바 언어 객체에 대한 참조를 해제(de--references)하고 그 객체 포인터를 참조한다. 다음에 나오는 소스 프로그램 중, xbDbf* Myfile=(xbDbf*)ptr; 라인은 int 포인터 값을 Xbase xbDbf에 대한 포인터로 형변환(cast) 한다.

#include <jni.h>
#include <xbase/xbase.h>
#include "CallDB.h"
JNIEXPORT jint JNICALL Java_CallDB_initdb(
 JNIEnv *env, jobject jobj) {
  xbXBase* x;
  x= new xbXBase();
  xbDbf* Myfile;
  Myfile =new xbDbf(x);
  return ((jint)Myfile);
}
JNIEXPORT jshort JNICALL Java_CallDB_opendb(
                    JNIEnv *env, jobject jobj,
                    jstring dbname, jint ptr) {
  xbDbf* Myfile=(xbDbf*)ptr;
  return((*Myfile).OpenDatabase( "MYFILE.DBF"));
}
JNIEXPORT jshort JNICALL Java_CallDB_GetFieldNo
                           (JNIEnv *env, jobject jobj,
                           jstring fieldname,
                           jint ptr) {
  xbDbf* Myfile=(xbDbf*)ptr;
  return((*Myfile).GetFieldNo(
 env->GetStringUTFChars(fieldname,0)));
}

메소드 호출(Calling Methods)
배열에 대한 이 섹션에서는 원시 코드 내로부터 자바 언어 메소드를 호출하려는 몇 가지 이유들에 대해 집중적으로 살펴본다. 예를 들면, 여러분이 리턴하려고 하는 결과를 해제할 필요가 있을 때가 그러한 경우이다. 만약, 여러분이 하나 이상의 결과를 되돌려주어야 할 필요가 있거나 또는 여러분이 그냥 단순히 원시 코드 내로부터 자바 언어 값들을 변경하기를 원할 경우, 여러분의 원시 코드로부터 자바 원시 메소드를 호출하는 방법을 사용하게 된다.

원시 코드 내로부터 자바 언어 메소드를 호출하기 위해서는 다음과 같은 세 가지 단계를 포함해야 한다.

클래스 참조를 얻는다. (Retrieve a class reference)
메소드 구분자를 얻는다
. (Retrieve a method identifier)
메소드를 호출한다. (Call the Methods)


클래스 참조 얻기(Retrieve a Class Reference)
첫번째 단계는 여러분이 참조하기를 원하는 메소드를 포함하고 있는 클래스에 대한 참조를 얻는 것이다. 참조를 얻기 위해서는 FindClass 메소드를 사용하거나 원시 메소드에 대한 매개변수인 jobject 또는 jclass를 참조(access)할 수 있다.

FindClass 메소드를 사용하는 경우(Use the FindClass method):

  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){
  jclass cls = (*env)->FindClass(env, "ClassName");
  }

jobject 매개변수를 사용하는 경우(Use the jobject argument):
  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){
  jclass cls=(*env)->GetObjectClass(env, jobj);
  }
또는(or)

jclass
매개변수를 사용하는 경우(Use the jclass argument):
  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jclass jcls){
  jclass cls=jcls;
  }

메소드 구별자 얻기(Retrieve a Method Identifier)
클래스를 얻은 다음, 두 번째 단계에서는 클래스 내에서 여러분이 선택한 메소드를 위한 구별자(idnetifier)를 얻기 위해  GetMethodID 함수를 호출한다. 구별자란 그 클래스 인스턴스의 메소드를 호출할 때 필요하다. 자바 언어는 메소드 중첩(method overloading)을 지원하기 때문에, 여러분은 역시 여러분이 호출하기를 원하는 특정 메소드 서명을 명시해 주어야 한다. 여러분의 자바 언어 메소드가 사용하는 서명이 어떤 것이지를 찾기 위해서는 다음과 같이 javap 명령어를 실행하면 됩니다

  javap -s Class

사용된 메소드 서명은  다음에 나타나 있는 것과 같이 각 메소드 선언 다음에 주석(comment)처럼 표시된다.

bash# javap -s ArrayHandler
Compiled from ArrayHandler.java

public class ArrayHandler extends java.lang.Object {
  java.lang.String arrayResults[];
   /*   [Ljava/lang/String;   */
  static {};
   /*   ()V   */
  public ArrayHandler();
   /*   ()V   */
  public void displayArray();
   /*   ()V   */
  public static void main(java.lang.String[]);
   /*   ([Ljava/lang/String;)V   */
  public native void returnArray();
   /*   ()V   */
  public void sendArrayResults(java.lang.String[]);
   /*   ([Ljava/lang/String;)V   */
}

객체 인스턴스 내에서 인스턴스 메소드를 호출하기 위해서는 GetMethodID 함수를 사용하고, 또는 스태틱 메소드를 호출하기 위해서는 GetStaticMethodID 함수를 사용해야 한다. 이 두 함수에 대한 매개변수 리스트는 서로 동일하다.

메소드 호출(Call the Methods)
세 번째, Call<type>Method 함수를 이용하여 해당 인스턴스 메소드를 호출한다. type 값은 Void, Object, Boolean, Byte, Char, Short, Int, Long, Float, 또는 Double 등이 될 수 있다

메소드에 대한 매개변수는 Call<type>MethodA 함수에 대한 값들의 배열인 콤마로 구분되는 리스트(comma-separated list),또는  va_list로서 전달될 수 있다. va_list C에서 다양한 매개변수 리스트를 위해 자주 사용되는 구조(construct)이다. CallMethodV va_list()를 전달하기 위해 사용ㄷ회는 함수이다.

스태틱 메소드들은 CallStaticByteMethodA와 같이 Static 구별자를 메소드의 이름에 추가적으로 포함하고, jclass 값이 jobject 값 대신 사용된다는 것을 제외하면 같다.

다음의 예제는 sendArrayResults 메소드를 호출함으로써 ArrayHandler로부터 객체 배열을 리턴한다.

// ArrayHandler.java
public class ArrayHandler {
  private String arrayResults[];
  int arraySize=-1;
  public native void returnArray();
  static{
    System.loadLibrary("nativelib");
  }

  public void sendArrayResults(String results[]) {
    arraySize=results.length;
    arrayResults=new String[arraySize];
    System.arraycopy(results,0,
                     arrayResults,0,arraySize);
  }
  public void displayArray() {
    for (int i=0; i<arraySize; i++) {
      System.out.println("array element
 "+i+ "= " + arrayResults[i]);
    }
  }
  public static void main(String args[]) {
    String ar[];
    ArrayHandler ah= new ArrayHandler();
    ah.returnArray();
    ah.displayArray();
  }
}

원시 C++ 코드는 다음과 같이 정의된다.

#include <jni.h>
#include <iostream.h>
#include "ArrayHandler.h"
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
  jobjectArray ret;
  int i;
  jclass cls;
  jmethodID mid;
  char *message[5]= {"first",
  "second",
  "third",
  "fourth",
  "fifth"};
  ret=(jobjectArray)env->NewObjectArray(5,
      env->FindClass("java/lang/String"),
      env->NewStringUTF(""));
  for(i=0;i<5;i++) {
    env->SetObjectArrayElement(
 ret,i,env->NewStringUTF(message[i]));
  }
  cls=env->GetObjectClass(jobj);
  mid=env->GetMethodID(cls,
 "sendArrayResults",
 "([Ljava/lang/String;)V");
  if (mid == 0) {
    cout <<Can't find method sendArrayResults";
    return;
  }
  env->ExceptionClear();
  env->CallVoidMethod(jobj, mid, ret);
  if(env->ExceptionOccurred()) {
    cout << "error occured copying array back" <<endl;
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  return;
}

리눅스 상에서 이를 생성하기 위해서는, 다음과 같은 명령어를 사용하면 된다

  javac ArrayHandler.java
  javah -jni ArrayHandler
  g++  -o libnativelib.so
 -shared -Wl,-soname,libnative.so
 -I/export/home/jdk1.2/include
 -I/export/home/jdk1.2/include/linux nativelib.cc 
 -lc

만약, 여러분이 상위 클래스의 메소드(super class method)를 명시하고자 할 때, 예를 들어 상위 클래스의 객체 생성자를 호출하고자 할 때, CallNonvirtual<type>Method 함수를 호출하면 가능하다.

원시 코드 내로부터 자바 언어 메소드 또는 필드를 호출할 때, 한 가지 중요한 점은 발생한 어떤 예외(any raised exceptions)들도 처리해(catch) 주어야 한다는 것이다. ExceptionClear 함수는 현재 발생한 어떤 예외들도 깨끗하게 처리(clear)하고, 반면 ExceptionOccured 함수는 현재 JNI 세션 내에서 예외가 발생했는지를 검사한다.

필드의 참조(Accessing Fields)
원시 코드 내로부터 자바 언어 필드를 참조하는 것은 자바 언어 메[소드를 호출하는 것과 유사하다. 그러나, 필드 또는 그 집합은 메소드 ID가 아니라 필드 ID를 가지고 얻어진다

이를 위해, 먼저 해야 할 것은 필드 ID를 얻는 것이다. GetFieldID 함수를 사용할 수 있고, 이 때 메소드 이름과 서명 대신 필드 이름과 서명을 주어야 한다. 필드 ID를 얻었다면, 필드 값을 설정하기 위해서 Get<type>Field 함수를 호출하면 된다. <type> j가 빠지고, 첫번째 문자가 대문자가 되는 것을 제외하면, 리턴된 원시 형과 같다. 예를 들어, 원시 형 jint를 위한 <type> 값은 Int이고, 원시 형 jbyte을 위한 <type> 값은 Byte이다.

Get<type>Field 함수 결과는 원시 형으로 리턴된다. 예를 들어, ArrayHandler 클래스 내의 arraySize 필드를 얻기 위해서는 다음에 나와 있는 예제에 나타난 것과 같이 GetIntField를 호출한다.

필드는 env->SetIntField(jobj, fid, arraysize) 함수를 호출함으로써 설정될 수 있다. 스태틱 필드는 SetStaticIntField(jclass, fid, arraysize) 함수를 호출함으로써 설정될 수 있고, GetStaticIntField(jobj, fid) 함수를 호출함로써 얻을 수 있다.

#include <jni.h>
#include <iostream.h>
#include "ArrayHandler.h"
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
    jobjectArray ret;
    int i;
    jint arraysize;
    jclass cls;
    jmethodID mid;
    jfieldID fid;
    char *message[5]= {"first",
                "second",
                "third",
                "fourth",
                "fifth"};
    ret=(jobjectArray)env->NewObjectArray(5,
        env->FindClass("java/lang/String"),
        env->NewStringUTF(""));
    for(i=0;i<5;i++) {
      env->SetObjectArrayElement(
        ret,i,env->NewStringUTF(message[i]));
    }
    cls=env->GetObjectClass(jobj);
    mid=env->GetMethodID(cls,
        "sendArrayResults",
        "([Ljava/lang/String;)V");
    if (mid == 0) {
        cout <<Can't find method sendArrayResults";
        return;
    }
    env->ExceptionClear();
    env->CallVoidMethod(jobj, mid, ret);
    if(env->ExceptionOccurred()) {
       cout << "error occured copying
                        array back" << endl;
       env->ExceptionDescribe();
       env->ExceptionClear();
    }
    fid=env->GetFieldID(cls, "arraySize",  "I");
    if (fid == 0) {
        cout <<Can't find field arraySize";
        return;
    }
    arraysize=env->GetIntField(jobj, fid);
    if(!env->ExceptionOccurred()) {
       cout<< "size=" << arraysize << endl;
    } else {
       env->ExceptionClear();
    }
    return;
}

스레드와 동기화(Threads and Synchronization)
비록 원시 라이브러리가 클래스 당 한번 적재되었더라도, 자바 언어로 작성된 애플리케이션 내의 각각의 스레드들은 원시 메소드를 호출할 때 그들 자신의 인터페이스 포인터를 사용한다. 만약, 원시 코드 내로부터의 자바 언어 메소드에 대한 참조를 제한하려 한다면, 호출한 자바 언어 메소드가 명시적인 동기화를 갖도록 확실하게 하든지 또는 JNI MonitorEnter 함수와 MonitorExit 함수를 사용하면 된다.

자바 언어에서 synchronized 키워드를 명시하면 해당 코드는 모니터에 의해 보호된다. 자바 프로그래밍 언어에서, 모니터 진입(monitor enter)과 모니터 진출(monitor exit)은 애플리케이션 개발자에게는 일반적으로 숨겨진다. JNI에서는, 스레드 안전 코드(thread safe code)에 대한 진입과 진출 위치를 명시적으로(explicitly) 나타내 주어야 한다.

다음에 나오는 예제는 CallVoidMethod 함수에 대한 참조를 제한하기 위해 Boolean 객체를 사용하고 있다.

  env->ExceptionClear();
  env->MonitorEnter(lock);
  env->CallVoidMethod(jobj, mid, ret);
  env->MonitorExit(lock);
  if(env->ExceptionOccurred()) {
    cout << "error occured copying array back" << endl;
    env->ExceptionDescribe();
    env->ExceptionClear();
  }

MFC 윈도우 핸들 또는 메시지 큐와 같은 로컬 시스템 자원을 참조하기를 원할 경우, 하나의 자바 Thread를 사용하여 로컬 스레드 원시 이벤트 큐 또는 원시 코드 내로부터의 메시지 시스템을 사용하는 것이 훨씬 좋다.

메모리 이슈(Memory Issues)
기본적으로, JNI는 객체를 원시 메소드 내에 생성할 때, 지역 참조(local references)를 사용한다. 이는 메소드가 리턴할 때, 그 참조들이 쓰레기 수집되기에(garbage collected) 적합하게 되도록 하기 위한 것이다. 만약, 어떤 객체가 원시 메소드 호출이 계속되는 동안 영속하기를 원한다면, 대신 전역 참조(global reference)를 사용해야 한다. 지역 참조에 대해 NewGlobalReference 함수를 호출하면 지역 참조에 대한 전역 참조(global reference)를 생성할 수 있다.

참조에 대해 DeleteGlobalRef 함수를 호출함으로써 그 참조가 쓰레기 수집되도록 명시적으로 표시(mark)할 수 있다. 마찬가지로 메소드의 밖에서 참조가능하지만, 쓰레기 수집될 수 있는 연약한 스타일 전역 참조(weak style Global reference)를 생성할 수도 있다. 이러한 참조들 중 하나를 생성하기 위해서는 참조가 쓰레기 수집되도록 표시하기 위한 NewWeakGlobalRef 함수와 DeleteWeakGlobalRef 함수를 호출하면 된다.

심지어, env->DeleteLocalRef(localobject) 메소드를 호출함으로써, 쓰레기 수집될 지역 참조를 명시적으로 표시할 수도 있다. 이러한 방식은 대용량의 임시 데이터를 사용하고 있을 경우에 유용하게 사용할 수 있다.

  static jobject stringarray=0;
  JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){
    jobjectArray ret;
    int i;
    jint arraysize;
    int asize;
    jclass cls, tmpcls;
    jmethodID mid;
    jfieldID fid;
    char *message[5]= {"first",
  "second",
  "third",
  "fourth",
  "fifth"};
    ret=(jobjectArray)env->NewObjectArray(5,
        env->FindClass("java/lang/String"),
        env->NewStringUTF(""));
  //Make the array available globally
    stringarray=env->NewGlobalRef(ret);
  //Process array
  // ...
  //clear local reference when finished..
    env->DeleteLocalRef(ret);
  }

수행(Invocation)
메소드의 호출에 대한 섹션에서 JNI 인터페이스를 사용하여 자바 언어 프로그램 내에 있는 메소드 또는 필드를 호출하고 FindClass 함수를 사용하여 적재된 클래스 내에 있는 메소드 또는 필드를 호출하는 방법에 대하여 보여주었다. 조금의 코드만 있더라도 , 자바 가상 머신을 수행하고 자바 언어 클래스들의 인스턴스를 생성하기 위해 사용될 수 있는 자신만의 JNI 인터페이스 포인터를 포함하는 독립형(standalone) 프로그램을 생성할 수 있을 것이다. 자바 2 릴리즈에서는, java라는 이름의 런타임 프로그램은 이러한 것들을 정확히 수행하는 작은 JNI 애플리테이션이라 할 수 있다.

자바 가상 머신을 생성하기 위해서는 JNI_CreateJavaVM를 호출하면 되고, 생성된 자바 가상 머신을 실행 정지(shutdown)시키기 위해서는 JNI_DestroyJavaVM를 호출하면 된다. 자바 가상 머신 역시 몇몇 추가적인 환경 속성(environment properties)을 필요로 할 것이다. 이러한 속성은 JavaVMInitArgs 구조체 형태로 JNI_CreateJavaVM 함수에 전달될 수 있다.

JavaVMInitArgs 구조체는 클래스패스(classpath), 자바 가상 머신의 버전(Java virtual machine version) 또는 일반적으로 명령행에서 프로그램으로 전달되는 시스템 속성 등과 같은 환경 정보를 저장하기 위해 사용되는 JavaVMOption 값에 대한 포인터를 포함한다.

JNI_CreateJavaVM 함수가 리턴할 때, FindClass 함수와 NewObject 함수를 이용하여 클래스의 메소드를 호출하거나 인스턴스를 생성할 수 있다. 임베디드 원시 코드(embedded native code)에 대해서도 같은 방식이다.

--------------------------------------------------------------------------------
주의(Note): 자바 가상 머신 수행은 단지 원시 스레드 자바 가상 머신에 대해서만 사용될 수 있다. 조금 구 버전의 자바 가상 머신은 수행 사용(invocation use)에 대해 안전한 그린 스레드 옵션(green threads option)을 가지고 있다. 유닉스 플랫폼 상에서는 -lthread 또는 -lpthread와 명시적으로 링크해 주어야 한다
.
--------------------------------------------------------------------------------

다음에 나오는 프로그램은 자바 가상 머신을 수행하고, ArrayHandler 클래스를 적재하고, -1 값을 포함하고 있는 arraySize 필드를 얻는다. 자바 가상 머신 옵션은 클래스패스(classpath)에 현재 경로를 포함하고, -Djava.compiler=NONE 옵션을 이용하여 JIT(Just-In-Time) 컴파일러가 작동되지 않도록 한다.

#include <jni.h>
void main(int argc, char *argv[], char **envp) {
  JavaVMOption options[2];
  JavaVMInitArgs vm_args;
  JavaVM *jvm;
  JNIEnv *env;
  long result;
  jmethodID mid;
  jfieldID fid;
  jobject jobj;
  jclass cls;
  int i, asize;
  options[0].optionString = ".";
  options[1].optionString = "-Djava.compiler=NONE";
  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = options;
  vm_args.nOptions = 2;
  vm_args.ignoreUnrecognized = JNI_FALSE;
  result = JNI_CreateJavaVM(
             &jvm,(void **)&env, &vm_args);
  if(result == JNI_ERR ) {
    printf("Error invoking the JVM");
    exit (-1);
  }
  cls = (*env)->FindClass(env,"ArrayHandler");
  if( cls == NULL ) {
    printf("can't find class ArrayHandler\n");
    exit (-1);
  }
  (*env)->ExceptionClear(env);
  mid=(*env)->GetMethodID(env, cls, "<init>", "()V");
  jobj=(*env)->NewObject(env, cls, mid);
  fid=(*env)->GetFieldID(env, cls, "arraySize", "I");
  asize=(*env)->GetIntField(env, jobj, fid);
  printf("size of array is %d",asize);
  (*jvm)->DestroyJavaVM(jvm);
}

스레드 연결(Attaching Threads)
자바 가상 머신이 수행된 후에는 자바 가상 머신을 실행하고 있는 하나의 로컬 스레드가 존재한다. 로컬 운영체제 내에서 스레드를 더 생성하고, 이렇게 생성된 스레드들을 자바 가상 머신에 연결할 수 있다. 멀티 스레드(multi-threaded) 원시 애플리케이션을 작성하기를 원할 경우에는 이렇게 하기를 원할 것이다.

로컬 스레드를 자바 가상 머신에 연결하기 위해서는 AttachCurrentThread 함수를 이용하면 된다. 이 때, 자바 가상 머신의 인스턴스와 JNI 환경에 대한 포인터를 제공해 주어야 한다. 자바 2 플랫폼에서는, 세 번째 매개변수에 스레드 이름이나 새로운 스레드가 속하게 될 그룹을 명시해 줄 수 있다. 먼저 연결되었던 스레드를 연결 해제 하는 것은 중요하다. 그렇지 않을 경우, 프로그램은 DestroyJavaVM 함수를 호출하더라도 빠져나가지(exit) 않게 된다

#include <jni.h>
#include <pthread.h>
JavaVM *jvm;
void *native_thread(void *arg) {
  JNIEnv *env;
  jclass cls;
  jmethodID mid;
  jfieldID fid;
  jint result;
  jobject jobj;
  JavaVMAttachArgs args;
  jint asize;
 
  args.version= JNI_VERSION_1_2;
  args.name="user";
  args.group=NULL;
  result=(*jvm)->AttachCurrentThread(
 jvm, (void **)&env, &args);
  cls = (*env)->FindClass(env,"ArrayHandler");
  if( cls == NULL ) {
    printf("can't find class ArrayHandler\n");
    exit (-1);
  }
  (*env)->ExceptionClear(env);
  mid=(*env)->GetMethodID(env, cls, "<init>", "()V");
  jobj=(*env)->NewObject(env, cls, mid);
  fid=(*env)->GetFieldID(env, cls, "arraySize", "I");
  asize=(*env)->GetIntField(env, jobj, fid);
  printf("size of array is %d\n",asize);
  (*jvm)->DetachCurrentThread(jvm);
}
void main(int argc, char *argv[], char **envp) {
  JavaVMOption *options;
  JavaVMInitArgs vm_args;
  JNIEnv *env;
  jint result;
  pthread_t tid;
  int thr_id;
  int i;
  options = (void *)malloc(3 * sizeof(JavaVMOption));
  options[0].optionString = "-Djava.class.path=.";
  options[1].optionString = "-Djava.compiler=NONE";
  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = options;
  vm_args.nOptions = 2;
  vm_args.ignoreUnrecognized = JNI_FALSE;
  result = JNI_CreateJavaVM(&jvm,(void **)&env, &vm_args);
  if(result == JNI_ERR ) {
    printf("Error invoking the JVM");
    exit (-1);
  }
  thr_id=pthread_create(&tid, NULL, native_thread, NULL);
// If you don't have join, sleep instead
//sleep(1000);
  pthread_join(tid, NULL);
  (*jvm)->DestroyJavaVM(jvm);
  exit(0);
}
[출처] JNI 종합편|작성자 잭니콜
http://www.dude.co.rk


P 이경철님의 파란블로그에서 발행된 글입니다.

댓글 없음:

댓글 쓰기