프로그래밍/Java

JNI(Java Native Interface) 이용 방법 (2/6) - 3.1~3.2

산을좋아한라쯔 2015. 7. 24. 20:21
반응형

3. 예제 (리턴 데이터가 있는 경우: Java <-- C)

JNI의 각 사용예에 구분해서 설명해 보자

   3.1 기본형 변수에 대한 반환이 있는 경우(Java <- C)
   3.2 기본형 변수를 파라미터로 보내는 경우(Java -> C)
   3.3 배열값을 반환(배열값: Java<-C)
   3.4 배열값을 파라미터로 전송(배열값: Java->C)
   3.5 자바의 인스턴스 변수에 대한 읽기/쓰기
   3.6 struct형태의 데이터를 자바로 리턴하기
   3.7 기타

 

3.1 기본형 변수에 대한 반환이 있는 경우(Java <- C)

JNI에서 복잡한 것은, Java와 C간의 데이터를 주고 받는 경우이다.

이번 챕터는 '기본형 변수'에 대한 반환이 있는 경우이다.

받는다는 것은 Java코드를 기준으로 한 것이기에, C코드 입장에서는 리턴값이 있는 함수의 경우이다.

 

 

기본형 변수라는 것은 byte, int 등 언어에서 기본으로 취급하는 primitive형태의 변수를 말한다.

 

사용되는 C 컴파일러에 따라 조금씩 다르겠지만, 표준 C 기준으로 보면 아래 표와 같이 11가지 형태의 변수가 존재하고, 이러한 C언어에서의 변수타입을 Java의 어떤 변수 형태로 받아야하는가만 결정하면 된다.

 

 번호

Storage size

C 언어

JNI Type

java type

 1

1

char

jbyte

byte

 2

1

unsigned char

jboolean

char

 3

2

short

jshort

short

 4

2

unsigned short

jchar

int

 5

4(2)

int

(jint)

int

 6

4(2)

unsigned int

(jlong)

long

 7

4

long

jint

int

 8

4

unsigned long

(jlong)

long

 9

4

float

jfloat

float

 10

8

double

jdouble

double

 11

8

long long

jlong

long

 

 

위 표에 보면 C언어에서의 변수 종류별로, 할당되는 메모리 크기, JNI에 정의된 형태(JNI Type), Java에서 어떤 변수로 받으면 되는지가 정리되어 있다.

JNI Type이라는 것은, JNI를 지원하기 위해서 C형태의 변수를 정의해 놓은 것으로, JDK폴더의 include\jni.h와 include\win32\jni_md.h에 아래와 같이 8개변수에 대해 정의되어 있다.

 

//jni.h

typedef unsigned char   jboolean;

typedef unsigned short  jchar;

typedef short           jshort;

typedef float           jfloat;

typedef double          jdouble; 

//jni_md.h : 아래 값은 운영체제에 따라 다를 것임

typedef long jint;

typedef __int64 jlong;

typedef signed char jbyte;

* 위 헤더파일의 정의에 따르면, int와 unsigned long은 정의되어 있지 않은데, 각각 jint와 jlong을 사용하면 된다. 이유는,

    - int의 경우: win32에서 int형이 4바이트 이기에, JNI형 변수에서 4바이트를 나타내는 jint를 사용

    - unsigend long의 경우: unsigend long은 4바이트이면서 양수만을 지원하므로, JNI의 부호가 있는 4바이트 변수인 jint를 사용하면 안되고(2^31보다 큰 양수를, 부호있는 4바이트 변수로 처리하면 음수가 되어 버리기에), 4바이트보다 더 큰 크기의 변수를 사용해야 한다. 해서 8바이트의 jlong 사용

표에서 Java타입으로 빨간색 처리가 되어 있는 부분은, 주의해야 할 부분이다. 모두 부호없는 변수형들이라는 공통점이 있다. 이는, Java에서는 부호없는 형태의 변수가 존재하지 않기 때문으로, 따라서, unsigned형 C 변수를 처리하기 위해서는, 해당 변수의  size보다 더 큰 변수로 처리해야한다.

 

 

 번호

Storage size

C 언어

JNI Type

java type

 2

1

unsigned char

jboolean

char

 4

2

unsigned short

jchar

int

 6

4(2)

unsigned int

(jlong)

long

 8

4

unsigned long

(jlong)

long

 

 

  • unsigend char: C에서는 char=부호있는 1바이트, unsigned char=부호없는 1바이트 인데, Java에서는 부호없는 1바이트형 변수가 없다. 해서, 부호가 있지만 2바이트인 char형을 unsigned char에 대응한 것. (char형이 2바이트이기에, 1바이트짜리 부호없는 값을 수용할 수 있다) 
  • unsigend short: 위와 같은 이유로, Java에서는 unsigned인 2바이트 변수가 없으므로, 4바이트짜리 int형을 이용해서 처리
  • unsigend int, unsigned long: 같은 이유.

이제, 실제 프로그램 예제를 통해 각각 다른 변수형태로 리턴될 때 어떻게 처리되는지 알아보자.

(실행환경은 앞장에서 살펴본 맛보기 예제와 동일 )

 

단계1. JNI선언이 포함된 자바 코딩

앞 장의 '맛보기' 예제에서 사용했던 JNITest프로젝트를 그대로 이용하자. 단, package와 Class를 다른 것으로 생성

   - package: ex

   - Class Name: Ex1_PrimitiveReturned

 

작성된 코드는 다음과 같다.

 

package ex;

 

public class Ex1_PrimitiveReturned{

        

         public native byte returnedChar(); //1

         public native char returnedUnsignedChar(); //2      

         public native short returnedShort(); //3

         public native int returnedUnsignedShort(); //4

         public native int returnedInt();   //5

         public native long returnedUnsignedInt(); //6       

         public native int returnedLong(); //7

         public native long returnedUnsignedLong(); //8

         public native float returnedFloat(); //9

         public native double returnedDouble(); //10

         public native long returnedLongLong(); //11

        

         static {

                  System.loadLibrary("JNI_Hello");

         }

        

         public static void main(String[] args) {

                  new PrimitiveReturned();

         }

        

         public PrimitiveReturned(){

                  byte b = returnedChar();//1

                  char uc = returnedUnsignedChar(); //2

                  short s = returnedShort(); //3

                  int us = returnedUnsignedShort(); //4               

                  int i_int = returnedInt(); //5

                  long l = returnedUnsignedInt(); //6        

                  int i_long = returnedLong(); //7           

                  long ul = returnedUnsignedLong(); //8               

                  float f = returnedFloat(); //9

                  double d = returnedDouble(); //10

                  long ll = returnedLongLong(); //11         

                 

                  System.out.println("b:"+b); //1

                  System.out.println("uc:"+uc); //2s

                  System.out.println("s:"+s); //3

                  System.out.println("us:"+us); //4

                  System.out.println("i_int:"+i_int); //5

                  System.out.println("l:"+l); //6

                  System.out.println("i_long:"+i_long); //7

                  System.out.println("ul:"+ul); //8

                  System.out.println("f:"+f); //9

                  System.out.println("d:"+d); //10

                  System.out.println("ll:"+ll); //11                           

         }

} 

 

단계2. javah를 이용해서 C용 헤더파일 생성

JNITest 프로젝트의 bin폴더에서, 아래와 같이 실행

 

javah ex.Ex1_PrimitiveReturned

 

 

단계3. 헤더파일에 선언된 C언어용 함수 작성

앞 장 '맛보기' 예제에서 사용했던 Visual Studio 프로젝트인 'JNi_Hello'를 그대로 이용하겠다. 기존 Hello.c외에 primitive_returned.c라는 파일을 추가 생성해서 아래와 같이 코딩. 

  - JNI_Hello프로젝트의 '소스 파일'을 선택하고 마우스 우클릭해서 '추가/새항목' 한 후, 이름이 primitive_returned.c 파일 생성

  - 위 단계2에서 생성된 헤더파일(E:\Workspace-Eclipse\JNITest\bin\ex_Ex1_PrimitiveReturned.h)에 선언된 함수들 구현

 

구현된 primitive_returned.c파일은 다음과 같다.

#include <stdio.h>

#include <jni.h>

#include "ex_Ex1_PrimitiveReturned.h"

 

JNIEXPORT jbyte JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedChar(JNIEnv *env, jobject obj){

         char c = 'a';

         return (jbyte)c;

}

 

JNIEXPORT jchar JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedUnsignedChar(JNIEnv *env, jobject obj){

         unsigned char uc = 'a';

         return uc;

}

 

JNIEXPORT jshort JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedShort(JNIEnv *env, jobject obj){

         short s = 257;

         return s;

}

 

JNIEXPORT jint JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedUnsignedShort(JNIEnv *env, jobject obj){

         unsigned short s = 30000;

        

         return s;

}

 

JNIEXPORT jint JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedInt(JNIEnv *env, jobject obj){

         int i = -70000;

         return i;

}

 

JNIEXPORT jlong JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedUnsignedInt(JNIEnv *env, jobject obj){

         unsigned int ui = 123456789;

         return ui;

}

 

 

JNIEXPORT jint JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedLong(JNIEnv *env, jobject obj){

         long l = 100000L;

         return l;

}

 

JNIEXPORT jlong JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedUnsignedLong(JNIEnv *env, jobject obj){

         //ul = power(2,31) +1

         unsigned long ul = 1;

         for (int i = 1; i <= 31; i++){

                  ul = ul * 2;

         }

         ul = ul + 1;

 

         return ul;

}

 

JNIEXPORT jfloat JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedFloat(JNIEnv *env, jobject obj){

         float f = 10.2f;

         return f;

}

 

JNIEXPORT jdouble JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedDouble(JNIEnv *env, jobject obj){

         double d = 100000.123;

         return d;

}

 

JNIEXPORT jlong JNICALL Java_ex_Ex1_1PrimitiveReturned_returnedLongLong(JNIEnv *env, jobject obj){

         //ll = power(2,33)

         long long ll = 1;

         for (int i = 1; i <= 33; i++){

                  ll = ll * 2;

         }

 

         return ll;

}

 

 

소스작성이 완료되면 F6을 눌러 빌드한다.

앞 장의 '맛보기' 예제에서 이미 세팅한 바와 같이, 아래와 같은 세팅으로 해서 개발생산성이 높아진다.

  • javah에 의해 생성되는 헤더파일을, C 코딩되는 폴더로 복붙(복사-붙여넣기) 하지 않고, Visual Studio의 세팅에서 '포함 디렉토리'에 javah에 의해 생성되는 헤더파일이 위치한 폴더를 지정함으로써, 자바코드 수정에 의해 헤더파일이 새로 생성되더라도 그 때마다 복붙하지 않고 그냥 DLL로 빌드가 가능하게 된다.
  • 빌드에 의해서 DLL이 생성되는 폴더를, 자바 프로젝트 폴더로 지정함으로써, 빌드할 때 마다 새로 생성되는 DLL을 일일이 복붙하는 번거로움이 사라진다.


단계 4. C언어로 제작된 라이브러리파일 이용하는 자바코딩 완성

단계1에서 이미 완성되었고, Visual Studio의 빌드에 의해 생성된 JNI_Hello.dll도 자바 프로젝트 폴더(E:\Workspace-Eclipse\JNITest)에 위치되어 있을 것이다. Eclipse에서 Ctrl-F11을 눌러 Ex1_PrimitiveReturned클래스를 실행해보면, 아래와 같은 결과가 나올 것이다.

 

b:97
uc:a
s:257
us:30000
i_int:-70000
l:123456789
i_long:100000
ul:2147483649
f:10.2
d:100000.123
ll:8589934592

 

 

3.2 기본형 변수를 파라미터로 보내는 경우(Java -> C)

이번 챕터는 앞 챕터와 반대로, 자바코드에서 데이터를 C 코드로 보내는 경우이다.

 

자바에서는 8개의 기본 변수형이 있다. 이 기본 변수형에 대해서 C에서 잘 받아내면 된다. 이 때 핵심은, 자바에서 보낸 변수를 다 커버할 수 있게 최소한 같거나 더 큰 메모리 공간을 차지하는 C 변수로 받아내는 것.

표로 정리해보면 다음과 같다. 

 

 번호

 Java 변수

Storage size

JNI Type

C 변수

 1

 boolean

2

jboolean

byte

 2

 byte

1

jbyte

char

 3

 short

2

jshort

short

 4

 char

2

jchar

int

 5

 int

4

jint

int

 6

 long

8

jlong

long

 7

 float

4

jfloat

int

 8

 double

8

jdouble

long

 

 

위 8가지 경우에 대해서 프로그램을 짜보자. (앞의 맛보기 예제와 동일한 환경 사용)

 

단계1. JNI선언이 포함된 자바 코딩

앞 장의 '맛보기' 예제에서 사용했던 JNITest프로젝트를 그대로 이용하자. 단, 새로운 Class를 생성

   - package: ex

   - Class Name: Ex2_PrimitiveParameter

 

작성된 코드는 다음과 같다.

package ex;

 

public class Ex2_PrimitiveParameter {

         public native boolean xor(boolean b1, boolean b2);

         public native int add(byte b1, byte b2);

         public native int add(short s1, short s2);

         public native int add(char c1, char c2);

         public native long add(int i1, int i2);

         public native long add(long i1, long i2);

         public native double add(float f1, float f2);

         public native double add(double d1, double d2);

         static {

                  System.loadLibrary("JNI_Hello");

         }

 

         public static void main(String[] args) {

                  new Ex2_PrimitiveParameter();

         }

 

         public Ex2_PrimitiveParameter() {

                  boolean bool = xor(true, false);

 

                  int i1 = add((byte) 1, (byte) 2); // 3

                  int i2 = add((short) 15000, (short) 15000); // 30000

                  int i3 = add('a', 'b'); // 195=97+98

 

                  long l1 = add(100000, 100000); // 20000

                  long l2 = add(2000000L, 2000000L); // 4000000

 

                  double d1 = add(1.5f, 1.5f); // 3.0

                  double d2 = add(100000.5d, 100000.5d); // 200001.0

 

                  System.out.println("bool:" + bool);

                  System.out.println("i1:" + i1);

                  System.out.println("i2:" + i2);

                  System.out.println("i3:" + i3);

                  System.out.println("l1:" + l1);

                  System.out.println("l2:" + l2);

                  System.out.println("d1:" + d1);

                  System.out.println("d2:" + d2);

         }

}

단계2. javah를 이용해서 C용 헤더파일 생성

 

JNITest 프로젝트의 bin폴더에서, 아래와 같이 실행 --> ex_Ex2_PrimitiveParameter.h 파일 생성될 것임

 

 

javah ex.Ex2_PrimitiveParameter

 

 

단계3. 헤더파일에 선언된 C언어용 함수 작성

앞 장 '맛보기' 예제에서 사용했던 Visual Studio 프로젝트인 'JNi_Hello'를 그대로 이용하겠다. 새롭게 primitive_parameter.c 라는 파일을 추가 생성해서 아래와 같이 코딩. 

  - JNI_Hello프로젝트의 '소스 파일'을 선택하고 마우스 우클릭해서 '추가/새항목' 한 후, 이름이 primitive_parameter.c 파일 생성

  - 위 단계2에서 생성된 헤더파일(E:\Workspace-Eclipse\JNITest\bin\ex_Ex2_PrimitiveParameter.h)에 선언된 함수들 구현

 

구현된 primitive_parameter.c파일은 다음과 같다.

#include <stdio.h>

#include <jni.h>

#include "ex_Ex2_PrimitiveParameter.h"

 

JNIEXPORT jboolean JNICALL Java_ex_Ex2_1PrimitiveParameter_xor(JNIEnv *env, jobject obj, jboolean b1, jboolean b2){

         return (b1 ^ b2);

}

 

JNIEXPORT jint JNICALL Java_ex_Ex2_1PrimitiveParameter_add__BB(JNIEnv *env, jobject obj, jbyte b1, jbyte b2){

         return (jint)(b1+b2);

}

 

JNIEXPORT jint JNICALL Java_ex_Ex2_1PrimitiveParameter_add__SS(JNIEnv *env, jobject obj, jshort s1, jshort s2){

         return (jint)(s1 + s2);

}

 

JNIEXPORT jint JNICALL Java_ex_Ex2_1PrimitiveParameter_add__CC(JNIEnv *env, jobject obj, jchar c1, jchar c2){

         return (jint)(c1 + c2);

}

 

JNIEXPORT jlong JNICALL Java_ex_Ex2_1PrimitiveParameter_add__II(JNIEnv *env, jobject obj, jint i1, jint i2){

         return (jlong)(i1 + i2);

}

 

JNIEXPORT jlong JNICALL Java_ex_Ex2_1PrimitiveParameter_add__JJ(JNIEnv *env, jobject obj, jlong l1, jlong l2){

         return (jlong)(l1 + l2);

}

 

 

JNIEXPORT jdouble JNICALL Java_ex_Ex2_1PrimitiveParameter_add__FF(JNIEnv *env, jobject obj, jfloat f1, jfloat f2){

         return (jdouble)(f1+f2);

}

 

JNIEXPORT jdouble JNICALL Java_ex_Ex2_1PrimitiveParameter_add__DD(JNIEnv *env, jobject obj, jdouble d1, jdouble d2){

         return (jdouble)(d1+d2);

}

 

 

 

소스작성이 완료되면 F6을 눌러 빌드한다. --> Eclipse프로젝트 폴더 밑에 JNI_Hello.dll 파일이 새롭게 생성될 것임

 

단계 4. C언어로 제작된 라이브러리파일 이용하는 자바코딩 완성

단계1에서 이미 완성되었고, Eclipse에서 Ctrl-F11을 눌러 Ex2_PrimitiveParameter클래스를 실행해보면, 아래와 같은 결과가 나올 것이다.

 

bool:true
i1:3
i2:30000
i3:195
l1:200000
l2:4000000
d1:3.0
d2:200001.0

 

 

-끝-



 

 

 

반응형