출처 :http://www.javastudy.co.kr/docs/jhan/javaadvance/jni.html
고급 애플리케이션 작성(Writing Advanced Applications)
Chapter 5: JNI기술(JNI Technology)
번역 자바스터디 네트워크 (http://www.javastudy.co.kr
Yongwoo's Park [ywpark@cse.konkuk.ac.kr]님
JavaTM플랫폼은 상대적으로 새롭다고 할 수 있는데, 이는 자바 언어로 작성된 프로그램들과 기존의 자바가 아닌 언어 서비스,API툴킷, 그리고 프로그램들과의 통합이 필요할 때를 의미하는 것이다.자바 플랫폼은 이러한 형태의 통합을 쉽게 할 수 있도록 돕기 위해JNI(Java Native Interface)를 제공한다.(The JavaTMplatform is relatively new, which means there could be times when you will need to integrate programs written with the Java language with existing non-Java language services, API toolkits, and programs. The Java platform provides the Java Native Interface (JNI) to help ease this type of integration.)
JNI는 이름을 짓고 호출하는데 대한 표준 규약(convention)을 정의함으로써 자바1가상 머신이 원시 메소드(native method)를 적재(locate)하고 수행(invoke)할 수 있도록 한다. 사실,JNI가 자바 가상 머신 내에 포함됨으로써, 자바 가상 머신이 호스트 운영체제 상의 입출력, 그래픽스, 네트워킹, 그리고 스레드와 같은 기능들을 작동하기 위한 로컬 시스템 호출(local system calls)을 수행할 수 있도록 한다.(The JNI defines a standard naming and calling convention so the Java1virtual machine can locate and invoke native methods. In fact, JNI is built into the Java virtual machine so the Java virtual machine can invoke local system calls to perform input and output, graphics, networking, and threading operations on the host operating system.)
이 장에서는 자바 언어로 작성된 프로그램 내에서 로컬 머신(local machine)상의 어떤 라이브러리를 어떻게 호출하고,원시 코드(native code)내로부터 자바 언어 메소드를 어떻게 호출하며, 그리고 자바 가상 머신(Java VM)인스턴스를 어떻게 생성하고 실행하는 지에 대하여 설명한다. 여러분이JNI를 사용하기 위해 어떻게 다루어야 하는지를 보여주기 위해, 이 장에서는JNI와Xbase C++데이터베이스API(Xbase C++ database API)와 통합하는 예제, 수학 함수를 호출 하는 방법에 대한 예제 등을 포함하고 있다.Xbase에는 여러분이 다운로드 받을 수 있는 소스들이 있다.(This chapter explains how to use JNI in programs written in the Java language to call any libraries on the local machine, call Java language methods from inside native code, and how to create and run a Java VM instance. To show how you can put JNI to use, the examples in this chapter include integrating JNI with the Xbase C++ database API, and how you can call a mathematic function.Xbasehas sources you can download.) 다음에 나와있는 테이블은 명시된 주제에 바로 갈 수 있도록 연결된 링크들입니다.(This table links you directly to specific topics.) 이번 섹션에서는 여러분은 원시 자바 메소드를 선언하고, 원시 코드를 포함하고 있는 라이브러리를 적재(loading)하고,그리고 나서 원시 메소드를 호출함으로써, 자바 언어로 작성된 프로그램에서 다른 어떤 프로그래밍 언어로 작성된 코드도 호출할 수 있다. 아래에 주어진 그러나, 프로그램을 성공적으로 실행하기 위해서는 자바 언어로 작성된 소스 파일을 컴파일 하는 것 외에 몇 가지 추가적인 과정들이 필요하다. 여러분이 소스 프로그램을 컴파일 한 후, 예제를 실행하기 전에 헤더 파일을 반드시 생성해 주어야 한다. 원시 코드는 생성된 헤더 파일 내에 포함되어 정의되어 있는 함수들을 구현하고 마찬가지로 비지니스 로직(business logic)을 구현한다. 다음에 나와있는 섹션은 그러한 모든 과정을 보여준다.(However, successfully running the program requires a few additional steps beyond compiling the Java language source file. After you compile, but before you run the example, you have to generate a header file. The native code implements the function defintions contained in the generated header file and implements the business logic as well. The following sections walk through all the steps.) 원시 코드 구현을 포함하고 있는 라이브러리는 프로그램을 컴파일하기 위해서는, 여러분이 일반적으로 하듯이 다음으로, 여러분은 원시 메소드 선언을 갖고 있는 헤더 파일을 생성하고 파일을 적재하고 읽기 위한C함수를 호출하기 위한 원시 메소드를 구현해야 합니다.(Next, you need to generate a header file with the native method declaration and implement the native method to call the C functions for loading and reading a file.) 헤더 파일을 생성하기 위해서는, 메소드 서명(method signature)매개변수들의 기능은 다음과 같습니다.(The method signature parameters function as follows:) 이 원시C소스 파일에서, 여러분은 직접 구현하는 대신 다음의 두 가지 방법 중 하나를 사용하여 기존의C함수를 호출하는 접근방법을 사용할 수 있습니다.(You can approach calling an existing C function instead of implementing one, in one of two ways:) 라이브러리는 동적(dynamic)또는 공용(shared)객체 라이브러리로 컴파일되어 런타임 시에 적재될 필요가 있습니다. 정적(static)또는 집적(archive)라이브러리들은 컴파일되어 실행파일 내로 폼함되기 때문에 런타임 시에 적재될 수 없습니다. 이 예제를 실행하기 위해서는, 자바 가상 머신이 원시 라이브러리를 찾을 수 있어야 합니다. 이를 위해, 라이브러리 경로를 다음과 같이 현재 디렉토리로 설정해 주어야 합니다.(To run the example, the Java virtual machine needs to be able to find the native library. To do this, set the library path to the current directory as follows:) 여러분의 플랫폼을 위해 적당하게 주어진 라이브러리 경로를 가지고, 다음과 같이 인터프리터 명령어를 가지고 일반적으로 하듯이 해당 프로그램을 실행하면 됩니다.(With the library path properly specified for your platform, invoke the program as you normally would with the interpreter command:) 이 섹션에서는 자바 프로그래밍 언어로 작성된 프로그램과 다른 언어로 작성된 프로그램 간에 문자열과 배열 데이터를 어떻게 전달하는지에 대하여 설명한다.(This section explains how to pass string and array data between a program written in the JavaTMprogramming language and other languages.) 다음에 나오는C JNI함수는C문자 배열을 예를 들어, 새로운float배열을 생성하기 위해서는, 이전 섹션의 예제에 있는 이전 섹션의 다음에 나오는C++예제는 자바 프로그래밍 언어에서,1차원 이상의 배열은 배열에 대한 배열로 취급된다. 예를 들어,2차원 정수 배열(two-dimensional integer array)은 정수 배열에 대한 배열로 다루어진다. 배열은 수평적으로 읽히고, 이러한 방식을 행 우선(row order)이라 한다. (In the Java programming language, any array that has more than one dimension is treated as an array of arrays. For example, a two-dimensional integer array is handled as an array of integer arrays. The array is read horizontally, or what is also termed as row order.) FORTRAN과 같은 다른 언어에서는 열 우선(column ordering)방식을 사용하기 때문에,만약 프로그램이 언어 배열을FORTRAN함수에 건네주게 될 때는 추가적인 주의가 필요하다. 또한, 자바 프로그래밍 언어로 작성된 애플리케이션 내에서 배열 요소들이 메모리 상에서 연속적일 것이라고 보장할 수 없다. 몇몇 수학 라이브러리들은 속도 최적화(speed optimizations)를 수행하기 위해 배열 요소들이 메모리 내에서 연속적으로 저장되어 있다고 생각하기 때문에, 그러한 함수에 배열을 전달하기 위해서는 추가적인 지역 복사본을 만들어 주어야 한다. (Other languages such as FORTRAN use column ordering so extra care is needed if your program hands a Java language array to a FORTRAN function. Also, the array elements in an application written in the Java programming language are not guaranteed to be contiguous in memory. Some numerical libraries use the knowledge that the array elements are stored next to each other in memory to perform speed optimizations, so you might need to make an additional local copy of the array to pass to those functions.) 다음에 나오는 예제는 요소들을 추려낸 후, 연산을 수행하고, 그리고 그 결과를 되돌려 주기 위해 자바 언어를 호출하는 원시 메소드에게2차원 배열을 전달하고 있다.(The next example passes a two-dimensional array to a native method which then extracts the elements, performs a calculation, and calls a Java language method to return the results.) 이 배열은 이 예제는 고정 크기의 행렬을 사용한다. 만약, 사용되고 있는 배열의 크기를 모를 경우에는,가장 바깥쪽 배열의 크기를 되돌려 주는 자바 언어로 작성된 프로그램으로 되돌려진 새로운 배열은 역으로 생성된다. 먼저, 이 섹션은 클래스, 메소드, 그리고 필드 등을 참조하기 위한 정보를 보여주고, 스레딩, 메모리 그리고 자바1가상 머신 등과 같은 이슈들을 전반적으로 포함합니다.(This section presents information on accessing classes, methods, and fields, and covers threading, memory, and Java1virtual machine issues.) 이러한 상황을 해결하기 위한 한 가지 방법은C++클래스 참조에 대한 레코드를 유지하고, 이를 호출 프로그램 또는 프록시(proxy)에게 되돌려 주는 것이다.C++클래스가 원시 메소드 호출 내내 영속하도록하기 위해,C++ 다음에 나오는 코드는Xbase데이터베이스와 자바 언어 코드 간의 매핑을 제공해 준다.Xbase데이터베이스는C++ API를 가지며, 이어서 일어나는 데이터베이스 연산을 수행하기 위한 초기화 클래스를 사용한다. 클래스 객체가 생성될 때, 이 객체에 대한 포인터는 자바 언어 상의 원시 코드 내로부터 자바 언어 메소드를 호출하기 위해서는 다음과 같은 세 가지 단계를 포함해야 한다.(Calling a Java language method from within native code involves the following three steps:) 메소드에 대한 매개변수는 스태틱 메소드들은 다음의 예제는 원시 코드 내로부터 자바 언어 메소드 또는 필드를 호출할 때, 한 가지 중요한 점은 발생한 어떤 예외(any raised exceptions)들도 처리해(catch)주어야 한다는 것이다. 이를 위해, 먼저 해야 할것은 필드 ID를 얻는 것이다. 필드는 자바 언어에서 다음에 나오는 예제는 참조에 대해 심지어, 자바 가상 머신을 생성하기 위해서는 로컬 스레드를 자바 가상 머신에 연결하기 위해서는
뛰어들기(In a Rush?)
주제(Topic) 섹션(Section) JNI 예제
(JNI Example)
문자열, 배열, 필드
(Strings, Arrays, and Fields)
기타 프로그래밍 이슈들
(Other Programming Issues)
ReadFile
예제 프로그램에 대하여 설명한다. 이 예제는 여러분이JNI(JavaTMNative Interface)를 이용하여 파일을 메모리로 읽어들이기 위한C함수를 호출하는 원시 메소드를 어떻게 수행(invoke)하는지를 보여준다.(This section presents theReadFile
example program. This example shows how you can use the JavaTMNative Interface (JNI) to invoke a native method that makes C function calls to map a file into memory.)예제에 대하여(About the Example)
ReadFile
소스 코드는 이러한 작업을 정확하게 수행한다.(You can call code written in any programming language from a program written in the Java language by declaring a native Java method, loading the library that contains the native code, and then calling the native method. TheReadFile
source code below does exactly this.)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
을 받아들이고, 그 파일의 내용을 메모리에 적재한 후, 바이트 배열로 되돌려 준다.(Thenative
declaration provides the bridge to run the native function in the Java1virtual machine. In this example, theloadFile
function maps onto a C function calledJava_ReadFile_loadFile
. The function implementation accepts aString
that represents a file name and returns the contents of that file in the byte array.)native byte[] loadFile(String name);
라이브러리 적재(Load the Library)
System.loadLibrary()
를 호출함으로써 적재된다. 정적 초기화 구문(static initializer ensures)내에서 이 호출을 함으로써, 클래스 당 단 한번만 적재되도록 한다.(역자주:클래스 내에 포함되어 있는 멤버에는 두 가지가 있습니다. 클래스에 대해 공유되는 멤버들을static을 이용하여 선언한 클래스 멤버, new 구문에 의해 클래스의 인스턴스가 생성될 때마다 각 인스턴스에 각각 할당되는 인스턴스 멤버입니다. 클래스에 대한 초기화 수행도 두 가지 방식으로 할 수 있습니다. 기본적으로 인스턴스에 대한 초기화를 수행하는 객체 생성자, 클래스에 대해 단 한 번 초기화를 위한 클래스 초기화 블럭을 이용한 초기화입니다. 이 때, 후자의 클래스에 대한 초기화는 클래스의 멤버를 선언하는 위치에 static { ... }와 같이 함으로써 클래스 초기화 블럭을 정의할 수 있습니다. 위의 예를 다시 한 번 더 참조하시기 바랍니다.) 만약, 여러분의 애플리케이션이 요구한다면, 해당 라이브러리는 정적 블럭(static block)의 밖에서 적재될 수도 있습니다. 여러분은loadLibrary
메소드가 여러분의 원시 코드 라이브러리를 찾을 수 있도록 하기 위해 여러분의 환경을 설정(configuration)해야 할 필요가 있을 것입니다. (The library containing the native code implementation is loaded by a call toSystem.loadLibrary()
. Placing this call in a static initializer ensures this library is only loaded once per class. The library can be loaded outside of the static block if your application requires it. You might need to configure your environment so theloadLibrary
method can find your native code library.)static { System.loadLibrary("nativelib"); }
프로그램의 컴파일(Compile the Program)
javac
컴파일러 명령을 다음과 같이 실행하기만 하면 됩니다.(To compile the program, just run thejavac
compiler command as you normally would:)javac ReadFile.java
헤더 파일의 생성(Generate the Header File)
ReadFile
클래스에 대해javah
명령을 실행합니다. 이 예제에서는ReadFile.h
라는 이름의 헤더 파일이 생성됩니다. 헤더 파일은 여러분이loadfile
원시 함수를 구현할 때, 사용해야 할 메소드 서명(method signature)을 제공해 줍니다.(To generate a a header file, run thejavah
command on theReadFile
class. In this example, the generated header file is namedReadFile.h
. It provides a method signature that you have to use when you implement theloadfile
native function.)javah -jni ReadFile
메소드 서명(Method Signature)
ReadFile.h
헤더 파일은 자바 메소드를 원시C함수로 연결해 주는 인터페이스를 정의하고 있습니다. 이 인터페이스는 자바 언어mappedfile.loadFile
메소드의 매개변수(arguments)와 리턴값(return value)을nativelib
라이브러리 내의loadFile
이라는 원시 메소드로 연결하기 위한 메소드 서명을 사용합니다. 다음은loadFile
원시 메소드 매핑을 위한 메소드 서명입니다.(TheReadFile.h
header file defines the interface to map the Java language method to the native C function. It uses a method signature to map the arguments and return value of the Java languagemappedfile.loadFile
method to theloadFile
native method in thenativelib
library. Here is theloadFile
native method mapping (method signature):)/* * Class: ReadFile * Method: loadFile * Signature: (Ljava/lang/String;)[B */ JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile (JNIEnv *, jobject, jstring);
JNIEnv *
: JNI환경에 대한 포인터입니다. 이 포인터는 자바 가상 머신 내에 있는 현재 스레드에 대한 핸들이고, 매핑 및 다른 살림살이(housekeeping)정보를 포함하고 있습니다.(A pointer to the JNI environment. This pointer is a handle to the current thread in the Java virtual machine, and contains mapping and other housekeeping information.)
jobject
:이 원시 코드를 호출한 메소드에 대한 참조(reference)입니다. 만약, 호출한 메소드가 클래스 메소드(static으로 선언된 메소드)이면, 이 매개변수(parameter)는 jobject
형 대신jclass
형이 될 것입니다.(A reference to the method that called this native code. If the calling method is static, this parameter would be typejclass
instead ofjobject
.)
jstring
:이 매개변수는(parameter)원시 메소드에 제공됩니다. 이 예에서는, 읽기 위한 파일의 이름이 되겠지요.(The parameter supplied to the native method. In this example, it is the name of the file to be read.)원시 메소드의 구현(Implement the Native Method)
loadFile
정의는ReadFile.h
헤더 파일 내에 포함되어 있는C선언을 그대로 복사하여 붙혀넣기(copy and paste)한 것입니다. 이 정의 바로 다음에 원시 메소드 구현이 오면 됩니다.JNI는 기본적으로C와C++모두를 위한 매핑을 제공해 줍니다.(In this native C source file, theloadFile
definition is a copy and paste of the C declaration contained inReadFile.h
. The definition is followed by the native method implementation. JNI provides a mapping for both C and C++ by default.)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);}
동적 및 공용 객체 라이브러리의 컴파일(Compile the Dynamic or Shared Object Library)
loadFile
예제를 위한 공용 객체 또는 동적 객체 라이브러리는 다음과 같이 컴파일 됩니다.(The library needs to be compiled as a dynamic or shared object library so it can be loaded at runtime. Static or archive libraries are compiled into an executable and cannot be loaded at runtime. The shared object or dynamic library for theloadFile
example is compiled as follows:)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 -lcGnu C++/Linux with Xbaseg++ -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/Win2000cl -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_PATHWindows NT/2000/95:set PATH=%path%;.
java ReadFile
스트링의 전달(Passing Strings)
자바 언어의String
객체는JNI에서jstring
으로 표현되며,16비트 유니코드 문자열이다.C에서, 문자열은 기본적으로8비트 문자들로부터 만들어진다. 그래서,C/C++함수에 전달된 자바 언어의String
객체를 참조하거나, 또는 자바 언어 메소드로 리턴된C/C++문자열을 참조하기 위해서는 원시 메소드 구현에 포함되어 있는JNI변환(conversion)함수들을 사용해야만 한다.(TheString
object in the Java language, which is represented asjstring
in Java Native Interface (JNI), is a 16 bit unicode string. In C a string is by default constructed from 8 bit characters. So, to access a Java languageString
object passed to a C or C++ function or return a C or C++ string to a Java language method, you need to use JNI conversion functions in your native method implementation.)GetStringUTFChar
함수는UTF(Unicode Transformation Format)를 이용하여16비트jstring
으로부터8비트 문자들을 얻어낸다.UTF는 유니코드를 어떤 정보의 손실도 없이8비트 또는16비트 문자들로 표현한다. 세 번째 매개변수는GetStringUTFChar
는 만약jstring
의 지역 복사본을 만들었을 경우에는JNI_TRUE
를 결과로 가지고, 그렇지 않을 경우에는JNI_FALSE
를 그 결과로 한다.(TheGetStringUTFChar
function retrieves 8-bit characters from a 16-bitjstring
using the Unicode Transformation Format (UTF). UTF represents Unicode as a string of 8 or 16 bit characters without losing any information. The third parameterGetStringUTFChar
results the resultJNI_TRUE
if it made a local copy of thejstring
orJNI_FALSE
otherwise.)C Version:(*env)->GetStringUTFChars(env, name, iscopy)C++ Version:env->GetStringUTFChars(name, iscopy)
jstring
으로 변환한다.(The following C JNI function converts an array of C characters to ajstring
:)(*env)->NewStringUTF(env, lastfile)
아래에 나오는 예제는lastfile[80]
C문자 배열을jstring
으로 변환하여, 호출한 자바 언어 메소드에 리턴해 준다.(The example below converts thelastfile[80]
C character array to ajstring
, which is returned to the calling Java language method:)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)에 대한 참조이다. (To let the Java1virtual machine know you are finished with the UTF representation, call theReleaseStringUTFChars
conversion function as shown below. The second argument is the originaljstring
value used to construct the UTF representation, and the third argument is the reference to the local representation of thatString
.)(*env)->ReleaseStringUTFChars(env, name, mfile);
만약, 원시 코드가 중간적인(intermediate)UTF표현 없이도 유니코드를 다루 수 있다면, 유니코드 문자열을 얻기 위해GetStringChars
함수를 호출하고,ReleaseStringChars
함수를 호출하여 그 참조를 해제할 수 있다.(If your native code can work with Unicode, without needing the intermediate UTF representation, call theGetStringChars
function to retrieve the unicode string, and release the reference with a call toReleaseStringChars
:)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
원시 메소드는 바이트 배열로 파일의 내용을 리턴하고, 이는 자바 프로그래밍 언어에서는 기본형이다. 자바 언어에서 적당한Type
Array함수를 호출함으로써 기본형을 얻고(retrieve)생성(create)할 수 있다. (In the example presented in the last section, theloadFile
native method returns the contents of a file in a byte array, which is a primitive type in the Java programming language. You can retrieve and create primitive types in the Java language by calling the appropriateType
Array function.)NewFloatArray
함수를 호출하거나,또는 새로운 바이트 배열을 생성하기 위해서는NewByteArray
함수를 호출하면 된다. 이름을 이런 방식으로 짓은 것은 배열에서 원소를 얻고, 원소를 추가하고, 배열 내의 원소를 변경하는 등에 대해서도 확장할 수 있다. 새로운 바이트 배열을 얻기 위해서는GetByteArrayElements
함수를 호출하면 된다. 배열에 원소를 추가하거나 또는 배열 내의 원소를 변경하기 위해서는Set<type>ArrayElements
함수를 호출하면 된다.(For example, to create a new array of floats, callNewFloatArray
, or to create a new array of bytes, callNewByteArray
. This naming scheme extends to retrieving elements from, adding elements to, and changing elements in the array. To get a new array of bytes, callGetByteArrayElements
. To add elements to or change elements in the array, callSet<type>ArrayElements
.)GetByteArrayElements
함수는 전체 배열에 영향을 미친다. 배열의 일부분에 대해 작업을 하기 위해서는 이 함수 대신GetByteArrayRegion
함수를 사용할수 있다. 그리고, 부분에 대해 배열의 원소를 변경하기 위한Set<type>ArrayRegion
함수만 있다. 그러나, 부분(region)의 크기가1일 수도 있고, 이는 존재하지 않는Sete<type>ArrayElements
함수와 같다.(TheGetByteArrayElements
function affects the entire array. To work on a portion of the array, callGetByteArrayRegion
instead. There is only aSet<type>ArrayRegion
function for changing array elements. However the region could be of size 1, which is equivalent to the non-existentSete<type>ArrayElements
.)
Native
Code TypeFunctions 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
원시 메소드에서는, 배열의 부분을 읽고 있는 파일의 크기로 명세함으로써 전체 배열이 갱신된다.(In theloadFile
native method from the example in the previous section, the entire array is updated by specifying a region that is the size of the file being read in:)jbyteArray jb; jb=(*env)->NewByteArray(env, finfo.st_size); (*env)->SetByteArrayRegion(env, jb, 0, finfo.st_size, (jbyte *)m); close(fd);
배열은 호출한 자바 언어 메소드로 리턴되고, 계속해서 더 이상 사용되지 않을 때, 배열에 대한 참조를 쓰레기 수집된다. 이 배열은 다음과 같이 호출함으로써 명시적으로 할당 해제 될 수도 있다.(The array is returned to the calling Java language method, which in turn, garbage collects the reference to the array when it is no longer used. The array can be explicitly freed with the following call.)(*env)-> ReleaseByteArrayElements(env, jb, (jbyte *)m, 0);
위에서ReleaseByteArrayElements
함수에 대한 마지막 매개변수는 다음과 같은 값을 가질 수 있다.(The last argument to theReleaseByteArrayElements
function above can have the following values:)
0
: C코드 내로부터의 배열에 대한 갱신이 자바 언어 복사본에 반영된다.(Updates to the array from within the C code are reflected in the Java language copy.)
JNI_COMMIT
:자바 언어 복사본은 갱신되지만, 지역 변수jbyteArray
는 해제되지 않는다.(The Java language copy is updated, but the localjbyteArray
is not freed.)
JNI_ABORT
:변경이 다시 복사되지는 않지만, jbyteArray
는 해제된다. 만약 배열이 복사본임을 의미하는 JNI_TRUE
얻기 모드(get mode)로 이 배열이 얻어진 경우에만 이 값이 사용될 수 있다.(Changes are not copied back, but thejbyteArray
is freed. The value is used only if the array is obtained with a get mode ofJNI_TRUE
meaning the array is a copy.)배열 고정시키기(Pinning Array)
배열을 참조(retrieving)할 때, 이 배열이 복사본인지(JNI_TRUE
)또는 자바 언어 프로그램 내에 존재하는 배열에 대한 참조인지(JNI_FALSE
)를 나타낼 수 있다. 만약, 배열에 대한 참조를 사용한다면, 배열이 자바 힙(Java heap)내에 있고, 쓰레기 수집기가 힙 메모리를 최적으로 정리할 때 옮겨지지 않기를 원할 것이다. 배열 참조가 옮겨지지 않도록 하기 위해, 자바 가상 머신은 배열을 메모리 내에 고정시킨다(pinning;못박는다). 배열을 고정시키는 것은 배열이 할당 해제될 때, 자바 가상 머신 내에서 정확한 요소들이 갱신되도록 하는 것을 보장한다.(When retrieving an array, you can specify if this is a copy (JNI_TRUE
) or a reference to the array residing in your Java language program (JNI_FALSE
). If you use a reference to the array, you will want the array to stay where it is in the Java heap and not get moved by the garbage collector when it compacts heap memory. To prevent the array references from being moved, the Java virtual machine pins the array into memory. Pinning the array ensures that when the array is released, the correct elements are updated in the Java VM.)loadfile
원시 메소드 예제에서는, 배열이명시적으로 해제되지 않았다. 배열이 더 이상 필요없을 때, 배열이 쓰레기 수집되도록 하는 확실한 하나의 방법은 자바 언어 메소드를 호출하고, 대신 바이트 배열을 전달하고, 그리고 나서 지역 배열 복사본을 해제하는 것이다. 이러한 기법은Multi-Dimensional Arrays에 대한섹션에서 보여질 것이다. (In theloadfile
native method example from the previous section, the array is not explicitly released. One way to ensure the array is garbage collected when it is no longer needed is to call a Java language method, pass the byte array instead, and then free the local array copy. This technique is shown in the section onMulti-Dimensional Arrays.)객체 배열(Object Arrays)
NewObjectArray
함수와SetObjectArrayElement
함수를 호출함으로써 배열 내에 어떤 자바 언어 객체도 저장할 수 있다. 객체 배열과 기본형 배열의 주요 차이점은jobjectarray
형을 생성할 때, 자바 언어 클래스가 매개변수로서 사용된다는 것이다.(You can store any Java language object in an array with theNewObjectArray
andSetObjectArrayElement
function calls. The main difference between an object array and an array of primitive types is that when constructing ajobjectarray
type, the Java language class is used as a parameter.)String
객체 배열을 생성하기 위해NewObjectArray
함수를 어떻게 호출하는지를 보여주고 있다. 배열의 크기는5(five)로 설정되고,FindClass
함수호출에서는 클래스 정의 리턴되고, 배열의 요소들은 공백 문자열로 초기화 된다. 배열 내에 저장할 위치(position)와 값을 가지고 SetObjectArrayElement
함수를 호출함으로써 배열의 요소들을 갱신할 수 있다.(This next C++ example shows how to callNewObjectArray
to create an array ofString
objects. The size of the array is set to five, the class definition is returned from a call toFindClass
, and the elements of the array are initialized with an empty string. The elements of the array are updated by callingSetObjectArrayElement
with the position and value to put in the array.)#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); }
이 원시 메소드를 호출하는 자바 클래스는 다음과 같다.(The Java class that calls this native method is as follows:)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차원 또는 다차원 배열을 사용한다.(You might need to call existing numerical and mathematical libraries such as the linear algebra library CLAPACK/LAPACK or other matrix crunching programs from your Java language program using native methods. Many of these libraries and programs use two-dimensional and higher order arrays.)jints
배열을 포함하고 있는 객체 배열로서 전달된다.GetObjectArrayElement
함수를 호출함으로써 객체 배열로부터jintArray
인스턴스를 먼저 가져오고,그리고 나서jintArray
행으로부터 요소들을 추출함으로써 각 요소들이 추출된다.(The array is passed as an object array that contains an array ofjints
. The individual elements are extracted by first retrieving ajintArray
instance from the object array by callingGetObjectArrayElement
, and then extracting the elements from thejintArray
row.)GetArrayLength(array)
함수를 사용하면 된다. 배열의 전체 크기를 알기 위해서는 배열의 각 차원에 대해GetArrayLength(array)
함수를 호출하면 된다.(The example uses a fixed size matrix. If you do not know the size of the array being used, theGetArrayLength(array)
function returns the size of the outermost array. You will need to call theGetArrayLength(array)
function on each dimension of the array to discover the total size of the array.)jintArray
인스턴스가 생성되고, 이 인스턴스는SetObjectArrayElement
함수를 호출함으로써 객체 배열 내에 설정된다.(The new array sent back to the program written in the Java language is built in reverse. First, a
jintArray
instance is created and that instance is set in the object array by callingSetObjectArrayElement
.)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++클래스 상의 함수를 호출하는 것을 다르게 만들 수도 있다.(So far, the native method examples have covered calling standalone C and C++ functions that either return a result or modify parameters passed into the function. However, C++ like the Java language uses instances of classes. If you create a class in one native method, the reference to this class does not have an equivalent class in the Java language, which makes it difficult to call functions on the C++ class that was first created.)new
연산자가C++객체에 대한 참조를 스택상에 생성하도록 하는 것이다.(One way to handle this situation is to keep a record of the C++ class reference and pass that back to a proxy or to the calling program. To ensure the C++ class persists across native method calls, use the C++new
operator to create a reference to the C++ object on the stack.)int
값으로 리턴된다. 여러분은32비트 이상의 머신에서는long
또는 보다 큰 값을 사용할 수 있다.(The following code provides a mapping between the Xbase database and Java language code. The Xbase database has a C++ API and uses an initialization class to perform subsequent database operations. When the class object is created, a pointer to this object is returned as a Java languageint
value. You can use along
or larger value for machines with greater than 32 bits.)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)한다.(The return result from the call to theinitdb
native method, theint
value, is passed to subsequent native method calls. The native code included in thedbmaplib.cc
library de--references the Java language object passed in as a parameter and retrieves the object pointer. The linexbDbf* Myfile=(xbDbf*)ptr;
casts theint
pointer value to be a pointer of Xbase typexbDbf
.)#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)
배열에 대한 이 섹션에서는 원시 코드 내로부터 자바 언어 메소드를 호출하려는 몇 가지 이유들에 대해 집중적으로 살펴본다. 예를 들면, 여러분이 리턴하려고 하는 결과를 해제할 필요가 있을 때가 그러한 경우이다. 만약, 여러분이 하나 이상의 결과를 되돌려주어야 할 필요가 있거나 또는 여러분이 그냥 단순히 원시 코드 내로부터 자바 언어 값들을 변경하기를 원할 경우,여러분의 원시 코드로부터 자바 원시 메소드를 호출하는 방법을 사용하게 된다.(The section on arrays highlighted some reasons for calling Java language methods from within native code; for example, when you need to free the result you intend to return. Other uses for calling Java native methods from within your native code would be if you need to return more than one result or you just simply want to modify Java language values from within native code.)클래스 참조 얻기(Retrieve a Class Reference)
첫번째 단계는 여러분이 참조하기를 원하는 메소드를 포함하고 있는 클래스에 대한 참조를 얻는 것이다. 참조를 얻기 위해서는FindClass
메소드를 사용하거나 원시 메소드에 대한 매개변수인jobject
또는jclass
를 참조(access)할 수 있다.(The first step is to retrieve a reference to the class that contains the methods you want to access. To retrieve a reference, you can either use theFindClass
method or access thejobject
orjclass
argument to the native method.)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
명령어를 실행하면 됩니다.(Once the class has been obtained, the second step is to call theGetMethodID
function to retrieve an identifier for a method you select in the class. The identifier is needed when calling the method of that class instance. Because the Java language supports method overloading, you also need to specify the particular method signature you want to call. To find out what signature your Java language method uses, run thejavap
command as follows:)javap -s Class
사용된 메소드 서명은 다음에 나타나 있는 것과 같이 각 메소드 선언 다음에 주석(comment)처럼 표시된다.(The method signature used is displayed as a comment after each method declaration as shown here:)bash# javap -s ArrayHandlerCompiled from ArrayHandler.javapublic 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
함수를 사용해야 한다. 이 두 함수에 대한 매개변수 리스트는 서로 동일하다.(Use theGetMethodID
function to call instance methods in an object instance, or use theGetStaticMethodID
function to call static method. Their argument lists are the same.)메소드 호출(Call the Methods)
세 번째,Call<type>Method
함수를 이용하여 해당 인스턴스 메소드를 호출한다.type
값은Void
,Object
,Boolean
,Byte
,Char
,Short
,Int
,Long
,Float
,또는Double
등이 될 수 있다.(Third, the matching instance method is called using aCall<type>Method
function. Thetype
value can beVoid
,Object
,Boolean
,Byte
,Char
,Short
,Int
,Long
,Float
, orDouble
.)Call<type>MethodA
함수에 대한 값들의 배열인 콤마로 구분되는 리스트(comma-separated list),또는 va_list
로서 전달될 수 있다.va_list
는C에서 다양한 매개변수 리스트를 위해 자주 사용되는 구조(construct)이다.CallMethodV
는va_list()
를 전달하기 위해 사용ㄷ회는 함수이다.(The parameters to the method can be passed as a comma-separated list, an array of values to theCall<type>MethodA
function, or as ava_list
. Theva_list
is a construct often used for variable argument lists in C.CallMethodV
is the function used to pass ava_list ()
.)CallStaticByteMethodA
와 같이Static구별자를 메소드의 이름에 추가적으로 포함하고,jclass
값이jobject
값 대신 사용된다는 것을 제외하면 같다.(Static methods are called in a similar way except the method naming includes an additional Static identifier,CallStaticByteMethodA
, and thejclass
value is used instead ofjobject
.)sendArrayResults
메소드를 호출함으로써ArrayHandler
로부터 객체 배열을 리턴한다.(The next example returns the object array by calling thesendArrayResults
method from theArrayHandler
class.)// ArrayHandler.javapublic 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++코드는 다음과 같이 정의된다.(The native C++ code is defined as follows:)#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;}
리눅스 상에서 이를 생성하기 위해서는,다음과 같은 명령어를 사용하면 된다.(To build this on Linux, run the following commands:)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
함수를 호출하면 가능하다.(If you want to specify a super class method; for example, to call the parent constructor, you can do so by calling theCallNonvirtual<type>Method
functions.)ExceptionClear
함수는 현재 발생한 어떤 예외들도 깨끗하게 처리(clear)하고, 반면ExceptionOccured
함수는 현재JNI세션 내에서 예외가 발생했는지를 검사한다.(One important point when calling Java language methods or fields from within native code is you need to catch any raised exceptions. TheExceptionClear
function clears any pending exceptions while theExceptionOccured
function checks to see if an exception has been raised in the current JNI session.)필드의 참조(Accessing Fields)
원시 코드 내로부터 자바 언어 필드를 참조하는 것은 자바 언어 메[소드를 호출하는 것과 유사하다. 그러나, 필드 또는 그 집합은 메소드ID가 아니라 필드ID를 가지고 얻어진다.(Accessing Java language fields from within native code is similar to calling Java language methods. However, the set or field is retrieved with a field ID, instead of a method ID.)GetFieldID
함수를 사용할 수 있고, 이 때 메소드 이름과 서명 대신 필드 이름과 서명을 주어야 한다. 필드ID를 얻었다면, 필드 값을 설정하기 위해서Get<type>Field
함수를 호출하면 된다.<type>
은j
가 빠지고, 첫번째 문자가 대문자가 되는 것을 제외하면, 리턴된 원시 형과 같다. 예를 들어, 원시 형jint
를 위한<type>
값은Int
이고, 원시 형jbyte
을 위한<type>
값은Byte
이다.(The first thing you need to do is retrieve a field ID. You can use theGetFieldID
function, but specify the field name and signature in place of the method name and signature. Once you have the field ID, call aGet<type>Field
function to set the field value. The<type>
is the same as the native type being returned except thej
is dropped and the first letter is capitalized. For example, the<type>
value isInt
for native typejint
, andByte
for native typejbyte
.)Get<type>Field
함수 결과는 원시 형으로 리턴된다. 예를 들어,ArrayHandler
클래스 내의arraySize
필드를 얻기 위해서는 다음에 나와 있는 예제에 나타난 것과 같이GetIntField
를 호출한다.(TheGet<type>Field
function result is returned as the native type. For example, to retrieve thearraySize
field in theArrayHandler
class, callGetIntField
as shown in the following example.)env->SetIntField(jobj, fid, arraysize)
함수를 호출함으로써 설정될 수 있다. 스태틱 필드는SetStaticIntField(jclass, fid, arraysize)
함수를 호출함으로써 설정될 수 있고,GetStaticIntField(jobj, fid)
함수를 호출함로써 얻을 수 있다.(The field can be set by calling theenv->SetIntField(jobj, fid, arraysize)
functions. Static fields can be set by callingSetStaticIntField(jclass, fid, arraysize)
and retrieved by callingGetStaticIntField(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)
비록 원시 라이브러리가 클래스 당 한번 적재되었더라도, 자바 언어로 작성된 애플리케이션 내의 각각의 스레드들은 원시 메소드를 호출할 때 그들 자신의 인터페이스 포인터를 사용한다. 만약, 원시 코드 내로부터의 자바 언어 메소드에 대한 참조를 제한하려 한다면, 호출한 자바 언어 메소드가 명시적인 동기화를 갖도록 확실하게 하든지 또는JNIMonitorEnter
함수와MonitorExit
함수를 사용하면 된다.(Although the native library is loaded once per class, individual threads in an application written in the Java language use their own interface pointer when calling the native method. If you need to restrict access to a Java language object from within native code, you can either ensure that the Java language methods you call have explicit synchronization or you can use the JNIMonitorEnter
andMonitorExit
functions.)synchronized
키워드를 명시하면 해당 코드는 모니터에 의해 보호된다. 자바 프로그래밍 언어에서,모니터 진입(monitor enter)과 모니터 진출(monitor exit)은 애플리케이션 개발자에게는 일반적으로 숨겨진다.JNI에서는, 스레드 안전 코드(thread safe code)에 대한 진입과 진출 위치를 명시적으로(explicitly)나타내 주어야 한다. (In the Java langauge, code is protected by a monitor whenever you specify thesynchronized
keyword. In the Java programming language, the monitor enter and exit routines are normally hidden from the application developer. In JNI, you need to explicitly delineate the entry and exit pointws of thread safe code.)CallVoidMethod
함수에 대한 참조를 제한하기 위해Boolean
객체를 사용하고 있다.(The following example uses aBoolean
object to restrict access to theCallVoidMethod
function.)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
를 사용하여 로컬 스레드 원시 이벤트 큐 또는 원시 코드 내로부터의 메시지시스템을 사용하는 것이 훨씬 좋다. (You may find that in cases where you want access to a local system resource like a MFC window handle or message queue, it is better to use one JavaThread
and access the local threaded native event queue or messaging system from within the native code.)메모리 이슈(Memory Issues)
기본적으로,JNI는 객체를 원시 메소드 내에 생성할 때, 지역 참조(local references)를 사용한다. 이는 메소드가 리턴할 때, 그 참조들이 쓰레기 수집되기에(garbage collected)적합하게 되도록 하기 위한 것이다. 만약, 어떤 객체가 원시 메소드 호출이 계속되는 동안영속하기를 원한다면, 대신 전역 참조(global reference)를 사용해야 한다. 지역 참조에 대해NewGlobalReference
함수를 호출하면 지역 참조에 대한 전역 참조(global reference)를 생성할 수 있다.(By default, JNI uses local references when creating objects inside a native method. This means when the method returns, the references are eligible to be garbage collected. If you want an object to persist across native method calls, use a global reference instead. A global reference is created from a local reference by callingNewGlobalReference
on the the local reference.)DeleteGlobalRef
함수를 호출함으로써 그 참조가 쓰레기 수집되도록 명시적으로 표시(mark)할 수 있다. 마찬가지로 메소드의 밖에서 참조가능하지만, 쓰레기 수집될 수 있는 연약한 스타일 전역 참조(weak style Global reference)를 생성할 수도 있다. 이러한 참조들 중 하나를 생성하기 위해서는 참조가 쓰레기 수집되도록 표시하기 위한NewWeakGlobalRef
함수와DeleteWeakGlobalRef
함수를 호출하면 된다.(You can explicitly mark a reference for garbage collection by callingDeleteGlobalRef
on the reference. You can also create a weak style Global reference that is accessible outside the method, but can be garbage collected. To create one of these references, callNewWeakGlobalRef
andDeleteWeakGlobalRef
to mark the reference for garbage collection.)env->DeleteLocalRef(localobject)
메소드를 호출함으로써, 쓰레기 수집될 지역 참조를 명시적으로 표시할 수도 있다. 이러한 방식은 대용량의 임시 데이터를 사용하고 있을 경우에 유용하게 사용할 수 있다.(You can even explicitly mark a local reference for garbage collection by calling theenv->DeleteLocalRef(localobject)
method. This is useful if you are using a large amount of temporary data.)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애플리테이션이라 할 수 있다.(The section on calling methods showed you how to call a method or field in a Java language program using the JNI interface and a class loaded using theFindClass
function. With a little more code, you can create a standalone program that invokes a Java virtual machine and includes its own JNI interface pointer that can be used to create instances of Java language classes. In the Java 2 release, the runtime program namedjava
is a small JNI application that does exactly that.)JNI_CreateJavaVM
를 호출하면 되고, 생성된 자바 가상 머신을 실행 정지(shutdown)시키기 위해서는JNI_DestroyJavaVM
를 호출하면 된다. 자바 가상 머신 역시 몇몇 추가적인 환경 속성(environment properties)을 필요로 할 것이다. 이러한 속성은JavaVMInitArgs
구조체 형태로JNI_CreateJavaVM
함수에 전달될 수 있다.(You can create a Java virtual machine with a call toJNI_CreateJavaVM
, and shut the created Java virtual machine down with a call toJNI_DestroyJavaVM
. A Java vitual machine might also need some additional environment properties. These properties can be passed to theJNI_CreateJavaVM
function in aJavaVMInitArgs
structure.)JavaVMInitArgs
구조체는 클래스패스(classpath),자바 가상 머신의 버전(Java virtual machine version)또는 일반적으로 명령행에서 프로그램으로 전달되는 시스템 속성 등과 같은 환경 정보를 저장하기 위해 사용되는JavaVMOption
값에 대한 포인터를 포함한다.(TheJavaVMInitArgs
structure contains a pointer to aJavaVMOption
value used to store environment information such as the classpath and Java virtual machine version, or system properties that would normally be passed on the command line to the program.)JNI_CreateJavaVM
함수가 리턴할 때,FindClass
함수와NewObject
함수를이용하여 클래스의 메소드를 호출하거나 인스턴스를 생성할 수 있다. 임베디드 원시 코드(embedded native code)에 대해서도 같은 방식이다.(When theJNI_CreateJavaVM
function returns, you can call methods and create instances of classes using theFindClass
andNewObject
functions the same way you would for embedded native code.)
주의(Note):자바 가상 머신 수행은 단지 원시 스레드 자바 가상 머신에 대해서만 사용될 수 있다. 조금 구 버전의 자바 가상 머신은 수행 사용(invocation use)에 대해 안전한 그린 스레드 옵션(green threads option)을 가지고 있다. 유닉스 플랫폼 상에서는-lthread
또는-lpthread
와 명시적으로 링크해 주어야 한다.(The Java virtual machine invocation used to be only used for native thread Java virtual machines. Some older Java virtual machines have a green threads option that is stable for invocation use. On a Unix platform, you may also need to explicitly link with-lthread
or-lpthread
.)
다음에 나오는 프로그램은 자바 가상 머신을 수행하고,ArrayHandler
클래스를 적재하고,-1값을 포함하고 있는arraySize
필드를 얻는다. 자바 가상 머신 옵션은 클래스패스(classpath)에 현재 경로를 포함하고,-Djava.compiler=NONE
옵션을 이용하여JIT(Just-In-Time)컴파일러가 작동되지 않도록 한다.(This next program invokes a Java virtual machine, loads theArrayHandler
class, and retrieves thearraySize
field which should contain the value minus one. The Java virtual machine options include the current path in the classpath and turning the Just-In-Time (JIT) compiler off-Djava.compiler=NONE
.)#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)원시 애플리케이션을 작성하기를 원할 경우에는 이렇게하기를 원할 것이다.(After the Java virtual machine is invoked, there is one local thread running the Java virtual machine. You can create more threads in the local operating system and attach the Java virtual machine to those new threads. You might want to do this if your native application is multi-threaded.)AttachCurrentThread
함수를 이용하면 된다. 이 때, 자바 가상 머신의 인스턴스와JNI환경에 대한 포인터를 제공해 주어야 한다. 자바2플랫폼에서는, 세 번째 매개변수에 스레드 이름이나 새로운 스레드가 속하게 될 그룹을 명시해 줄 수 있다. 먼저 연결되었던 스레드를 연결 해제 하는 것은 중요하다. 그렇지 않을 경우,프로그램은DestroyJavaVM
함수를 호출하더라도 빠져나가지(exit)않게 된다.(Attach the local thread to the Java virtual machine with a call to
AttachCurrentThread
. You need to supply pointers to the Java virtual machine instance and JNI environment. In the Java 2 platform, you can also specify in the third parameter the thread name and/or group you want this new thread to live under. It is important to detach any thread that has been previously attached; otherwise, the program will not exit when you callDestroyJavaVM
.)#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);}
'Java' 카테고리의 다른 글
자바를 이용한 SNMP (1) | 2006.04.17 |
---|---|
JNI(Java Native Interface) Part I (0) | 2006.03.24 |
JNI(Java Native Interface) Part II (0) | 2006.03.24 |