본문 바로가기
Programming/etc

[JNI] JNI 사용법 및 튜토리얼 (1)

by ga.0_0.ga 2023. 3. 11.
728x90
반응형

이번 포스팅에서는 JNI에 대한 간단한 설명과 튜토리얼을 실습해보겠습니다!

 

 

▶ JNI 란?

먼저! JNI란 무엇일까요? JNI 는 JAVA Native Interface 의 줄임말입니다.

JAVA는 가상머신 위에서 실행되는 언어입니다. 그렇기 때문에, java 가상 머신을 실행할 수 있는 모든 기계나 장치에서 실행할 수 있습니다. 

하지만,, 종종 특정 아키텍처에서 고유하게 컴파일 된 코드를 사용해야 하는 경우가 있습니다. JAVA 코드에서 C나 C++로 작성된 라이브러리들을 호출해야 하는 경우들이 이에 해당합니다. 이때, 이 두 언어 사이를 연결해주는 브리지가 필요하고 이를 JNI라고 합니다.

 

 

▶ JNI의 사용이유?

JNI의 구체적인 사용이유로는 아래 3가지를 꼽을 수 있습니다.

1. 일부 하드웨어를 처리해야 하는 경우가 필요합니다.

2. 매우 까다로운 프로세스들의 성능향상이 필요한 경우가 발생합니다.

3. Java로 다시 작성하는 대신 기존의 코드들을 재사용 해야 하는 라이브러리들이 존재합니다.

 

 

▶ 필수 구성요소

1. Java 코드

2. 네이티브 코드

3. JNI 헤더 파일

4. C/C++ 컴파일러

 

- Java 코드 

=> 네이티브 키워드 - 네이티브 키워드로 표시된 모든 메소드는 네이티브 공유 라이브러리에서 구현되어 있어야 합니다.

=> System.loadLibrary()- 공유 라이브러리를 메모리에 로드하고 내보낸 함수를 Java 코드에서 사용할 수 있도록 하는 정적 메소드입니다.

 

- C/C++ 코드

=> JNIEXPORT - 함수를 공유 라이브러리에 내보내기 기능으로 표시하여 함수 테이블에 포함되도록하고, 이러한 과정을 통해 JNI가 해단 함수를 찾을 수 있도록 합니다.

=> JNIEnv - 네이티브 코드를 사용하여 Java 요소에 엑세스할 수 있는 메소드가 포함된 구조입니다.

 

 

▶ 간단한 튜토리얼

step 1) JNI 선언이 포함된 Java Class를 작성합니다.

c++로 작성된 코드에서 수행될 함수들을 정의 하는 단계입니다.

Hello를 출력하는 함수와 특정 문자열을 함수의 파라미터로 받아 출력하는 함수, 2가지를 먼저 정의하겠습니다.

native void printHello();
native void printString(String str);

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

 첫 번째, 두 번째줄의 함수 정의부는 가장 앞에 꼭 "native" 키워드를 사용해주어야 합니다. 이렇게 선언해야 두 함수가 호출될 때 java코드가 아닌 네이티브로 구현된 메소드를 찾게됩니다. 

세 번쨰 줄은 네이티브로 작성된 라이브러리를 지정하는 부분입니다. 저는 libhellojni.so 라는 라이브러리를 로드하도록 할 것이고, 코드로 작성시에는 "hellojni"만 적어주면 됩니다.

전체 java 코드는 아래와 같습니다. 나머지 부분은 일반적인 자바코드와 동일합니다. 편의를 위해 package 명은 작성하지 않고 진행하였습니다. 만약 package명을 사용하고 싶다면 주석처리 된 부분을 추가하면 됩니다.

// package com.test.testjni;

public class HelloJNI{

    native void printHello();
    native void printString(String str);

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

    public static void main(String[] args){
        HelloJNI myJNI = new HelloJNI();

        myJNI.printHello();
        myJNI.printString("Hello World from java ~~~ ");

        System.out.println("Success");
    }
}

 

step 2) javah 명령어를 이용한 C헤더 파일 생성

먼저, 위의 작성한 자바 파일을 javac 명령어를 이용해 .class파일을 생성합니다.

javac HelloJNI.java

같은 폴더 안에 HelloJNI.class 파일이 생성된 것을 확인 할 수 있을겁니다! 다음은 이렇게 생성된 .class 파일을 이용해 헤더파일을 만들어보겠습니다. 아래 명령어를 이용하면 됩니다.

javah HelloJNI 

// 패키지명 사용시에는 패키지의 최상위 폴더가 들어있는 상위 폴더로 이동 후 아래 명령어 실행
// 예제에서는 com의 상위 폴더로 이동 후 실행하면 됩니다.
javah -jni com.test.testjni.HelloJNI

이렇게 하여 생성된 헤더파일(.h)은 다음과 같습니다.

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

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printHello
  (JNIEnv *, jobject);

/*
 * Class:     HelloJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printString
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

자바 쪽에서 정의한 printHello()함수와 printString() 함수의 선언부를 살펴보겠습니다. (Java_패키지_클래스_메소드) 순서로 자동생성되었습니다. 예제에서는 패키지를 사용하지 않았기 때문에 패키지는 생략되었습니다. 만약 패키지를 사용한다면 선언부는 아래처럼 생성됩니다.

JNIEXPORT void JNICALL Java_com_test_testjni_HelloJNI_printHello
  (JNIEnv *, jobject);

/*
 * Class:     HelloJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_test_testjni_HelloJNI_printString
  (JNIEnv *, jobject, jstring);

 

step 3) 헤더 파일에 선언된 C++ 메소드 생성

자바 쪽에서 정의한 함수들을 구현합니다

#include "HelloJNI.h"
#include <iostream>
using namespace std;

JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj){
    cout << "Hello" << endl;
    return;
}

JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string){
    const char *str = env->GetStringUTFChars(string, 0);
    cout << str << endl;

    return;
}

먼저 step2에서 생성한 헤더 파일을 include합니다. 그리고 함수를 구현할 때는 아래 형식을 따릅니다.

JNIEXPORT 리턴형 JNICALL Java_클래스이름_함수이름(JNIEnv *env, jobject obj, 파라미터들...){
    구현부...
    
}

다음은 printString 함수를 살펴보겠습니다. 파라미터로 넘어오는 string 타입의 변수를 별도의 c++ 변수에 저장하려면

env->GetStringUTFChars(string, 0)을 이용해주면 됩니다.

 

 

step 4) 공유 라이브러리 생성

이렇게 작성된 코드를 컴파일 하고 자바에서 사용하게 될 공유 라이브러리로 만들어보겠습니다. 아래 명령어를 이용하면 됩니다.

g++ -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -c hellojni.cpp -o hellojni.o 

g++ -shared -fPIC -o libhellojni.so hellojni.o

위 두 명령어를 차례대로 실행해 주면 됩니다. 그럼 최종적으로 libhellojni.so 파일이 생성되게 되고, 이 파일을 step 1의 

static {System.loadLibrary("hellojni");} 부분에서 로드하여 사용하는 것입니다.

 

 

step 5) 자바 프로젝트 실행하기

이제 다시 자바 프로젝트를 실행해보겠습니다. 경로를 찾지 못하는 문제가 발생할 수 있으므로 아래 명령어로 실행하는 것을 추천합니다.

java -Djava.library.path="." HelloJNI

// 패키지명이 있을 경우
java -Djava.library.path="/home/work/com/test/testjni/" -classpath home/work/*: com.test.testjni.HelloJNI

"Hello" 와 인자로 넘겨준 "Hello World from java ~~~ " 가 출력되는 것을 확인할 수 있습니다.

 

 

 

 

 

 

728x90
반응형

댓글