GCJ - java로 씌여진 코드를 Native Code로 바꿔주는 컴파일러
2007. 5. 15. 10:10ㆍJava
[edit]
1.1 사용환경 ¶GCJ는 gcc의 일부이기 때문에 gcc를 사용할 수 있는 환경이 되어야 한다. 기본적으로 리눅스에서는 사용이 가능하며 윈도우즈에서 http://www.mingw.com 에서 제공하는 것을 이용할 수 있다. [edit]
1.2 간단한 예제 ¶public class ExamGCJ { public static void main(String[] args) { System.out.println("ExamGCJ Class GCJ Test"); } } # gcj --main=ExamGCJ ExamGCJ.java -o ExamGCJ
[edit]
1.3 두개 이상의 클래스로 이루어진 코드 컴파일 ¶public class ExamGCJ { public static void main(String[] args) { System.out.println("Two Class GCJ Test"); new LibExamGCJ(); } } public class LibExamGCJ { LibExamGCJ() { System.out.println("Two Class GCJ Test --- this is print in lib"); } } # gcj -c LibExamGCJ.java # gcj -c ExamGCJ.java # gcj --main=ExamGCJ ExamGCJ.o LibExamGCJ.o -o ExamGCJ [edit]
1.4 라이브러리로 묶어서 사용하기 ¶분명 어떤 언어로 프로그래밍을 하든지 자주 쓰는 코드를 묶어두고 사용하는 것이 편리하다. GCJ에서도 자주 사용하는 코드를 묶어두고 필요할때 사용하는 방법이 존재한다. GCJ는 GCC의 일부다. 따라서 GCC처럼 라이브러리를 생성할 수 있다. 필자는 리눅스를 자주 사용하지 않는다. 따라서 여기의 설명이 윈도우즈의 MinGW에서의 작업에 초점이 맞추어져 있다는 것을 참고하기 바란다.
# ar -crs libexam.a LibExamGCJ.o
아무튼 이렇게 생성한 것을 같이 링크하면 되는데 그것역시 GCC와 동일하다.
# gcj --main=ExamGCJ ExamGCJ.o -L.\ -lexam
따라서 LibExamGCJ와 같이 라이브러리로 묶을 소스 코드는 바이트 코드로도 생성해서 Jar로 묶어두고 이 라이브러리를 사용하는 경우에 클래스 패스에 포함시키는 것이 좋다.
# gcj -C LibExamGCJ.java # jar cf swt.jar LibExamGCJ.class [edit]
2 패키지를 라이브러리화 하기 ¶GCJ는 주로 Native Code를 생성하기 위해 사용하는 컴파일러이기 때문에 자바로 이루어진 패키지를 라이브러리화하여 링크하는 것이 원할한 개발을 위해 바람직하다. 특히 링크할때 오브젝트코드(.o)는 사용하지 않아도 같이 묶지만 정적링크라이브러리(.a)는 필요한 것만 묶어지므로 라이브러리화 해서 사용하는 것이 좋다. 그러면 그 방법을 하나의 예제와 함께 간단히 소개하고자 한다. [edit]
2.1 패키지 안의 오브젝트코드의 이름은 어떻게 정해지는가? ¶패키지의 구성을 보면 보통 이런 형태로 되어 있을 것이다. java.util.Vector 위에 보면 알 수 있듯이 java.util 패키지의 Vector클래스이다. 그리고 소스 코드 내에서 사용하려면 다음처럼 import를 한다. import java.util.Vector 물론 GCJ로 컴파일 할 소스도 똑같이 한다. 그럼 이것을 사용한 코드를 링크할때는 어떤 이름을 가진 오프젝트가 필요한가? 바로 다음과 같은 이름을 가진 오브젝트 코드가 필요하게 된다. 라이브러리 안에서 이야기이다. java_util_Vector.o 그리고 실제로 libgcj.a안에 존재한다. 아무튼 그렇게 라이브러리를 만들어두고 필요할때마다... 즉 해당 패키지를 사용할때마다 오프젝트 코드가 아닌 라이브러리를 링크해서 사용하면 된다. [edit]
2.2 간단한 예제를 통해서 이해하기 ¶이제 간단한 예제를 통해서 이해해 보자. 이쯤되면 앞의 "GCJ의 간단한 소개"에서는 사용하지 않았지만 Makefile을 사용하는게 편할 것이다. 물론 GCC를 사용하시는 분들은 당연히 받아들여지겠지만 JAVA만 하시던 분들은 조금 어색하실지도 모른다. 보통 자바로 프로그래밍을 하면 Apache Project의 Ant가 그 비슷한 역할을 하기 때문에 Ant가 익숙할 것이다. 아무튼 이제 우리는 GCC(Gnu Compiler Collection)을 사용하니 Gnu Make Tool인 Makefile을 사용할 것이다. 그리고 Makefile의 복잡한 것은 모두 제외하고 꼭 필요한 단순한 것만 사용할 것이므로 걱정하지 않아도 된다. 혹시 Makefile에 대한 내용을 모르신다면 kldp.org에서 찾아서 가볍게 읽어보기 바란다. 그럼 이제 시작하도록 하겠다. 먼저 디렉토리 구성을 간단히 살펴보자. 존재하는 디렉토리는 다음과 같다. ./bin ./obj ./lib ./src ./src/org/nahome/hangulee/
package org.nahome.hangulee; public class LibExamGCJ { LibExamGCJ() { System.out.println("Two Class GCJ Test --- this is print in lib"); } } package org.nahome.hangulee; public class LibExamGCJ2 { LibExamGCJ2() { System.out.println("Two Class GCJ Test --- this is print in lib2"); } } CLASSPATH = ./src; OBJS = \ obj/org_nahome_hangulee_LibExamGCJ.o \ obj/org_nahome_hangulee_LibExamGCJ2.o CLASSES = \ bin/org/nahome/hangulee/LibExamGCJ.class \ bin/org/nahome/hangulee/LibExamGCJ2.class all:$(OBJS) $(CLASSES) exam.jar lib/libexam.a lib/libexam.a: ar -crs ./lib/libexam.a ./obj/*.o exam.jar: jar cf exam.jar -C bin . bin/org/nahome/hangulee/LibExamGCJ.class:src/org/nahome/hangulee/LibExamGCJ.java gcj -C -fCLASSPATH=$(CLASSPATH) -d bin src/org/nahome/hangulee/LibExamGCJ.java obj/org_nahome_hangulee_LibExamGCJ.o:src/org/nahome/hangulee/LibExamGCJ.java gcj --classpath=$(CLASSPATH) -c src/org/nahome/hangulee/LibExamGCJ.java \ -o obj/org_nahome_hangulee_LibExamGCJ.o bin/org/nahome/hangulee/LibExamGCJ2.class:src/org/nahome/hangulee/LibExamGCJ2.java gcj -C -fCLASSPATH=$(CLASSPATH) -d bin src/org/nahome/hangulee/LibExamGCJ2.java obj/org_nahome_hangulee_LibExamGCJ2.o:src/org/nahome/hangulee/LibExamGCJ2.java gcj --classpath=$(CLASSPATH) -c src/org/nahome/hangulee/LibExamGCJ2.java \ -o obj/org_nahome_hangulee_LibExamGCJ2.o 자!.. 이제 간단한 라이브러리가 준비되었다. [edit]
2.3 라이브러리를 사용한 간단한 예제 ¶예제는 다른 디렉토리에다가 만들도록 할 것이다. 그리고 이번에는 Makefile없이 간단히 컴파일 하는 것으로 끝내도록 할것이다.. 우선 다음과 같은 파일을 만든다. import org.nahome.hangulee.*; public class ExamGCJ { public static void main(String[] args) { System.out.println("Two Class GCJ Test"); new LibExamGCJ(); new LibExamGCJ2(); } } gcj --classpath=(위 예제가 있는 디렉토리 경로)/exam.jar -c ExamGCJ.java -o ExamGCJ.o gcj --main=ExamGCJ ExamGCJ.o -L(위 예제가 있는 디렉토리 경로) -lexam -o ExamGCJ Two Class GCJ Test Two Class GCJ Test --- this is print in lib Two Class GCJ Test --- this is print in lib2 (위 예제가 있는 디렉토리 경로)/src
[edit]
3 Class.forName를 사용할때 ¶JAVA로 프로그래밍을 할때 JDBC 드라이버를 동적으로 로딩하는 경우... 그 경우 외에는 그다지 사용되지 않은듯 하는 메소드일 것이다. 하지만 데이터 베이스 없이 프로그래밍을 하는 경우도 흔치 않으니 자주 사용된다고 볼 수 도 있을 것이다. 그러면 이런 Class.forName이라는 클래스가 어떻게 동작하는지에 대해서 간단히 알아보자. [edit]
3.1 GCJ로 프로그래밍 할때 Class.forName메소드 ¶이제 이 메소드를 GCJ에서 어떻게 사용할 수 있을까? 위에서 보니 동적으로 로딩하는데... GCJ는 하나로 링크할때 묶어서 사용되어야 한다는 것을 알게 된다. 다음 예제를 수행해보기 바란다. public class ExamClassforName { public static void main(String[] args) { System.out.println("Class.forName Example"); try { Class ex = Class.forName("TextClass"); } catch(Exception e) { System.out.println("error = "+e.toString()); } } } ./와 core://에서 찾았으나 없다고 나온다. 바로 그 위치에 클래스가 존재해야 한다. 결국 이 메소드를 쓰면 단순히 println할때보다.. 네이티브 코드의 크기가 1MB정도 증가한것을 볼 수 있는데... 클래스를 해석할.. 필요한게 바인딩 된 것일 것이다. 그 아래 컴파일된 클래스가 있다면... 그런데 그 컴파일된 클래스는 바이트코드로 컴파일 되어야 한다. 아무튼 있다면 그것을 읽어들여서 사용하게 된다. [edit]
3.2 간단한 예를 통해 알아보기 ¶이제 예를 통해 조금 더 알아보도록 하자. 아래와 같이 한개의 인터페이스와 그것을 구현한 클래스 그리고 사용할 클래스를 준비한다. public interface forNameExamInterface { void test(); } public class forNameExam implements forNameExamInterface { forNameExam() { System.out.println("forNameExample - initial"); } public void test() { System.out.println("forNameExample - test"); } } public class ExamClassforName { public static void main(String[] args) { System.out.println("Class.forName Example"); try { Class ex = Class.forName("forNameExam"); forNameExamInterface obj = (forNameExamInterface)ex.newInstance(); obj.test(); } catch(Exception e) { System.out.println("error = "+e.toString()); } } } OBJS = forNameExamInterface.o \ ExamClassforName.o CLASSES = forNameExam.class MAINCLASS = ExamClassforName all: $(CLASSES) $(OBJS) gcj --main=$(MAINCLASS) $(OBJS) -o $(MAINCLASS) forNameExamInterface.o : forNameExamInterface.java gcj -c forNameExamInterface.java -o forNameExamInterface.o ExamClassforName.o : ExamClassforName.java gcj -c ExamClassforName.java -o ExamClassforName.o forNameExam.class : forNameExam.java gcj -C forNameExam.java [edit]
4 GCJ에서 GUI 하기 ¶GCJ는 자바 API 1.2에 기준을 둔 API를 제공한다. 하지만 GCJ 공식 홈페이지에서 밝히고 있듯이 몇몇 패키지가 빠져 있으며 대표적인 것이 AWT이다. 따라서 GCJ로 자바 프로그래밍을 하려면 GUI에 있어서 문제를 겪게 될 수 있다. 물론 GCJ User Group의 LINK에 링크해두었듯이 XAWT같은 것도 있다. 그러한 대안으로 Eclipse를 제작하기 위해 Eclipse Project의 일부로 개발된 SWT를 GCJ로 빌드해서 사용할 수 있다. 실제 그렇게 하고 있는 사람들이 있으며 그점에 대해서는 GCJ User Group의 Projects에서 "JFace & SWT Native Library Build"를 참고하시기 바란다. 잠시 언급을 하자면 현재 이 글을 쓰고 있는 시점에 2.1을 빌드한 결과물은 타 사이트에서 베포중이며.. GCJ User Group의 Project에서는 3.0M6을 빌드했으며 이는 아직 베포하지 않고 있다. 계정 용량 관계로 마땅히 베포할 만한 장소가 없어서이다. RefactorMe JavaGnome을 이용하면 GTK/GNOME을 GUI로 사용할 수 있다. GNOME 2.6부터는 GnomePlatformBinding이라는 형태로 다른 언어로 GNOME어플리케이션을 개발하기 위한 라이브리러를 배포하는데, C++, Perl과 함께 Java가 기본으로 포함되어 있다. --iolo [edit]
4.1 SWT를 빌드하기 ¶SWT3.0M6을 기준으로 설명하겠다. 일단 소스를 받아서 디렉토리에 모두 풀면 대략 이런 디렉토리가 나온다. org/eclipse/swt/... 바로 패키지가 그렇게 되기 때문이다. 아무튼 이걸 염두해두고 빌드하면 된다. 패키지를 빌드하는 것은 JAVA의 Package를 라이브러리로 묶어 사용하기를 참고하기 바란다. 또한 JNI에 대한 내용도 참고하기 바란다. SWT라이브러리는 JNI를 사용하고 있기 때문이다. 하지만 그냥 빌드하면 몇가지 에러를 만나게 된다. 그런 에러를 해결하는 방법에 대해서 간단히 이야기하고 넘어가겠다. 다음 패키지의 클래스에서 에러가 발생한다. org.eclipse.swt.custom.TableCursor
void traverse... →boolean traverse...
org.eclipse.swt.custom.StyledText
javac -classpath .\src src/org/eclipse/swt/custom/StyledText.java
[edit]
4.2 SWT를 사용한 간단한 예제 ¶이제 빌드를 했다면 아마 두가지의 결과물이 있게 될 것이다. libswt.a swt.jar 그리고 다음 예제를 준비한다. import org.eclipse.swt.widgets.*; public class ExamSWT { public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.open (); while (!shell.isDisposed ()) { if (!display.readAndDispatch ()) display.sleep (); } display.dispose (); } } swt-awt-win32-3034.dll swt-win32-3034.dll 위 두파일은 소스를 받아서 직접 빌드를 할 수도 있으며 빌드된 것을 다운받을 수도 있다. [edit]
5 GCJ에서 Java Native Interface사용하기 ¶자바 프로그래밍을 할때 자바 내에서 해결이 어렵거나 빠른 처리를 위해서 JNI라는 Java Native Interface를 사용한다. GCJ에서도 이와 비슷한 형태로 프로그램을 작성하는 것이 가능하다. 다음 URL에 있는 예제를 함께 살펴보면서 GCJ에서 어떻게 JNI를 사용하는지 살펴보자. http://gcc.gnu.org/java/jni-comp.txt [edit]
5.1 JNI를 위한 기초 ¶GCJ에서는 Sun사의 Java에서처럼 JNI를 제공한다. 이를 통해서 GCJ에 패키지로 제공되지 않는다 하더라도 무엇이든지 하고자 하는 것을 하는 것이 가능하다. 하지만 JNI외에도 CNI를 제공한다. 아직 CNI에 대해서 필자는 제대로 아는 바는 없지만 어렴풋한 느낌에 의존해 이야기하자면 동적 링크 모듈의 필요성 여부이다. JNI를 사용한 경우에는 리눅스와 같은 경우에는 .so라는 모듈이 윈도우즈같은 경우에는 .dll이라는 모듈이 필요하게 된다. 반면에 CNI는 그것이 필요 없는 듯 하다. 이 점이 잘못된 내용이라면 정정해주기 바란다. 일단 이런 두가지 방법을 제공하기 때문에 GCJ에서는 기본적으로 CNI를 사용한다는 가정을 하고 자바 소스 코드를 컴파일한다. 그렇기 때문에 JNI를 사용하는 경우 그냥 컴파일을 하면 에러를 발생한다. 따라서 별도의 옵션이 필요하다. 바로 -fjni이다. 이것은 jni를 사용하는 swt를 빌드할때도 사용되었다. JNI를 사용하려는 자바 소스 코드가 있다면 다음처럼 컴파일한다. gcj -fjni -o sample sample.java
gcjh -jni sample
그리고 sample.h에 맞게 구현을 하였다면 그것을 동적 링크 모듈로 컴파일해야 한다. 방법은 gcc로 프로그래밍 할때와 같다. gcc -c sampNat.c gcc -shared -o sampNat.dll sampNat.o [edit]
5.2 간단한 예제 ¶간단한 예제를 보자. 위의 URL에서 그대로 복사해서 윈도우즈의 Mingw에서 사용 가능하도록 약간 수정을 했다. 거의 같다고 보아도 무방하다. sample.java
public class sample { public native void myNative(String s); public void myJava(String s) { s = s + ", Java"; System.out.println(s); } public static void main(String args[]) { sample x = new sample(); x.myJava("Hello"); x.myNative("Hello, Java (from C)"); x.myJava("Goodbye"); } static { System.loadLibrary("sampNat"); } } sampNat.c
#include <jni.h> #include "sample.h" #include <stdio.h> JNIEXPORT void JNICALL Java_sample_myNative (JNIEnv *env, jobject this, jstring s) { jclass cls; jfieldID fid; jobject obj; jmethodID mid; printf("From C\n"); cls = (*env)->FindClass(env, "java/lang/System"); if (cls == 0) { printf("java/lang/System lookup failed\n"); return; } fid = (*env)->GetStaticFieldID(env, cls, "out", "Ljava/io/PrintStream;"); if (fid == 0) { printf("java/lang/System::out lookup failed\n"); return; } obj = (*env)->GetStaticObjectField(env, cls, fid); if (obj == 0) { printf("GetStaticObjectField call failed\n"); return; } cls = (*env)->GetObjectClass(env, obj); if (cls == 0) { printf("GetObjectClass(out) failed\n"); return; } mid = (*env)->GetMethodID(env, cls, "println", "(Ljava/lang/String;)V"); if (mid == 0) { printf("println method lookup failed\n"); return; } (*env)->CallVoidMethod(env, obj, mid, s); } 이렇게 했다면 빌드하는 일이 남았다. 위의 URL에서는 Makefile을 이용하고 있다. 필자는 그냥 빌드에 필요한 순서대로 나열해보도록 하겠다. gcj -C sample.java gcjh -jni sample gcc -c sampNat.c -o sampNat.o gcc -shared -o sampNat.dll sampNat.o gcj -fjni -o sample sample.class --main=sample 그리고 생성된 sample을 실행시켜보라. 멋지게 실행될 것이다. [edit]
6 GCJ Bug & Patch ¶
문서 일지
See Also
from wiki.kldp.org by me |
|