[JNI]
- Java Native Interface의 약자로,
자바로, C와 C++의 코드를 실행시키는 인터페이스를 말합니다.(꼭 C C++에 국한된 것은 아니지만, 실질적으로 사용되는 것이 C/ C++입니다.)
(기본 설명)
- SW 프로그램 구동 원리를 이해해야 합니다.
- '소스 코드는 텍스트일 뿐이다.'
텍스트인 소스 코드를 개발자가 작성하여, 컴퓨터에게 일을 시키기 위해서는 이를 컴퓨터가 이해할수 있는 명령 체계로 변환을 시켜야 합니다.
이것이 바로 컴파일링 입니다.
각 프로그래밍 언어별로, 문법이 서로 다르고,
이 다른 문법을 해석하여, 컴퓨터가 이해하고 실행할수 있는 형식으로 변화시켜 주는 것이 바로 각 언어의 컴파일러의 역할입니다.
당연히 다른 언어는 다른 문법체계를 가지고, 다른 컴파일러를 가지고 있습니다.
이를 달리 말하자면, 결국 어떤 언어든간에, 서로 다른 언어는, 소스코드 텍스트 상으로, 서로 다르고, 실제 실행될 시에는 바이너리로써 기계어가 되는 것입니다.
- 프로그램이라는 것은 결국 CPU에게 일을 시키는 명령 체계를 말하는 것인데, 그렇다고 해서 모든 언어의 소스코드가 동일한 바이너리 코드로 변하는건 아닙니다.
C와 자바는 개념부터, 실행 환경까지 다릅니다.
C는, 직접 바이너리 코드로 변환되어 CPU에게 직접 일을 시키기에, 빠르지만, 하드웨어와 직접 연결되어 있기에, 하드웨어별로 호환성이 보장되지 않고, 컴퓨터 자원 관련해서도 신경써야할 것이 많이 있습니다.
(C언어 메타 개발자들이 신경쓰는건, C언어의 문법 + C언어 컴파일러죠.)
자바 코드는, 직접 바이너리 코드로 변환되는 것이 아닙니다.
자바 프로그램이 실행되면, 먼저 자바 코드가 메모리에 올라가는 것이 아니라, JVM이라는, 자바 프로그램 실행 환경이 돌아가게 되고,
바로 그 위에, 자바 코드, 그러니까 컴파일된 자바 바이트 코드가 돌아가게 되는 것입니다.
(자바 언어 개발자들이 신경쓰는 것은, 자바 문법과 + JVM입니다.)
즉, 두 언어간의 개념 자체가 다르다는 것을 설명한 것입니다.
- 서로 다른 언어를 통합시키고자 할 때도 있습니다.
예를들어, 내 친구는 C 개발자로, 아주 멋진 모듈을 만들었기에, 그 소스코드를 제품으로 만들어 팔고 싶지만, 꾸미기를 잘 못하고,
나의 경우는 자바 개발자로, 일단 모듈만 주어지면 아주 멋지게 제품을 만들어낼수 있는데, 자바 모듈로는 퍼포먼스가 별로 안 좋아서 좌절중이죠.
서버 개발이야 MSA 를 구축하여 연결하면 되지만, Android 와 같은 클라이언트 서비스를 클라이언트 환경에서 돌리고 싶다면, 바로 위와 같은 두 언어 사이의 차이점에 의한 문제가 대두됩니다.
자바에서 C 언어를 돌리고 싶다?
그럴 때 JNI 를 사용합니다.
(JNI)
- JNI는 정확히 말하자면, C, C++의 '함수'를 자바에서 사용할수 있도록 도와주는 역할을 합니다.
- C언어에서 만들어낸 dll을 이용하여, 해당 '라이브러리를 자바 객체로써' 가져오고,
자바 프로그램에서 이 객체를 사용하여, 자바의 인자값을 전해주거나, C 모듈의 반환값을 가져오거나, C 로직으로 작업을 수행하는 등의 일을 할수 있습니다.
여기서 사용되는 C 함수는, 자바의 바이트 코드로써 JVM이 실행시키는 것이 아니라,
단지 JVM이 해당 바이너리 코드를 메모리에 올려서, 독립된 C 실행 프로그램으로써 실행하게 하고, 그 사이에 인자값이나 반환값에 대한 데이터 교환의 역할만을 보장하는 것입니다.
(즉, 실행되는 C 로직은 JVM에 의해 실행되는 것이 아니기에, GC와 같은 해택을 받지 못하고, 하드웨어별로 영향을 받게 됩니다.)
(JNI 기술 적용)
- 자바코드
public class JNI {
static {
System.loadLibrary("hello-jni");
}
private native int getNumber();
private native void printHelloWorld();
public static void main(String[] args){
JNI jni = new JNI();
jni.printHelloWorld(); //JNI로 호출 한 HelloWorld!
System.out.println(jni.getNumber()); //JNI로 호출 한 숫자 메서드
}
}
1. 먼저 가져올 C언어 바이너리 파일명을 적어줍니다.
확장자는 필요 없이, 절대/ 상대 경로를 적어주면 됩니다.
static 필드로 인해, 그 안에서 사용되는, System.loadLibrary를 사용하면, JNI를 사용할 준비가 끝난 것입니다.
2. 사용할 네이티브 함수 명을 선언해 줍니다.
native 키워드를 사용하여, C 파일 내에 사용된 함수명과 동일한 이름으로, 선언하면 됩니다.
3. JNI 객체를 생성해줍니다. //native 메소드를 만들고, static 블록으로 System.loadLibrary를 해준 클래스를 객체화 하면 됩니다.(native 적용 클래스를 따로 떼어내서 작성후 사용해도 되겠네요.)
4. 생성한 jni 객체 변수를 사용하여, 해당 네이티브 함수를 사용해주면 됩니다.
- 자바 코드 컴파일
javac 패키지명.클래스명
javah -jni 패키지명.클래스명
위와 같이 javac로 컴파일 하여 바이트 코드인 .class를 생성하고,
javah를 이용하여, 자바 클래스의 .h 헤더 파일을 추출합니다.
- C 코드
: hello-jni.c
라는 이름으로 프로젝트를 만들어 주세요.(자바의 System.loadLibrary에 쓴 dll 파일명과 같으면 됩니다.)
프로젝트의 '헤더파일' 폴더에, 자바로 만들었던 헤더파일을 넣어줍니다.
그리고 해당 프로젝트 속성 페이지로 가서, 아래와 같이, dll 형식으로 프로젝트를 변형시켜줍니다.
이후, C플러스에서 사용할 라이브러리를 위해, 아래와 같이 JDK Include를 추가시켜 주세요.
- 위와 같은 준비가 끝났다면,
자바에서 사용할 C코드를 작성해봅시다.
#include <stdio.h>
#include <jni.h>
#include "jni_JNI.h"
JNIEXPORT jint JNICALL Java_jni_JNI_getNumber(JNIEnv *env, jobject jobj){
return 3;
}
JNIEXPORT void JNICALL Java_jni_JNI_printHelloWorld(JNIEnv *env, jobject jobj){
printf("Hello World!");
}
- 위와 같이 작성합니다.
JNI를 적용할 C 모듈의 작성 문법은 이따가 정리하겠습니다.
- 이제, 작성된 C 코드를 컴파일 하여, dll파일을 만들어둡시다.
- 작동 : 만들어진 C 코드 dll을, 자바 실행 경로에 넣고, 자바 JNI 프로젝트를 컴파일 하면, 이제 JNI에 의해서 C언어가 잘 실행될 것입니다.
- 이러한 방식으로 사용된 C 코드는, JVM이 아니라 CPU에 직접 실행되는 코드이기에, JVM으로는 구현할수 없는 부분의 기능을 해내거나, 보다 퍼포먼스가 올라갈 테지만, 해당 코드에 의존하는 자바 코드는, 범용성이 가져다주는 장점을 잃게 될것입니다.
(C언어 JNI 작성 문법)
- 데이터 타입 : c언어에서 JNI 모듈을 만들기 위해 사용하는 데이터 타입 이름은 아래와 같이 사용해야 합니다.
Java | JNI(C/C++) | JNI(C/C++ 배열) |
boolean | jboolean | jbooleanArray |
byte | jbyte | jbyteArray |
char | jchar | jcharArray |
short | jshort | jshortArray |
int | jint | jintArray |
long | jlong | jlongArray |
float | jfloat | jfloatArray |
double | jdouble | jdoubleArray |
object | jobject | jobjectArray |
void | void |
- JNI 문법
A. 함수 이름은 반드시 <반환값>_Java_<패키지명>_<클래스명>_<메서드명> 이어야 한다.
B. 함수 인수의 첫 번째는 꼭 JNIEnv여야 하며, 두 번째는 jobject여야 한다.
C. cpp로 작성하였다면 extern "C"를 선언해야한다.
D. JNIEnv* env와 jobject thiz는 JNI로부터 받은 변수 이므로 함수 인수에 꼭 사용해야 한다.
E. 포인터 env는 JNI에서 가장 중요한 포인터로, 자바 VM에 대한 인터페이스를 포함하는 구조체의 포인터이다.
F. 포인터 env에는 JNI의 환경 정보가 포함되어 있고, 자바 VM과의 상호작용 및 자바 객체와의 연계에 필요한 모든 기능을 포함한다.
G. 변수 thiz에는 포함된 클래스의 정보가들어 있다. 이 예제에서 thiz는 stringFromJNI() 함수를 포함하는 HelloJni의 클래스를 참조한다.
H. 만약 인수가 추가된다면 env와 변수 thiz 다음에 선언하여 사용한다.
I. 라이브러리 Symbol은 항상 C 형식으로 있어야한다. C++로 작성했다면 extern "C"로 함수를 감싸서 C 형식으로 변경해야한다.
J. c++ 형식일 경우env 포인터를 이용하여 멤버 함수를 호출할 때 멤버 함수에 env포인터를 전달 하지 않아야한다.
'Programming Language' 카테고리의 다른 글
[Java] TLV(Tag-Length-Value) 설명 및 해석 함수 구현 (0) | 2024.10.26 |
---|---|
[Java] JVM Garbage Collector 정리 (2) | 2024.10.13 |
[Java] 자바 Thread Dump 개인정리 (0) | 2024.10.13 |
[Java] 자바를 사용한 병렬 프로그래밍 정리와 synchronized, volatile 설명 (0) | 2024.10.13 |
JVM 메모리 누수 방지를 위한 체크사항 (5) | 2024.09.29 |