'language/JAVA'에 해당되는 글 4건

Decompiler jad

language/JAVA 2010. 7. 22. 16:23

C와 같은 언어는 소스파일을 컴파일하면 해당 시스템에 적합한 바이너리 코드를 생성합니다.
바이너리 코드를 역컴파일 하는 것이 불가능 한것은 아니나, 이것은 지루하고 복잡한 작업입니다.
흔히들 이와같은 작업을 역공학(리버스 엔지니어링)이라 하며, 소프트웨어 보안과 라이센스 정책에 크게 위협이 될수 있는 분야이기도 합니다.

Java의 경우 C와 같은 언어와 달리 바이트 형태의 class파일을 생성하는데 'Write Once, Run Anywhere"라는 Java의 패러다임에서 알수 있듯이, JavaVM이 존재하는 모든 시스템에서 실행될수 있는 시스템 독립적인 코드입니다.
따라서, 바이트 코드와 같은 경우는 바이너리 코드와 달리 손쉽게 역컴파일이 가능합니다.

jad는 바로 Java의 class파일을 디컴파일 해주는 프로그램입니다.
즉, java소스 파일은 없고 class파일만 존재하고 있을때, jad를 이용해 class파일을 java파일로 변환할 수 있습니다.


jad를 Eclipse IDE에 통합하여 편리하게 사용할수 있게 도와주는 plugin이 있습니다.
(Eclipse 3.4에 jadclipse3.3 설치가 가능합니다.)



1. 설치하기

아래 그림과 같이 다운로드 받은 jad파일을 Eclipse디렉토리에 복사합니다.


JadClipse파일(net.sf.jadclipse_3.3.0.jar)을 Eclipse plugins디렉토리에 복사합니다.


Eclipse를 재식하고 Window > Preference > Java > JadClipse에서 아래 그림과 같이 Path to decompiler을 입력합니다.


한글이 깨지는 것은 방지하기 위해 아래 그림과 같이 마지막 항목을 체크합니다.




2. class파일 디컴파일

디컴파일하고자 하는 class파일을 더블클릭하면, 아래 그림과 같이 디컴파일된 java파일이 나타납니다.




3. 난독처리

Java 프로그램은 컴파일된 코드만 배포한다고 하여도(바이트 코드) 이로부터 소스코드를 쉽게 만들어 낼 수 있으므로 소프트웨어 보호라는 측면에서 많은 문제점을 가지고 있습니다. 이와 같은 위험으로부터 소스코드를 보호하기 위해 소스코드를 난독화 할수 있습니다. 난독화는 는프로그램 코드를 변환하는 방법의 일종으로, 프로그램에 사용된 변수명을 아무 의미없는 이름으로 변환하는 등 코드를 지저분하게 하여 사람이 읽기 어렵게 만들어주는 기술입니다.

'language > JAVA' 카테고리의 다른 글

& 0xFF를 사용한 unsigned byte 및 int 비교 방법  (0) 2010.07.21
JAVA Native Method (JNI)  (1) 2009.07.01
JNI (Java Native Interface) 란 ?  (0) 2009.06.30
블로그 이미지

란마12

,

11111111 11111111 11111111 10001110 (-114)

00000000 00000000 00000000 11111111 (0xFF)

------------------------------------- 20150831 추가



public class T

{
    public static void main(String[] args) throws Exception

    {
        byte[] b = { (byte)0x34, (byte)0x32, (byte)0x31, (byte)0x8E, (byte)0xF0 };
        System.out.println( "i:\t b[i]\t 0xff&b[i]\t toHex(0xff&b[i])" );
        for( int i=0; i<=4; i++ )

        {

            System.out.println(i + ":\t" + b[i] + "\t" + (0xFF&b[i]) + "\t" + Integer.toHexString(0xFF&b[i]));
        }
    }
}
========================================
C:\>java T
i: b[i] 0xff&b[i] toHex(0xff&b[i])
0: 52 52 34
1: 50 50 32
2: 49 49 31
3: -114 142 8e
4: -16 240 f0
========================================

1. byte & int & bit
1 byte = 8bit : -128 ~ 127, -2^(8-1) ~ 2^(8-1)-1 : 2^8개
1 int = 4 byte = 32bit : -2,147,483,648 ~ 2,147,483,647, -2^(32-1) ~ 2^(32-1)-1 : 2^32개

byte: 00000000
int: 00000000 00000000 00000000 00000000

0x34 = 00000000 00000000 00000000 00110100
(byte)0x34 = 00110100

0x32 = 00000000 00000000 00000000 00110010
(byte)0x32 = 00110010
...
0x8E = 00000000 00000000 00000000 10001110
(byte)0x8E = 10001110 (NOTE: sign bit is 1)

0xF0 = 00000000 00000000 00000000 11110000
(byte)0xF0 = 11110000 (NOTE: sign bit is 1)

2. (byte)0xF0 = 11110000 인데, 가장 좌측의 sign bit가 1이므로, 이는 음수를 뜻합니다.
byte형에 적재된 11110000의 "2의보수"는 00010000 입니다.
  11110000
+ 00010000
------------
  1 00000000  (마치, 10진수 3의 "10의보수"는 7 이듯...)

00010000 는 2^4 = 16 이므로, 결국 (byte)0xF0 = 11110000 는 -16으로 나타나게 됩니다.

3. 0xFF & b[i]
ex) 0xFF & (byte)0xF0

0xFF = 00000000 00000000 00000000 11111111 { = 32-bit = Integer }
                                           & 11110000 { = (byte)0xF0 = -16 }
        ------------------------------------
         00000000 00000000 00000000 11110000 = 0xF0 = 240 { = 32-bit = Integer }


NOTE: byte로 타입 캐스팅을 해서 출력하면 이상한 값이 들어가는 것처럼 보이는 이유는 byte도 int의 경우처럼 맨 앞의 비트를 sign bit 으로 사용하기 때문입니다. 8bit로 표현되는 0xFF 이하의 수치(0~255)에 한해서 (byte)형으로 casting하게 되면 하여 집어넣을 경우 & 0xFF 연산을 해 주게되면 32-bit 크기(int)로 unsigned byte의 값을 얻을 수 있습니다. (참고: 자바에는 primitive type 으로 unsigned 형이 없습니다.)

 

=========================================================================================================

 

자바에서 "int"형은 4byte 로 표현됩니다. 1byte는 8bit 이니 int형은 총 32 bit 로 표현됩니다.

예) int형 1의 bit 표현은 결국 다음과 같습니다.
     00000000 00000000 00000000 00000001

32bit를 이용하여 표현하니 총 표현할 수 있는 수치의 개수는 2의 32승, 즉 4,294,967,296 개가 될 것입니다.

그러나, 가장 좌측 bit 는 음수표식을 위해 사용되니(sign bit), 0을 포함하여 31bit로 표현될 수 있는 수의 개수는 2의 (32-1)승개가 됩니다. 2의 31승은 21,47,483,648 이니, 양수로 가장 큰 수는 21,47,483,648-1 입니다. 이 수는 4byte 형에서 다음과 같이 표현됩니다.
01111111 11111111 11111111 11111111 = 21,47,483,647

음수는 "2의보수"로 표현되는데,  예를 들면 int형(4byte,32bit) -1은 "2의보수"로 표현되어,

11111111 11111111 11111111 111111111 로 저장(?) 됩니다.

이는 마치 10진법 3의 10의 보수는 7, 즉
  3
+ 7
----
 10

이듯이,

  00000000 00000000 00000000 000000001
+ 11111111 11111111 11111111 111111111
--------------------------------------
1 00000000 00000000 00000000 000000000

이므로, 00000000 00000000 00000000 000000001 의 int형 "2의 보수"는
           11111111 11111111 11111111 111111111 인 것이지요.

"2의보수"를 찾는 알고리즘은 통상 NOT연산 후 +1을 취하면 됩니다. 즉,
|-1| 은 1입니다.  1은 이진수로   00000000 00000000 00000000 000000001 입니다. 
   이것에 대해 먼저 NOT연산후   11111111 11111111 11111111 111111110,
                         +1 을 취하면   11111111 11111111 11111111 111111111 입니다.

|-21,47,483,647| 은 01111111 11111111 11111111 11111111 인데,
NOT연산을 취하면  10000000 00000000 00000000 00000000 이고,
+1 을 취하면          10000000 00000000 00000000 00000001 입니다.

|-21,47,483,648| 은 10000000 00000000 00000000 00000000 인데,
NOT연산을 취하면  01111111 11111111 11111111 11111111 이고,
+1 을 취하면          10000000 00000000 00000000 00000000 입니다.

이 처럼 가장 좌측 bit 가 1 인 상태에서 2의 보수로 표현될 수 있는 가장 작은 수는 -21,47,483,648 이 됩니다.

long 형은 8 byte, 64 bit로 표현된다는 것 외엔 동일합니다.
결국, long형의 가장 큰 수는 2의 (64-1)승 -1 인, 9,223,372,036,854,775,807 까지
표현 됩니다. 음수로는 -{2의 (64-1)승} 인 -9,223,372,036,854,775,808 까지 표현되겠지요.

추가로 아래 글들을 참고하세요.

-128>>>1 = 2147483584 ??? 
http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=javatip&c=r_p&n=998318237

Re: 1>>> 32 가 1인 이유는 ???? 
http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=QandA&c=r_p&n=994003831

[자바의 숫자표현] 어디까지 가능할까 !!! 
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=920434490
 

 


[출처] 이원영, 192 Re: 자바의 자료형
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=1018155018

'language > JAVA' 카테고리의 다른 글

Decompiler jad  (0) 2010.07.22
JAVA Native Method (JNI)  (1) 2009.07.01
JNI (Java Native Interface) 란 ?  (0) 2009.06.30
블로그 이미지

란마12

,
보통 서로 다른 장, 단점을 갖고 있는 JAVA와 C++, 이 두 언어를 결합할 수만 있으면 큰 시너지를 발휘할 수 있다고 생각한다. 뭐, 어느 정도의 범위에서는 그렇긴 하지만...그러나 왠만하면, 정말 어쩔 수 없는 경우를 제외하고는 이 JNI 기술은 활용하지 않는 것이 최상이라고 난 생각한다. -_-;;

이제 마소에서 두 언어를 결합하는 방법을 전격 공개한다. 먼저 자바의 JNI를 이용해 C로 작성된 Win32 DLL을 호출하는 방법을 알아볼 텐데, 자바 코드에서 만든 데이터를 C 코드로 전달하거나 혹은 C 코드에서 생성된 데이터를 자바로 전달하는 방법, 한글 문제를 해결하는 방법 등도 포함돼 있다. 더불어 C/C++에서 자바 VM을 이용하는 방법도 함께 소개한다.

자바 네이티브 메쏘드(Java Native method, 이하 JNI)는 다른 언어로 작성된 코드를 자바에서 호출하도록 만들어진 규약이다. 현재는 C/C++에 대한 호출만을 정확하게 지원한다. 어떻게 보면 JNI는 자바가 만들어진 철학과 정반대되는 것이다. 네이티브 함수가 플랫폼에 종속적이기 때문이다. 자바의 가장 큰 장점 중의 하나로 내세우는 ‘플랫폼 독립적’이라는 부분을 해치는 JNI 규약이 만들어진 것은, 자바의 현실적인 문제들 때문이다.

먼저 속도 문제를 들 수 있다. JIT 컴파일러와 같은 기술로 자바 프로그램의 속도가 예전보다는 많이 빨라지고 있지만, 자바는 원칙적으로 바이트 코드를 인터프리트해 수행되기 때문에 아무리 자바 기술이 발전한다 해도, 네이티브 코드(플랫폼에 종속적인 기계어 코드)의 속도를 따라갈 수는 없다. 사실 자바의 처리 속도 문제는 자바가 안고 있는 가장 큰 단점이기도 하다. 따라서 아주 빠른 처리가 요구되는 대량의 계산 작업이나 실시간(real-time) 처리에는 자바를 이용하기 힘들다. - 이제 속도 문제는 신경을 안 써도 된다. 정말 속도가 중요시되는 전문 과학 분야를 제외하고는 JAVA의 처리 속도로도 거의 모든 문제를 해결할 수 있다.

JNI가 필요한 것은 단지 속도 문제만이 아니다. 기존에 작성된 프로그램이나 기존의 시스템(legacy)과의 연계 문제가 있으며, 더욱 중요한 이유는 플랫폼에 따라 다르게 제공되는 서비스를 이용할 수 있다는 점이다. 자바의 클래스 라이브러리는 방대하고 다양한 서비스를 제공하지만, 특정 플랫폼에서 제공하는 고유의 서비스의 기능을 모두 포함할 수는 없다. 특히, 특수한 목적으로 제작된 하드웨어를 자바에서 제어해야 할 필요가 있다고 한다면, 자바만으로 해결하기는 힘들다.

이 때문에 JNI가 필요하다. 지나치게 JNI에 의존하는 것은 자바가 갖고 있는 많은 장점들을 해치는 결과를 초래할 수 있지만, 특정 부분에 적절하게 사용한다면 JNI는 자바의 장점과 C/C++의 장점을 골고루 이용할 수 있는 길을 제공한다.

여기서 한 가지 궁금증이 생길 것이다. 그렇다면 과연 JNI를 어떤 부분에 사용해야 앞서 언급한 효과를 볼 수 있을까? JNI가 사용되면 좋은 곳을 정리해 보면 다음과 같다.

1. 속도 문제가 있는 계산 루틴
2. 자바에서 하드웨어 제어
3. 자바에서 지원되지 않은 특정 운영체제 서비스
4. 기존의 프로그램에서 자바가 제공하는 서비스를 이용

자바가 엔터프라이즈 솔루션에 많이 사용되는 것은 자바가 갖고 있는 많은 장점들이 대규모 시스템 구축에 적합하다는 강점을 인정받고 있는 셈이다. JNI는 그 자체가 중요한 솔루션이 될 수는 없지만, 기존의 코드나 기존의 시스템 혹은 이질적인 다른 시스템과의 인터페이스에 사용될 수 있는, 자바가 가진 좋은 기능 중의 하나이다.


★ 간단한 JNI 프로그래밍, HelloWorld

자바 프로그래밍을 한 번이라도 해 본 사람이라면, 다음과 같은 HelloWorld 프로그램을 작성해 보았을 것이다.

public class HelloWorld {

       public static void main(String arg[]) {

             System.out.println("Hello, World");

       }

}


이제 이 프로그램을 JNI 버전으로 다시 만들어 보는 것부터 시작해 보려 한다. 가장 간단한 JNI 호출 방법과 컴파일 방법을 익히기 위해서이다

1단계 : 네이티브 메쏘드를 가진 클래스 작성

두 개의 자바 프로그램을 작성하고, 컴파일해 NativeHello.class, HelloWorld.class 파일을 생성한다.

C:\jni>javac NativeHello.java
C:\jni>javac HelloWorld.java

NativeHello.java 프로그램은 C/C++로 작성된 Win32 네이티브 코드를 호출하기 위한 네이티브 메쏘드를 선언하는 부분이다.

public native void SayHelloWorld();

SayHelloWorld는 자바에서 호출하는 메쏘드 이름이 되는데, ‘native’ 키워드를 이용해 선언하며, 메쏘드의 몸체(body)는 없다. 몸체는 C/C++ 코드로 컴파일된 DLL에 만들어진다. HelloWorld.Java는 NativeHello 객체를 만들어 SayHello World() 메쏘드를 호출하는 간단한 형태의 프로그램이다

2단계 : C 헤더 파일 생성

javah 명령을 써서 다음과 같이 헤더 파일을 생성한다. javah 명령은 JDK에 포함돼 있는 명령으로 .class 파일을 읽어, 여기에 들어있는 네이티브 메쏘드를 판독하고, 이를 C 헤더 파일로 생성해 낸다.

C:\jni>javah -jni NativeHello

이 명령을 쓰려면 물론 JDK의 환경변수(특히 CLASSPATH 등)가 세팅돼 있어야 한다. 이렇게 하면 NativeHello.h라는 C 언어 헤더 파일이 생성된다. 그리고 옵션으로 jni를 주어야 한다. 물론 jni 옵션을 주지 않아도 헤더 파일이 생성되지만, JNI 스타일의 헤더가 아닐 것이기 때문이다. JDK 1.0 스타일의 헤더 파일을 생성하고자 한다면 -stubs 옵션을 준다.

생성된 헤더 파일인 NativeHello.h를 살펴보자. 이 파일은 다음과 같은 함수 선언을 포함하고 있다.

JNIEXPORT void JNICALL Java_NativeHello_SayHelloWorld(JNIEnv *, jobject);

이 파일의 첫 부분에 ‘DO NOT EDIT THIS FILE’이란 말이 있다. 실제로 이 헤더 파일은 수정하지 말아야 한다. 이 Java_Native Hello_SayHelloWorld 함수 선언은 JNIEnv struct 포인터와 jobject 등 두 개의 인수를 갖고 있다. 이 두 개의 인수는 모든 JNI 호출에 추가된다.

첫번째 인수인 JNIEnv 포인터는 자바 가상머신(Virtual Machine, 이하 VM) 영역에 대한 포
인터이고, 두번째 jobject는 C++의 this에 해당한다. 즉, 클래스를 참조하는데 사용된다.

생성된 헤더 파일에서 ‘Signature:’ 부분도 눈여겨볼 필요가 있다. 현재 ‘V’라는 글자가 있는데 이것은 리턴 타입이 void란 의미이다.

javah 사용과 관련해 지정할 수 있는 옵션들은 다음과 같은 것들이 있다.

-o : 출력될 파일의 이름을 지정한다.
-d : 출력 디렉토리를 지정한다.
-jni : JNI 스타일의 헤더를 생성하도록 한다.
-td : 작업용 임시 디렉토리를 지정한다.
-stubs : stub 파일을 생성한다(JDK 1.0 스타일).
-trace : stub 파일에 추적 정보를 덧붙인다.
-verbose : 작업에 대한 자세한 정보를 출력한다.
-classpath : 클래스 경로를 지정한다. 디렉토리는 ‘;’으로 분리한다.
-version : 빌드 버전을 출력한다.

3단계 : 네이티브 메쏘드의 몸체가 될 C 코드 작성

1. 비주얼 C++에서 Win32 Dynamic-Link Library 타입으로 Native Hello라는 이름의 프로젝트를 생성한다.
2. 위저드의 1/1 단계에서 ‘A Simple Projcet’를 선택한다.
3. javah로 생성된 헤더 파일(NativeHello.h)을 프로젝트 디렉토리로 옮기고, NativeHello
.cpp 파일을 <리스트 4>와 같이 수정한다. 여기서는 헤더에서 생성된 함수의 이름을 그대로
구현하는 것이다.
4. Prject / Settings 메뉴를 선택한 뒤, <화면 1>과 같이 다음 인클루드 디렉토리를 추가
한다. 이 jni.h 파일은 네이티브 메쏘드를 위해 JDK가 제공하는 헤더 파일이다.

C:\jdk1.2.2\include,C:\jdk1.2.2\include\win32

화면 1

화면 1


4단계 : 빌드하여 NativeHello.dll 파일을 생성하고 프로그램을 수행한다.

NativeHello.dll 파일을 NativeHello.class 파일이 있는 곳에 복사한다. 이렇게 하면 준비가 다 된 것이다. 이제 HelloWorld Java를 다음과 같이 수행한다.

C:\jni>java HelloWorld

<화면 2>는 이것의 수행된 결과이다. 간단한 메시지 박스가 나타난다. HelloWorld.java에서 NativeHello 클래스의 SayHelloWorld() 네이티브 메쏘드를 호출하는데, 이 네이티브 메쏘드의 몸체가 바로 NativeHello.cpp 파일의 Java_NativeHello_ SayHelloWorld() 함수가 되는 것이다.

이 함수는 간단히 MessageBox()란 Win32 API를 호출하는 내용으로 돼있다. DLL은 사실상 Win32의 실행 파일과 마찬가지로 모든 Win32 API를 사용할 수 있기 때문에 이론적으로는 JNI에서 모든 Win32 API를 이용할 수 있다.

화면 2

화면 2



★ 자바의 데이터 타입, C/C++의 데이터 타입

우리는 앞에서 JNI 프로그래밍의 전체적인 과정을 훑어보기 위해 가장 간단한 형태의 JNI 프로그램을 작성해 보았다. 사실 독자들이 가장 궁금해 할 내용은 아마도 자바 코드에서 만들어진 데이터를 C 코드로 전달하거나, 혹은 C 코드에서 생성된 데이터를 자바로 전달해주는 방법일 것이다. 일단 다음과 같이 String 타입의 객체를 인수로 전달하는 경우를 살펴보자.

public class NativeClass {

       public native void setString(String s);

       public native void setInt(int i);

       public native void setData(float f, double d);

       ...

}


이 코드를 컴파일한 후 javah로 C 헤더 파일을 생성하면 헤더에는 다음과 같은 내용의 함수
선언이 생성된다.

JNIEXPORT void JNICALL Java_NativeClass_setString(JNIEnv *, jobject, jstring);
JNIEXPORT void JNICALL Java_NativeClass_setInt(JNIEnv *, jobject, jint);
JNIEXPORT void JNICALL Java_NativeClass_setData(JNIEnv *, jobject, jfloat, jdouble);

생성된 함수 선언을 보면, 모든 함수의 인수가 두 개 이상인 것을 확인할 수 있다. 처음 두
개의 인수(JNIEnv *, jobject)는 모든 JNI 함수에 생긴다. 세번째 인수부터가 우리가 전달
하고자 하는 인수이다.

JNI에는 C 언어와의 데이터 타입의 차이를 극복하기 위해 헤더에 ‘j’로 시작하는 새로운 타입을 재정의했다. 이 데이터 타입의 크기가 C와 같을 수도 있지만, 경우에 따라서는 다를 수도 있다.

예를 들어 Win32 환경의 경우 jint는 int와 같은 32비트 정수이다. 하지만 jchar은 char 타입과 다르다. 자바의 char은 16비트 정수이며, C의 char 타입은 8비트 정수이다. 또한 일반적으로 비주얼 C++에서 사용하는 BOOL은 int 타입을 재정의한 것이다. 따라서 자바의 boolean에 해당하는 jboolean 타입과는 크기가 다르다. 참고로 자바에서 true, false에 해당하는 부울 값은 에 다음과 같이 정의돼 있다.

#define JNI_FALSE 0
#define JNI_TRUE 1

이런 차이점에 유의해서 코드를 작성해야 한다(<표 1>).

<표 1> JNI에서 사용하는 데이터 타입과 범위
Java
C(JNI)
비트수
C/C++(Win32)
boolean
jboolean
8 unsigned
unsigned char
byte
jbyte
8
unsigned char
char
jchar
16 unsigned
unsigned short
short
jshort
16
short
int
jint
32
int
long
jlong
64
_int64 (비주얼C++)
float
jfloat
32
float
double
jdouble
64
double
void
void
-
void


★ UTF-8 문자열 다루기

자바 코드와 C 코드 간에는 jstring 타입으로 문자열을 주고받는데, jstring은 객체이기 때문에 C에서 ‘char *’와 같은 형태로 바로 이용할 수는 없다. 때문에 이를 위해 몇 가지 함수들이 제공된다.

우리는 두 가지 문제만 해결할 수 있으면 충분하다. 하나는 자바 코드에서 넘어온 jstring 타입을 C의 ‘char *’로 바꾸어 처리하는 방법과 C 코드에서 생성한 문자열을 자바로 리턴하는 방법이다. 먼저 자바에서 생성된 String 타입(jstring)의 객체를 C 코드에서 다루는 방법을 살펴보자.

자바에서는 아스키(ASCII) 문자열 이외에 UTF-8, UTF-16과 같은 문자열 포맷이 지원된다. 이것은 7비트로만 나타낼 수 없는 문자들(한글, 한자 등), 2바이트 이상으로 표현되는 문자들을 지원하기 위한 포맷이다. UTF-8(Universal Character Set Transforma tion Format, 8bit)은 8비트 즉, 바이트의 나열로 표시된다.

영문 아스키 문자의 경우는 일반적인 C의 문자열과 다르지 않다. 0x0001∼0x007F 범위의 문자는 1바이트를 사용하며, 0x0080~0x07FF 범위의 문자를 표현할 때는 2바이트가 사용된다.

첫번째 바이트는 이진수 110으로 시작하며, 두번째 바이트는 10으로 시작한다. 따라서 연속
된 두 바이트를 다음과 같이 조합해 하나의 유니코드 문자를 만들게 된다.

((첫번째_바이트 & 0x1f)<<6) + (두번째_바이트 & 0x3f)

이와 비슷하게 0x0800∼0xFFFF 범위 사이의 문자 코드는 3개 바이트를 사용해 표현하며, 다음과 같이 연산해 하나의 유니코드 문자를 만들어 내게 된다. <표 2>는 UTF-8이 인코딩되는
방법을 보여주고 있다.

((첫번째_바이트 & 0xf)<<12)+((두번째_바이트 & 0x3f) <<6) + (세번째_바이트 & 0x3f)

<표 2> UTF-8 포맷의 문자열 인코딩 방식
유니코드(16진수)
인코딩된 바이트 수
UTF-8로 인코딩된 비트(2진수)
0x0001~0x007F
1
0xxxxxxx
0x0080~0x07FF
2
110xxxxx 10xxxxxx
0x0000
-
-
0x0800~0xFFFF
3
1110xxxx 10xxxxxx 10xxxxxx

자바 VM이 사용하는 UTF-8 포맷에 대해 주의할 점이 있다. 첫번째는 널(null, 0x0000) 문자에 대한 표현이다. 0x0000 문자는 1바이트가 아닌 두 바이트로 표시된다. 즉, 0x0000 문자를 2진수 비트로 표시해 보면,

11000000 10000000

의 두 바이트로 나열된다. 따라서 C에서처럼 ‘\0(2진수 000 00000)’으로 끝나는 문자열이 아니라는 점을 알 수 있다. 만일 여러분이 jstring을 strcpy()와 같은 함수에 직접 사용한다면, ‘\0’ 문자가 없기 때문에 분명 프로그램이 죽을 것이다. 반대로 C의 문자열을 그대로 jstring으로 넘긴다면 자바 VM이 죽는다. jstring을 잘못 사용하는 예를 들어보자.

JNIEXPORT void JNICALL Java_WinDialogBox_setTextField(JNIEnv *env, jobject obj, jstring str) {
    char strBuff[128];
    strcpy(strBuff, str) ; // C 모듈이 crash한다.
}

JNIEXPORT jstring JNICALL Java_WinDialogBox_getTextField(JNIEnv *env, jobject obj) {
    char strBuff[128] = "Hello Java VM";
    return strBuff; // VM이 crash한다.
}

JNI는 jstring과 C의 문자열 처리를 위해 GetStringUTFChars (), ReleaseStringUTFChars() 함수를 제공한다.

GetString UTFChars() 함수는 UTF-8 포맷의 문자열을 널로 끝나는 C 스타일의 문자열로 전환해 포인터를 리턴하는 함수이다.

반대로 Relea seStringUTFChars() 함수는 원상태로 복구하는 함수이다. 덧붙여 C 스타일의 문자열을 UTF-8 문자열로 만들려면, New StringUTF() 함수를 사용한다. 다음은 C 문자열과 자바의 jstring 사이의 문제를 해결한 C++ 코드이다.

JNIEXPORT void JNICALL Java_WinDialogBox_setTextField(JNIEnv *env, jobject obj, jstring str) {
    char strBuff[128];
    const char *sz = env->GetStringUTFChars(str, 0) ;
    strcpy(strBuff, sz) ;
    env->ReleaseStringUTFChars(str, sz) ;
}

JNIEXPORT jstring JNICALL Java_WinDialogBox_getTextField(JNIEnv *env, jobject obj) {
    char strBuff[128] = "Hello Java VM";
    return env->NewStringUTF(strBuff) ;
}

똑같은 내용이지만 C에서 호출한다면, 즉 확장자가 .c인 프로그램에서는 JNI 함수를 호출하는 방법이 약간 달라진다. 다음은 같은 내용의 C 코드이다.

JNIEXPORT void JNICALL Java_WinDialogBox_setTextField(JNIEnv *env, jobject obj, jstring str) {
    char strBuff[128];
    const char *sz = (*env)->GetStringUTFChars(env, str, 0) ;
    strcpy(strBuff, sz) ;
    (*env)->ReleaseStringUTFChars(env, str, sz) ;
}

JNIEXPORT jstring JNICALL Java_WinDialogBox_getTextField(JNIEnv *env, jobject obj) {
    char strBuff[128] = "Hello Java VM";
    return (*env)->NewStringUTF(env, strBuff) ;
}

이처럼, 같은 내용이라도 JNI 함수를 C와 C++에서 호출하는 방법에 차이가 있음을 알아두자.

(*env)->FindClass(env, “java/lang/String”); // C의 경우
env->FindClass(“java/lang/String”); // C++의 경우

이 내용들을 가지고 간단한 Win32 에디터를 만들어 보자.

먼저 <리스트 5>와 <리스트 6>에 있는 두 개의 자바 소스 파일을 만들고 각각을 컴파일한다. <리스트 5>의 WinDialgoBox.java 프로그램은 세 개의 네이티브 메쏘드를 선언하고 있는 클래스이며, 실제 이 네이티브 메쏘드의 몸체는 WinDialogBox.DLL 안에 만들어질 것이다. 그리
고 앞과 마찬가지로 자바 헤더 파일을 만든다.

C:\jni>javah -jni WinDialogBox

이렇게 하면 WinDialogBox.h가 생성된다. 이 파일은 세 개의 네이티브 함수에 대한 선언을 포함하고 있다. 그리고 비주얼 C++에서 ‘WinDialogBox’란 프로젝트를 만든다(‘Win32 Dynamic-Link Library’ 타입으로 생성하며, 위저드의 첫 단계에서 ‘A Simple Projcet’를 체크한다). 그리고 javah 프로그램이 생성한 WinDialogBox.h 파일을 프로젝트에 포함시키고, Win DialogBox.cpp 파일을 <리스트 7>과 같이 편집한다.

이 C++ 프로그램의 내용을 잠깐 살펴보자. Java_WinDialog Box_doModal() 함수는 다이얼로그를 화면에 표시하는 함수이다. Java_WinDialogBox_setTextField()는 자바 코드에서 문자열을 넘겨 전역 문자 배열에 저장하는 함수이며, Java_WinDialogBox _getTextField() 함수는 전역 문자 배열의 내용을 jstring 타입으로 변환해 자바로 문자열을 리턴하는 함수이다.

DialgProc() 함수는 다이얼로그 프로시저이다. 이 다이얼로그 프로시저는 두 개의 메시지(WM_INITDIALOG, WM_COMMAND)를 처리하고 있다. 다이얼로그가 화면에 나타날 때, 즉 WM_INIT DIALOG 메시지가 전달되면 SetDlgItemText() 함수를 이용해 전역 문자열에 있던 문자를 에디트 컨트롤에 지정한다.

반대로, 버튼이 눌리면 GetDlgItemText() 함수를 이용해 편집된 문자열을 얻어와 전역 문자열에 저장한다. 이 저장된 값은 getTextField() 메쏘드를 통해 자바 프로그램으로 리턴되어 편집된 내용을 출력하게 된다.

이 C++ 코드가 생성하는 DLL은 리소스를 가지고 있다. 따라서 프로젝트에 리소스를 추가하고 다이얼로그 박스를 생성한다. 다이얼로그 박스는 <화면 3>과 같이 디자인한다. 이 다이얼로그 박스에는 IDC_EDIT_FIELD란 ID를 갖는 에디트 컨트롤이 포함돼 있다. 프로그램을 수행하면 <화면 4>와 같은 Win32 다이얼로그 박스가 나타난다. 여기에 입력된 내용은 다시 자바 프로그램으로 리턴돼 <화면 5>와 같이 도스창 화면에 출력된다.

화면 5

화면 5


지금까지 기본적인 데이터 타입인 jint, jstring 등의 데이터를 자바와 C 코드에서 넘겨주고 받는 방법들을 살펴보았다.

참고로 다음 코드는 자바에서 Win32 애플리케이션이 프로그램의 상태와 관련된 정보를 저장하기 위해 사용하는 Win32 API 함수 중의 하나인 GetPrivateProfileString()을 구현해본 것이다(레지스트리의 등장으로 요즘은 거의 사용하지 않지만). JNI는 이렇듯, 운영체제의 특징적인 서비스를 이용할 수 있는 장점이 있다.

jstring JNICALL Java_Win32Profile_GetPrivateProfileString(JNIEnv *jni, jclass, jstring section, jstring key, jstring defaut, jint maxLen, jstring name) {
    char returned[4096];
    LPCSTR file;
    LPCSTR szSection;
    LPCSTR szKey;
    LPCSTR szDefault;
    jint ret = 0;
    file = jni->GetStringUTFChars(name, 0);
    szSection = jni->GetStringUTFChars(section, 0);
    szKey =jni->GetStringUTFChars(key, 0);
    szDefault=jni->GetStringUTFChars(defaut, 0);

    if (0 != strcmp(file, “win.ini”))
        ret = GetPrivateProfileString(szSection, szKey, szDefault, returned, maxLen, file);
    else
        ret = GetProfileString(szSection, szKey, szDefault, returned, maxLen);
    if (*returned == ‘\0’)
        strcpy(returned, szDefault);
    jni->ReleaseStringUTFChars(name, file);
    jni->ReleaseStringUTFChars(section, szSection);
    jni->ReleaseStringUTFChars(key, szKey);
    jni->ReleaseStringUTFChars(defaut, szDefault);
    return jni->NewStringUTF(returned);
}

주의 깊은 독자라면 이 프로그램을 테스트하면서 커다란 문제점을 발견할 수 있을 것이다. 그것은 바로 개발자들을 끊임없이 괴롭혀온 문제 중의 하나인 한글 문제이다.

자바에서 한글이 포함된 문자열을 C로 보내면(UTF-8 포맷으로), C에서는 이를 직접 사용할 수 없다. 또한 다이얼로그 박스에서 한글이 입력돼 자바로 전달하면 이것도 나타나지 않거나 ‘???’로 표시된다. 이런 문제는 왜 생기며, 어떻게 해결할 수 있을까?

화면 3

화면 3


화면 4

화면 4



★ JNI와 한글 문제

자바는 기본적으로 모든 문자열을 유니코드(Unicode)로 처리한다. 유니코드는 한 글자를 16비트로 표현하는 표준 코드로 바이트 문자열에 비해 크기가 커지는 단점이 있지만, 다양한 언어를 자유롭게 지원할 수 있다는 장점이 있다.

- 자바 : 유니코드(Unicode)
- JNI : UTF-8
- C/C++ : 바이트 문자열(KSC5601)

하지만 보통 Win32, C/C++ 환경에서 사용하는 문자열은 유니코드가 아닌 바이트 문자열(KSC5601)이다. 더구나 JNI에서 사용하는 jstring은 UTF-8 포맷으로 인코딩된 문자열이므로 직접 C 코드에서 사용하는 데는 어려움이 있다. 앞의 프로그램에서 해본 대로 영문자의 경우는 아무런 문제가 없다.

UTF-8 포맷이 영문자에 대해서는 1바이트로 인코딩되기 때문에 바이트 문자열과 UTF-8 포맷의 문자열은 사실상 같은 것이라고 보아도 무방하다. 하지만 한글의 경우는 다르다. 예를 들어보자. ‘한’이란 문자가 있다 하자. 이 글자는 <표 3>에서 볼 수 있듯 전혀 다른 형태로 저장된다.

만일 JNI를 통해 한글을 쓰고 싶다면, JNI를 통해 넘어온 jstring 문자열은 UTF-8 포맷이므로 이를 KSC5601 코드로 바꾸는 작업이 필요하다. 이 작업을 C 프로그램에서 해야 한다면, 사실 배보다 배꼽이 더 큰 일이다. 더욱이 UTF-8은 KSC5601 코드를 변환한 것이 아니라 유니코드를 인코딩한 것이므로 일은 더욱 복잡해진다.

자바에는 이 코드 변환에 대한 해결책이 이미 준비돼 있다. 따라서 이것을 C에서 해결하려 하기보다는 C에서는 단순하게 바이트 문자열만을 다루고, 유니코드와 UTF-8은 자바 코드에서 다루도록 하는 것이 프로그래밍을 훨씬 간단하게 한다.

즉, jstirng을 사용하지 않고, 바이트 배열을 주고받는 것이다. 경험으로 미루어 볼 때 자바와 C 간의 데이터 교환에서 가장 중요한 것은 사실 jstring보다 바이트 배열이다. 바이트 배열은 임의의 데이터 형태를 모두 전달할 수 있기 때문이다. 이것은 비단 JNI에만 국한된 문제는 아니다.

서블릿(sublet), 자바 애플리케이션(application), 애플릿(applet) 모두 외부에서 입력된 문자열을 처리해야 할 필요가 있기 때문에 자바 프로그래머라면 언제고 한번쯤은 부딪히게 되는 문제이다.

자바에서 외부에서 입력된 바이트 문자열을 자바 내부에서 사용하는 유니코드 혹은 UTF-8로 변환하는 것은 의외로 간단하다. <리스트 8>은 표준 입력에서 바이트 문자열을 입력받아 내부적으로 사용되는 String 객체로 다시 생성하는 방법을 보이고 있다.

System.in.read()를 통해 바이트 단위로 입력된 바이트 배열은 String 생성자를 통해 유니코드로 전환된다. String 생성자를 이용할 때, 바이트 배열이 인코딩된 방식을 지정해줄 수 있다.

이 생성자가 생성해낸 String 객체는 유니코드 문자열을 가지게 된다. 이 내용을 출력해 보면 똑같은 내용이 출력된다(<화면 6>). 자바에서 표준 출력에 출력할 때, 플랫폼 환경에 맞추어 다시 인코딩한 후 출력하기 때문이다.

화면 6

화면 6


String(byte[] bytes, String enc); // 바이트 문자열을 유니코드 문자열로 전환
byte[]getBytes(String enc); // 유니코드 문자열을 바이트 문자열로 전환

이 두 개의 메쏘드는 String 클래스의 메쏘드로, 코드 변환에 유용하게 사용된다. enc에는 인코딩된 방식을 써주면 된다. 예를 들어 Win32 환경에서 가장 많이 사용하는 KSC5601 코드의 경우, 바이트 문자열을 유니코드로 전환하려면 다음과 같이 한다.

byte buffer [] = new byte[200];
...
String uni = new String(buffer, KSC5601);

또한 유니코드를 KSC5601 바이트 문자열로 바꾸고자 한다면, 다음과 같이 해주면 된다.

byte[] buffer;
String uni = “한글”;
buffer = uni.getBytes(KSC5601);

앞에서 작성해 본 WinDialogBox.java 프로그램을 한글 처리를 염두에 두고 다시 작성해 보자. 파일 한글 처리가 문제되는 것은 외부에서 입력받는 내용이 유니코드 문자열이 아니라, 일반적으로 KSC5601 코드의 바이트 문자열이라는 점에 있다. 따라서 앞서 이야기한 KSC5601 바이트 코드와 유니코드 간의 변환을 이용하되, 자바 코드 외부에서 입력 혹은 전달되는 내용은 모두 바이트 배열로 처리하는 것이 바람직하다.

JNI에서는 <표 1>의 JNI에서 볼 수 있는 데이터 타입 이외에 다음과 같은 데이터 타입을 지
원한다.

struct _jobject;
typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

객체나 각 기본 타입의 배열들에 대한 지원도 하고 있다. 여기서는 jbyteArray 타입을 이용할 것이다.

<표 3> ‘한’에 대한 코드
바이트 문자열(KSC5601)
유니코드
UTF-8
0xC7 0xD1
0xD55C
0xED 0x95 0x9C
2바이트
16비트 코드
3바이트


★ JNI에서 자바 배열 객체

자바의 배열은 자바의 객체이다. JNI를 통해 넘겨받은 자바의 배열을 다루기 위해 JNI가 제공하는 함수들에 대해 알아보자.

모든 자바 배열은 크기 필드를 갖고 있다. 다음 함수는 자바 배열 요소 개수를 알아내는 함
수이다.

jsize GetArrayLength(JNIEnv *env, jarray array);

배열의 각각 요소들을 다루기 위해서는 다음 JNI 함수가 필요하다.

jobject GetObjectArrayElement(JNIEnv *env, jarray array, jsize index);
void SetObjectArrayElement(JNIEnv *env, jarray array, jsize index, jobject value);

이름에서도 알 수 있듯이 이 두 개의 함수는 배열의 요소를 얻거나 요소를 할당하는 함수이다. 배열에서 요소 값을 읽어올 때는 다음과 같은 GetArrayElements() 함수를 이용한다. 이 함수의 세번째 인자 isCopy는 자바 VM에서 배열의 요소들이 기억장소에 연속적으로 배치되어 있는지의 여부를 jboolean값으로 리턴한다. 이 값이 참(JNI_ TRUE)이라면 연속적으로 배
열된 복사본이라는 사실을 알 수 있고, 거짓(JNI_FALSE)이라면 실제 배열 요소를 다루어야
한다.

NativeType GetArrayElements(JNIEnv *env, jarray array, jboolean *isCopy);

<표 4>는 GetArrayElements() 함수의 실제 함수 이름과 타입별 리턴 타입, 그리고 그에 해당하는 자바의 배열 타입을 정리한 것이다. 이 함수를 사용하고 나면, ReleaseArrayElements() 함수를 사용해 복사본을 기억장소에서 해지해야 한다. 이 함수의 실제 함수 이름과 리턴 타입도 <표 5>에 정리해 놓았다.

<표 4> 배열을 다루기 위한 JNI의 GetArrayElements 함수
JNI 함수
리턴타입
자바 배열 타입
GetBooleanArrayElements()
jboolean *
boolean[]
GetByteArrayElements()
jbyte *
byte[]
GetCharArrayElements()
jchar *
char[]
GetShortArrayElements()
jshort *
short[]
GetIntArrayElements()
jint *
int[]
GetLongArrayElements()
jlong *
long[]
GetFloatArrayElements()
jfloat *
float[]
GetDoubleArrayElements()
jdouble *
double[]

<표 5> 배열을 다루기 위한 JNI의 ReleaseArrayElements 함수
JNI 함수
네이티브 배열 타입
자바 배열 타입
ReleaseBooleanArrayElements()
jboolean *
boolean[]
ReleaseByteArrayElements()
jbyte *
byte[]
ReleaseCharArrayElements()
jchar *
char[]
ReleaseShortArrayElements()
jshort *
short[]
ReleaseIntArrayElements()
jint *
int[]
ReleaseLongArrayElements()
jlong *
long[]
ReleaseFloatArrayElements()
jfloat *
float[]
ReleaseDoubleArrayElements()
jdouble *
double[]

void ReleaseArrayElements(JNIEnv *env, jarray array, NativeType elems, jint mode);

이 함수는 mode에 세 가지 다른 값을 지정할 수 있다.

0 : 변경된 내용을 반영하고, 복사본의 기억장소를 해지한다.
JNI_COMMIT : 변경된 내용을 반영만 하고, 복사본의 기억장소를 해지하지 않는다.
JNI_ABORT : 변경된 내용을 반영하지 않고, 복사본의 기억장소를 해지한다.

이제 WinDialogBox의 두번째 버전(WinDialogBox2)을 만들어 보자. 이 프로그램을 만드는 방법은 앞서 보았던 WinDialogBox 프로그램을 만드는 방법과 꼭 같다. 자바 소스 프로그램을 <리스트 9>, <리스트 10>과 같이 만들고 컴파일한다. 그 다음, javah 명령을 이용해 WinDialogBox2.h 파일을 생성한다.

C:\jni> javac TestWinDialogBox2.java
C:\jni> javac WinDialgoBox2.java
C:\jni> javah jni WinDialgoBox2

이제 DLL을 생성하기 위한 프로젝트로 WinDialogBox2를 새롭게 만든다. 여기에 다이얼로그 박스를 만들고, WinDialogBox2.cpp 프로그램을 <리스트 11>과 같이 작성한다. 빌드하여 만들어진 WinDialgoBox2.DLL 파일을 클래스 파일이 있는 곳에 복사하고, 다음과 같이 자바 프로그램을 수행시킨다.

C:\jni> java TestWinDialogBox2

<화면 7>은 TestWinDialogBox2 프로그램을 수행시킨 화면이다. 이제 한글 문제가 완전히 해
결됐다.

화면 7

화면 7


이처럼 JNI를 이용해 C 프로그램과 데이터 교환을 할 때 가장 유용하게 사용할 수 있는 것이 바이트 배열이다.


★ C/C++에서 자바 사용

지금까지 우리는 자바에서 C/C++ 코드를 사용하는 방법을 살펴보았다. 그렇다면 반대로 C/C++에서 VM을 이용하려면 어떻게 해야 할까? 이 부분은 자바에서 C 코드를 호출하는 것에 비해 상대적으로 중요하지 않기 때문에 자세하게 다루지는 않지만, 독자 여러분이 찾아볼 수 있을 정도의 간략한 개요를 정리해 보겠다.

C/C++에서 VM을 사용하기 위해 가장 먼저 해야 할 것은 C 코드에서 자바 VM을 생성하는 일이다. JNI는 C/C++ 애플리케이션에 자바 VM을 포함(embedding)할 수 있도록 지원한다. C/ C++ 애플리케이션에 VM을 생성하면, VM의 모든 클래스와 메쏘드를 이용할 수 있다.

환경에 대한 디폴트 설정 값을 얻어오기 위해 JNI_GetDefaultJavaVMInitArgs() 함수를 사용한다(JDK 1.1 이하). 이 함수는 JDK1_1InitArgs 구조체 포인터를 인자로 받는데, 이 구조체에는 다음과 같이 버전(vm_args.version) 값을 지정해 주어야 한다.

JDK1_1InitArgs vm_args;
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs (&vm_args);

버전 값은 다음과 같이 정의돼 있다(JDK 1.2 이상). 버전으로 사용된 값 0x00010001의 의미는 1.1.2 이상되는 버전의 VM이면, 애플리케이션에서 사용할 수 있다는 의미가 된다.

#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002

그런데 이 JNI_GetDefaultJavaVMInitArgs() 함수는 JDK 1.2 이상에서는 쓰이지 않는다. C/C++에서 VM을 사용하려면 먼저 JNI_CreateJavaVM() 함수를 통해 VM을 생성한다.

JavaVM *jvm;
JNIEnv *env;
jint rc = JNI_CreateJavaVM (&jvm, &env, &vm_args);

이렇게 해 JVM이 생성되면, JNI 함수 중 FindClass()와 Call StaticVoidMethod()를 이용해 자바 클래스와 메쏘드를 호출한다. JVM 사용이 끝나면, DestroyJavaVM() 함수를 써서 VM을 삭제한다.

jvm-> DestroyJavaVM ()

C/C++에서 대략 이런 과정을 통해 VM을 사용하게 된다. C/C++에서 자바 클래스를 이용하는 것이 실제로 많이 요구되지는 않는다.

지금까지 자바에서 C/C++ 코드를 호출하는데 필요한 내용들을 살펴보았다. 특히, 유니코드 관련 내용과 바이트 배열 처리 부분은 상당히 유용한 코드가 될 것이다.

이 JNI 코드 작성을 통해 우리는 몇 가지 사실을 알 수 있다. 우선, 자바에서 모든 Win32 호출을 이용할 수 있다는 점이다. 이것은 유닉스에서도 마찬가지이다. 모든 절차는 똑같으며, 단 유닉스에서는 DLL 대신 공유 라이브러리(shared library)를 사용한다는 점이 다르다.

이 글은 애플릿에서의 문제점은 다루고 있지는 않다(애플릿에서 JNI는 보안과 인증 문제를 갖고 있기 때문에 이 글에서 모두 다루기는 힘들다). C/C++로 액티브X 컴포넌트를 개발하는 프로그래머들이 비주얼 베이직 개발자들을 지원하듯, JNI는 C/C++ 개발자들이 자바 개발자들을 지원하는 창구 역할을 한다.

아무튼 자바에서 기존의 C/C++로 작성된 코드를 사용할 수 있고, 자바와 C/C++ 간의 데이터 교환이 가능하며, Win32 API를 모두 이용할 수 있다는 점은 JNI의 큰 매력이 아닐 수 없다.
컴퓨터/프로그래밍/Java l 2008/07/24 11:00

'language > JAVA' 카테고리의 다른 글

Decompiler jad  (0) 2010.07.22
& 0xFF를 사용한 unsigned byte 및 int 비교 방법  (0) 2010.07.21
JNI (Java Native Interface) 란 ?  (0) 2009.06.30
블로그 이미지

란마12

,

1. JNI (Java Native Interface) 란 ?

- 자바가 다른 언어로 만들어진 어플리케이션과 상호 작용할 수 있는 인터페이스를 제공한다.

- 자바가상머신(JVM)이 원시 메소드(native method)를 적재(locate)하고 수행(invoke)할 수 있도록 한다

- JNI가 자바가상머신내에 포함됨으로써, 자바가상머신이 호스트 운영체제상의 입출력, 그래픽스, 네트워킹, 그리고 스레드와 같은 기능들을 작동하기 위한 로컬시스템호출(local system calls)을 수행할 수 있도록 한다.

* 쉽게 말해 Java와 다른 언어를 연동하는 솔루션입니다.

 

[그림1] C로 만들어진 Library와 JAVA를 연결해주는 JNI

 

 

 

2. Why do you need JNI ?

 

자바 네이티브 메쏘드(Java Native method, 이하 JNI)는 다른 언어로 작성된 코드를 자바에서 호출하도록 만들어진 규약이다. 현재는 C/C++에 대한 호출만을 정확하게 지원한다. 어떻게 보면 JNI는 자바가 만들어진 철학과 정반대되는 것이다.

 

그러나. Java에도 한계가 있다.

 

1. 속도 문제가 있는 계산 루틴
 > 자바가 Native Code(플랫폼에 종속적인 기계어 코드)에 비해 느리다.

2. 자바에서 하드웨어 제어

3. 자바에서 지원되지 않은 특정 운영체제 서비스
 > 자바의 클래스 라이브러리는 방대하고 다양한 서비스를 제공하지만, 특정 플랫폼에서 제공하는 고유의 서비스의 기능을 모두 포함할 수는 없다. 특히, 특수한 목적으로 제작된 하드웨어를 자바에서 제어해야 할 필요가 있다고 한다면, 자바만으로 해결하기는 힘들다.

4. 기존의 프로그램에서 자바가 제공하는 서비스를 이용
 > 기존에 작성된 프로그램이나 기존의 시스템(legacy)과의 연계 문제

 

∴ JNI를 써서 해결해보자.

 

 

3. C를 이용한 JNI 예제

VC++을 이용해 C문법으로 작성되어 만들어진 DLL을 로딩하여 Java에서 사용해보겠습니다.

 

1단계 : Native Method를 선언하는 자바 클래스 작성
2단계 : 1단계에서 작성한 클래스 컴파일
3단계 : javah를 사용해서 Native Method가 사용할 헤더 파일 생성
4단계 : C언어로 Native Method 실제 구현
5단계 : C 코드와 헤더 파일을 컴파일
6단계 : 자바 프로그램 실행


 

1단계 : Native Method를 선언하는 자바 클래스 작성

Java 소스 파일 : HelloJni_Jsource.java

 

import java.util.*;

class HelloJniClass {
   native void Hello();

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

  public static void main(String args[]) {   
      HelloJniClass myJNI=new HelloJniClass();
      myJNI.Hello();
   }
}

 

// 아래는 좀 위의 내용 보충 그림



 


2단계 : 1단계에서 작성한 클래스 컴파일



* 컴파일시에는 일반 java 컴파일때와 마찬가지로 환경변수 셋팅이 되어 있어야 합니다.
 -> Path가 JDK의 Javac.exe가 있는 폴더에 설정되어 있어야 합니다.

 

 

3단계 : javah를 사용해서 Native Method가 사용할 헤더 파일 생성

 

HelloJniClass.h을 열어보면

JNIEXPORT void JNICALL Java_HelloJniClass_Hello  (JNIEnv *, jobject);
위의 함수를 Implement만 해서 DLL을 만들면 됩니다. (4단계)


4단계 : C언어로 Native Method 실제 구현(1)

1) VC++ 프로젝트 만들기 : Win32용 DLL 프로젝트로 만듭니다.
New - Projects : Win32 Dynamic-Link Library

2) Add Files Projects : HelloJniClass.h 파일 추가

3) Projects Setings(Alt+F7)
   - Link탭에 Output file Name : 1단계의 2. 라이브러리 적재시 작성한 DLL파일명(Hello_DLL.dll)
   - C/C++탭 Preprocessor 카테고리의 Additional Include directories
       JDK의 Include폴더와 Include폴더 밑의 win32폴더

          예) C:\Program Files\Java\jdk1.5.0_03\include\,
             C:\Program Files\Java\jdk1.5.0_03\include\win32

 

 


 

 

4. 값의 전달과 리턴

 

1단계 : Java 소스 파일 StringPass_Jsource.java
* 일반 자바 메쏘드 선언과 동일합니다.

class JNI_Message {
   native byte[] Message(String input);

  // 라이브러리 적재(Load the library) 

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

 

  public static void main(String args[]) {   
 byte buf[];

    // 클래스 인스턴스 생성(Create class instance)
    JNI_Message myJNI=new JNI_Message();

    // 원시 메소드에 값을 주고 받음
    buf = myJNI.Message("Apple");
 
 System.out.print(buf); // 받은값 출력
 }
}

 


2단계 : 컴파일
 javac StringPass_Jsource.java

 

3단계 : header파일 생성
 javah JNI_Message

 

4단계 : method구현 : StringJNIDLLSource.c

#include <stdio.h>
#include <jni.h>
#include <string.h>
#include "JNI_Message.h"

JNIEXPORT jbyteArray JNICALL Java_JNI_1Message_Message (JNIEnv * env, jobject jobj, jstring input)
{
    jbyteArray jb;
    jboolean iscopy;
    char* buf;
    static char outputbuf[20];

    buf=(*env)->GetStringUTFChars(env, input, &iscopy);  // 입력 String 읽어오는 함수
    printf ("\nDLL receive Data from JAVA : %s\n",buf);   // 입력받은 내용을 출력
    strcpy(outputbuf,"Delicious !!\n");
    jb=(*env)->NewStringUTF(env, outputbuf);  // 출력할 내용의 java버퍼에 output버퍼값을 셋팅

   return(jb); // java버퍼 리턴
}

 

 

(*env)->함수명 형태로, JAVA의 메쏘드를 C에서 이용할수 있습니다.
* JAVA는 C로 문자열을 넘겨줄때 UTF-8형태를 사용합니다.
* cpp로 컴파일하지 말고 c로 컴파일 한다. 

 

5단계 : 실행

C:\test\C_JNI\Paramerter Pass>java JNI_Message

DLL receive Data from JAVA : Apple
Delicious !!

 

 

 


5. KVM ? KNI ?

KVM은 J2ME의 일부로서 작고 자원이 한정된 기계장치를 위해 설계된 소형 JVM.
JVM에서는 JNI가 KVM의 KNI가 있다.


 

6. 기타프로그래밍 이슈들
참고 URL :http://www.javastudy.co.kr/docs/jhan/javaadvance/jni.html

언어적 이슈(Language Issues)
메소드 호출(Calling Methods)
필드의 참조(Accessing Fields)
스레드와 동기화(Threads and Synchronization)
메모리 이슈(Memory Issues)
수행(Invocation)
스레드 연결(Attaching Threads)


http://sinuk.egloos.com/2676307

'language > JAVA' 카테고리의 다른 글

Decompiler jad  (0) 2010.07.22
& 0xFF를 사용한 unsigned byte 및 int 비교 방법  (0) 2010.07.21
JAVA Native Method (JNI)  (1) 2009.07.01
블로그 이미지

란마12

,