안녕하세요 돼지왕왕돼지입니다.
오늘은 JNI Tutorial 중 "Invocation Interface" 에 대해 알아보겠습니다.
이 글은 http://java.sun.com/docs/books/jni/html/invoke.html#11202 내용을 요약 정리한 내용입니다.
Invocation Interface
이번 section 에서는 JVM 을 어떻게 native application 에 포함시킬지에 대해 알아봅니다. JVM 구현은 보통 native library 형태로 전달됩니다. native application 은 이 library 를 링크하고, invocation interface 를 통해 JVM 을 로드합니다.
Creating the Java Virtual Machine
<JNI C, C++>
#include <jni.h>
#define PATH_SEPARATOR ';' /* define it to be ':' on Solaris */
#define USER_CLASSPATH "." /* where Prog.class is */
main() {
JNIEnv *env;
JavaVM *jvm;
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jclass stringClass;
jobjectArray args;
#ifdef JNI_VERSION_1_2
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString =
"-Djava.class.path=" USER_CLASSPATH;
vm_args.version = 0x00010002;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */
if (res < 0) {
fprintf(stderr, "Can't create Java VM\n");
exit(1);
}
cls = (*env)->FindClass(env, "Prog");
if (cls == NULL) {
goto destroy;
}
mid = (*env)->GetStaticMethodID(env, cls, "main",
"([Ljava/lang/String;)V");
if (mid == NULL) {
goto destroy;
}
jstr = (*env)->NewStringUTF(env, " from C!");
if (jstr == NULL) {
goto destroy;
}
stringClass = (*env)->FindClass(env, "java/lang/String");
args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
if (args == NULL) {
goto destroy;
}
(*env)->CallStaticVoidMethod(env, cls, mid, args);
destroy:
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionDescribe(env);
}
(*jvm)->DestroyJavaVM(jvm);
}
<Java>
public class Prog {
public static void main(String[] args) {
System.out.println("Hello World " + args[0]);
}
}
JVM 을 load 하기 위해서는 initialize argument 들이 필요합니다. JDK1.1에는 JDK1_1InitArgs 라는 것을 사용하고, JDK1.2 부터는 JavaVMInitArgs 라는 것을 제공합니다. ( JDK1_1InitArgs 는 1.2에서도 물론 사용 가능합니다. ) JDK 1.1 에서는 JNI_GetDefaultJavaWMInitArgs 를 호출하여 default VM setting 을 얻어옵니다. 이 함수는 heap size, stack size, default class path 등을 vm_args 에 담아서 제공합니다. 이 vm_args에호출하고자 하는 class 의 path 를 vm_args.classpath 에 저장합니다. 1.2에서는 JavaVMOption array 에 initialize array 에 저장합니다. 그리고 command line 에 들어갈, common option이나 implementation specific option 들을 넣어줍니다. ignoreUnrecognized 필드를 JNI_TRUE 로 설정함으로서 인식 못하는 option 들은 ignore 할 수 있게 해줄 수 있습니다.
이렇게 init args 설정을 마치면, JNI_CreateJavaVM 을 호출하여 JVM 을 load 하고 initialize 해줍니다. 이 함수는 JVM 에 대한 interface pointer 와 JNIEnv interface pointer 를 return 해줍니다.
사용을 마치면 DestoryJavaVM 함수를 호출하여 JVM 을 unload 합니다. ( JDK 1.1과 JDK 1.2 에서는 DestoryJavaVM 이 항상 에러 코드를 return 합니다. ).
Linking Native Applications with the Java Virtual Machine.
Linking with a known Java Virtual Machine.
@Solaris
cc -I<jni.h dir> -L<libjava.so dir> -lthread -ljava invoke.c
-lthread 는 native thread support 하에 JVM 을 사용하겠다는 의미이고, -ljava 는 libjava.so 는 Solaris version lib 이라는 걸 명시합니다.
@Windows
cl -I<jni.h dir> -MD invoke.c -link <javai.lib dir>\javai.lib
-MD 는 Win32 multithreaded C library 와 native app 이 연결되었다는 것을 의미하고, javai.lib 파일은 VM 연결과 관련된 내용을 담고 있습니다. windows 버전에서는 javai.dll 이라는 dynamic link library 를 runtime 이 가지고 있습니다.
JDK 1.2 version 에서는 libvm.so 와 jvm.lib 또는 jvm.dll 이 각각 사용됩니다.
shared library 나 dynmaic link library 연결에 실패한다면, LD_LIBRARY_PATH 나 PATH variable 에 path 를 제대로 기술해줘야 합니다.
Linking with Unknown Java Virtual Machines.
다른 알려지지 않은 vendor 의 VM 을 쓰기 위해서는
@Windows
LoadLibrary와 GetProcAddress 를 통해서 코드에서 load 를 해주어야 합니다.
/* Win32 version */
void *JNU_FindCreateJavaVM(char *vmlibpath)
{
HINSTANCE hVM = LoadLibrary(vmlibpath);
if (hVM == NULL) {
return NULL;
}
return GetProcAddress(hVM, "JNI_CreateJavaVM");
}
@Solaris
dlopen 과 dlsym 을 통해 libary 를 로드해줘야 합니다.
/* Solaris version */
void *JNU_FindCreateJavaVM(char *vmlibpath)
{
void *libVM = dlopen(vmlibpath, RTLD_LAZY);
if (libVM == NULL) {
return NULL;
}
return dlsym(libVM, "JNI_CreateJavaVM");
}
Attaching Native Threads
어떤 일을 처리할 때 ( 예를 들면 네트워크 ), 여러개의 thread 로 나눠서 일을 처리할 일이 많이 생깁니다. 이 때 여러 thread 에서 JVM 을 사용하게 될 경우에는 다른 thread 를 방해하지 않고, JVM 을 쓰려고 하는 thread 가 JVM 을 붙였다가, 떼었다가 할 수 있어야 합니다. JVM 보다 해당 thread 가 먼저 종료될 수 있기 때문입니다.
다음 코드는 windows 에서 JVM에 thread 를 붙이고 떼는 것을 보여줍니다.
/* Note: This program only works on Win32 */
#include <windows.h>
#include <jni.h>
JavaVM *jvm; /* The virtual machine instance */
#define PATH_SEPARATOR ';'
#define USER_CLASSPATH "." /* where Prog.class is */
void thread_fun(void *arg)
{
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jclass stringClass;
jobjectArray args;
JNIEnv *env;
char buf[100];
int threadNum = (int)arg;
/* Pass NULL as the third argument */
#ifdef JNI_VERSION_1_2
res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
#else
res = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
#endif
if (res < 0) {
fprintf(stderr, "Attach failed\n");
return;
}
cls = (*env)->FindClass(env, "Prog");
if (cls == NULL) {
goto detach;
}
mid = (*env)->GetStaticMethodID(env, cls, "main",
"([Ljava/lang/String;)V");
if (mid == NULL) {
goto detach;
}
sprintf(buf, " from Thread %d", threadNum);
jstr = (*env)->NewStringUTF(env, buf);
if (jstr == NULL) {
goto detach;
}
stringClass = (*env)->FindClass(env, "java/lang/String");
args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
if (args == NULL) {
goto detach;
}
(*env)->CallStaticVoidMethod(env, cls, mid, args);
detach:
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionDescribe(env);
}
(*jvm)->DetachCurrentThread(jvm);
}
main() {
JNIEnv *env;
int i;
jint res;
#ifdef JNI_VERSION_1_2
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString =
"-Djava.class.path=" USER_CLASSPATH;
vm_args.version = 0x00010002;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = TRUE;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */
if (res < 0) {
fprintf(stderr, "Can't create Java VM\n");
exit(1);
}
for (i = 0; i < 5; i++)
/* We pass the thread number to every thread */
_beginthread(thread_fun, 0, (void *)i);
Sleep(1000); /* wait for threads to start */
(*jvm)->DestroyJavaVM(jvm);
}
주의깊게 봐야할 것은 DestoryJavaVM 은 모든 thread 가 종료된 이후에 수행됩니다. ( JVM 이 완전히 free 되면 ) JNI_AttachCurrentThread 는 현재 thread 를 JVM 에 attach 합니다. 해당 thread 가 종료할 때에는 DetachCurrentThread 를 수행시켜 local reference 들을 해제해줍니다.
댓글