본문 바로가기
Programing/Java

Window에서 C++코드를 Java에서 JNI를 이용하여 호출하기

by Tomining 2016. 10. 11.
C++로 작성된 코드를 Java에서 호출하기 위해 사용할 수 있는 방법 중 JNI를 이용하는 방법이 있습니다.
JNI란 위키백과에서 아래와 같이 설명하고 있습니다.

자바 네이티비 인터페이스(Java Native Interface, JNI)는 자바 가상 머신(JVM)에 실행되고 있는 자바코드를 네이티브 응용 프로그램(특히 하드웨어와 운영 체제 플랫폼)들과 C, C++ 그리고 어샘블리 같은 다른 언어들로 구현된 라이브러리에 의해 호출되거나 호출할 수 있는 프로그래밍 프레임워크이다.

쉽게 이야기하자면 Java에서 C++ 같은 언어로 작성된 코드를 호출할 수 있도록 도와주는 프레임워크라고 할 수 있습니다.

IDE

IDE로는 이클립스를 사용해 보았습니다.
현재(at 2016/10/11) 기준으로 최신버전인 Neon 버전 C/C++용 IDE를 다운로드 하였습니다.

컴파일러 환경 설정

JNI 예제를 소개하기 전에 Window 환경에서 컴파일러가 필요합니다.
일반적으로 Cygwin이나 MinGW를 사용하는데, 여기서는 MinGW를 사용하였습니다.
tdm-gcc(http://tdm-gcc.tdragon.net/download) 사이트에서 OS 비트 수에 맞는 번들을 다운로드 받아 설치합니다.


64 bit Window 환경으로 위 캡처화면에 나오는 번들을 다운로드 받아 설치하였습니다.
(일반적인 윈도우 프로그램 설치 흐름처럼 설치하면 PATH 등록까지 자동으로 완료됩니다.)

JNI 예제

HelloWorld Java 클래스 작성하기

Java 프로젝트를 생성 후 HelloWorld 클래스를 아래와 같이 작성합니다.

package jni;

import java.io.File;

/**
 * @author tomining
 */
public class HelloWorld {
    static {
        String libExt = (System.getProperty("os.name").toLowerCase().indexOf("win") == -1) ? ".so" ".dll";
        File r = new File(ClassLoader.getSystemResource("libHelloWorld" + libExt).getFile());
        System.load(r.getAbsolutePath());
    }

    public native void print(String name);
}


print() 메서드가 native 타입으로 작성되어 있습니다. 이는 C++ 같은 Native 코드를 호출하겠다는 의미입니다.
static 블록에 작성된 코드는 추후에 다시 설명하겠습니다.

JNI 헤더 파일 생성하기

먼저 native print() 메서드에 대응되는 헤더 파일을 생성해야 합니다. idk에 포함된 javah를 사용하여 쉽게 헤더 파일을 생성할 수 있습니다.
jdk에 포함된 javah를 사용하여 헤더 파일을 생성할 수 있습니다.

#javah 사용방법
javah [-classpath {path}] 클래스명
javah jni.HelloWorld

위처럼 수행하면 아래와 같이 헤더 파일이 자동으로 생성됩니다.
헤더 파일을 내용을 살펴보면 하나의 메서드(java_jni_HelloWorld_print 메서드)를 구현해야 하며, 헤더 파일 최상단에 수정하지 말라는 권고 사항 주석도 보입니다.

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

#ifndef _Included_jni_HelloWorld
#define _Included_jni_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jni_HelloWorld
 * Method:    print
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_jni_HelloWorld_print
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

HelloWorld C++ 프로젝트 생성하기


Toolchain으로 MinGW를 선택합니다.(MinGW를 설치했기 때문)

JNI 헤더 파일 임포트 & 구현

javah를 이용하여 생성한 헤더 파일을 임포트하고 구현하려면 jni.h가 필요합니다.
이를 위해 이클립스 GCC C++ 컴파일러 설정에서 아래와 등록합니다.


jni_HelloWorld.cpp 구현 코드는 아래와 같습니다.

#include <iostream>

using namespace std;

#include "jni_HelloWorld.h"

JNIEXPORT void JNICALL Java_jni_HelloWorld_print(JNIEnv *_env, jobject _this, jstring _name) {
        const char* name = _env->GetStringUTFChars(_name, NULL);

        cout << name << ", Hello World!!" << endl;

        _env->ReleaseStringUTFChars(_name, name);
}

컴파일 하기

프로젝트 우클릭 > Build Targets > Create를 클릭하여 아래와 같이 설정합니다.


그럼 아래와 같이 등록됩니다.


붉은 박스의 create를 더블 클릭하면 빌드가 실행됩니다.
실행 후에는 libHelloWorld.dll 파일이 생성되었음을 확인할 수 있습니다.

dll 등록 후 JNI로 호출하기

컴파일 결과물인 dll 파일을 Java HelloWord 프로젝트 resources 아래에 복사합니다.
그리고 처음에 HelloWorld 클래스를 생성할 때 static 블록에 아래와 같이 코드를 작성하였습니다.

    static {
        String libExt = (System.getProperty("os.name").toLowerCase().indexOf("win") == -1) ? ".so" ".dll";
        File r = new File(ClassLoader.getSystemResource("libHelloWorld" + libExt).getFile());
        System.load(r.getAbsolutePath());
    }

Window에서는 dll을 사용하고 그 외에는 so 파일을 사용하도록 설정 후 System.load() 메서드를 통해서 JNI 라이브러리를 로딩합니다.
native 타입 메서드를 호출해 보겠습니다.
아래와 같이 HelloWorldMain 클래스를 만들어서 수행해 보았습니다.

public class HelloWorldMain {
    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.print(“JNI");
    }
}

실행 결과를 아래와 같이 확인할 수 있습니다.


정상적으로 호출되는 것을 확인 할 수 있습니다.
다소 복잡한 과정을 걸쳐야 하나 JNI를 통해 Native 프로그램을 실행할 수 있음을 알 수 있었습니다.


JNI 외에도 JNA도 있습니다.
이는 다음 주제로 다뤄보도록 하겠습니다.

참고