Android

JNI : Java Native Interface

단돈백이원 2022. 5. 17. 08:24

자바 네이티브 인터페이스(Java Native Interface, JNI)는 자바 가상 머신(JVM)위에서 실행되고 있는 자바코드가 네이티브 응용 프로그램(하드웨어와 운영 체제 플랫폼에 종속된 프로그램들) 그리고 C, C++ 그리고 어샘블리 같은 다른 언어들로 작성된 라이브러리들을 호출하거나 반대로 호출되는 것을 가능하게 하는 프로그래밍 프레임워크이다.

  • 참고
  • 자바에서 Method를 다른 프로그래밍 언어로 구현하는 것을 말한다. 성능 향상이나 하드웨어 제어같은 목적을 위해서 사용한다.
    • 하드웨어에 종속된 API 등을 시스템콜로 불러오지 못하는 경우 혹은 성능상 문제로 그렇게 불러와서는 안되는 경우 반드시 필요할 수 있음.
  • JNI를 생성하는 단계는 크게 4단계이다.
    1. JNI를 사용할 java파일을 작성.
    2. java파일을 javac로 컴파일 후 클래스파일을 javah를 이용해 c header(.h)파일을 생성.
    3. 인터페이스 역할을 하는 .h파일을 include하고 정의된 함수를 구현할 네이티브 소스파일 작성.
    4. 기계어로 변환할 컴파일러( 일반적으로 c컴파일러. gcc등. )에 JNI 헤더파일 인클루드 경로를 지정하고, 네이티브 소스를 컴파일하여 네이티브 라이브러리를 생성한다.
  • 생성된 라이브러리를 사용하여 실행하려면 java 실행시 라이브러리 위치를 알려주어야한다.
    • java -Djava.library.path={라이브러리 위치} NativeMethodTest
    • 혹은 스크립트로 실행하는 경우라면 환경변수 갱신하여 실행시에는 굳이 안넣어도됨.
      • export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. # java 환경변수중 라이브러리 path를 현재 위치를 참조하도록 갱신함.
  • 파라미터/리턴데이터
    • https://realapril.tistory.com/35 를 참고하는게 좋다.
    • 기본형 데이터는 javah가 알아서 변환해 주어서 그거 보고 j로 시작하는 JNI 유닛단위(typedef로 래핑된 c 기본형)으로 주고받으면 된다.
      • 혹은 JDK폴더의 include\jni.h와 include\win32\jni_md.h에 아래와 같이 8개변수에 대해 정의되어 있다.
      • 단, c에서 데이터를 처리시 JNI 유닛단위로 변환하는 경우 java와 c사이에 기본데이터형이 다르므로 컨버팅에 주의해야함.
    • 객체형 데이터는
  • 알쓸신잡
    • javah를 지원하기 전엔 헤더파일을 일일히 다 작성해줘야 했다고함. 결국 편의성을 위한 도구임.

예시

  • `HelloWorld.java` : Native 라이브러리를 참조할 코드를 정의한 java 코드
class HelloWorld
{
	static{
		System.loadLibrary("HelloWorld"); // 생성된 라이브러리를 System.loadLibrary("")로 불러온다. javah로 생성한 이름이다.
	}
    
	private native void print(); // 라이브러리에 정의된 함수를 호출할 수 있도록 native 멤버로 선언한다. 
	public static void main(String[] args)
	{
		new HelloWorld().print(); // HelloWorld가 제공하는 native 메서드 print를 호출.
	}
}
  • `HelloWorld.h` : JNI 라이브러리를 import하려는 java파일을 compile한 결과물인 class파일을 `javah`도구를 이용해 생성한 헤더파일.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    print
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_print // c 파일에서 구현해야할 함수 정의.
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  • `libHelloWorld.c `: 실제 네이티브 call을 할 c파일. 생성된 header파일에 정의된 함수를 구현해야한다.
 #include <stdio.h>
 #include "HelloWorld.h" // 인터페이스가 되는 헤더파일 include 

 JNIEXPORT void JNICALL 
 Java_HelloWorld_print(JNIEnv *env, jobject obj) // 정의된 인터페이스 구현.
 {
     printf("Hello World!\n");
     return;
 }
  • `make.sh` : JNI를 사용하는 `class` 및 JNI 라이브러리를 생성할 수 있게끔하는 빌드 자동화 파일.
#!/bin/sh

# openbsd 4.9
# gcc 4.2.1
# openjdk 1.7.0
JAVA_HOME=$(readlink -f /usr/bin/javac | sed "s:bin/javac::")
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. # java 환경변수중 라이브러리 path를 현재 위치를 참조하도록 갱신함.
javac HelloWorld.java # java파일을 컴파일하여 class파일을 생성.
javah HelloWorld # javah : class파일로 JNI용 c header 파일을 생성 할 수 있는 도구.
gcc -I${JAVA_HOME}/include -shared libHelloWorld.c -o libHelloWorld.so # 지정된 컴파일러에 jni 헤더파일 include 경로지정( -I ), 컴파일하여 정적/동적링크파일을 생성할 JNI문법으로 작성된 c파일( .c ) 그리고 생성될 라이브러리 이름 ( -o .so 등 )을 지정하여 컴파일한다. 
java HelloWorld # 실행. 위에서 환경변수를 갱신했기 때문에 -D 옵션을 사용하지 않음.

`chmod +x make.sh` `./make.sh`