임베디드 플랫폼에서의 효율적인 GUI 개발

기술적인 내용을 언급하기 전에 그 배경과 역사를 알아보도록 하자. 개발자들이 왜 이렇게 만들었고 어디에 쓰려고 했는지를 알면 우리도 거꾸로 이 라이브러리를 어디에 적용하면 어떤 면에서 좋고 나쁜지를 알 수 있을 것이다. 따라서 GTK+의 역사부터 알아보도록 하겠다.그 시작은 GIMPGnu Tool Kit, Gimp Tool Kit 둘 다 GTK+의 맞는 약자이다. 캘리포니아의 컴퓨터공학과 BSD로도 유명한 캘리포니아 주립대학 계열인 버클리 대학교의 xcf 랩 대학원생들이 처음 GTK+를 개발하기 시작하였다. 개발목적은 영리를 목적으로 한 것이 아닌, 대학원생들의 연구목적이 주체를 이루었다. 따라서 회사의 수익에 얽매이지 않은 새로운 시도와 충분한 개발시간을 통해 고품질의 라이브러리가 탄생할 수 있었다. 이런 성과는 연구와 하고 싶은 것에만 몰두할 수 있게 해주는 미국의 대학 문화 시스템이 기반이 되었다. 교수님의 심부름이 우선인 우리의 대학원 시스템이 본받아야 될 점이기도 하다.그들이 처음부터 GTK+개발을 목적으로 한 것은 아니었다. 당시 자유 소프트웨어 진영에서 포토샵과 같은 킬러 어플리케이션을 만들어 보려는 목적으로 GIMP를 만들기 시작하였는데 GUI 툴킷 라이브러리에 적당한 것이 없어서 MOTIF로 개발하기 시작하였다. 그러나 MOTIF가 가지는 있는 한계 때문에 개발이 불가능하다 판단하여 새롭게 모든 것을 만들기로 결정한다. 그래서 GTK+가 중간에 개발되기 시작한 것이다. 물론 이런 경우는 아주 드문 경우로 개발자의 능력과 환경이 기반 되어야 가능하다. 마치 윈도우우즈에서 비교하자면 MFC로 개발하다가 메시지 맵 방식이 마음에 안 들어서 API로 다시 클래스 라이브러리를 새롭게 만들어 쓴다는 얘기와 같다.공부해보기 아주 좋은 코드로 분석해보면 이것은 대학원생이라는 학생 수준에서 나올 수 있는 코드라고 믿어지지 않을 만큼 아주 효율적인 설계와 프로페셔널 수준의 테크닉을 가지고 있다. 10년 전의 코드라도 많은 테크닉을 배울 수 있으므로 꼭 분석해보길 추천한다.그 당시 작성된 옛날 버전(11년 전)은 ftp://ftp.gimp.org/pub /gimp/historical에서 발견할 수 있다.0.54.1 버전은 MOTIF 코드이며 그 후에 0.60이 GTK+로 개발하기 시작한 버전이다. GIMP 개발 역사에 있어서 두 버전은 의미 있는 버전으로 아직도 ftp 사이트에 저장돼 있다.왜 지금은 쓰지도 않는 과거의 코드를 분석하라고 하냐면 GTK+의 원 저자들이 MOTIF로 개발하다가 새롭게 툴킷 자체를 모두 새롭게 설계해서 만들어야 했던 문제점과 그 후에 만들어진 GTK+에서의 개선점을 모두 알 수 있기 때문이다. 그건 곧 GTK+의 장단점을 완벽하게 알 수 있다는 이야기다. 사실 그 정도 분석이 끝났다면 툴킷 라이브러리를 직접 고쳐서 패치 할 능력이 되므로 어떤 툴킷을 선택한다는 것이 의미가 없어지는 수준이다. 그러나 시간이 없는 직장인들은 그렇게 공부하면서 할 수 없기에 여유 있는 독자 분들만 한다는 가정 하에 생각을 해 본다.다음 연재에서 비교하여 설명할 Qt와 GTK+의 태생적인 가장 큰 차이점은 미국에서 만들어져서 시작된 라이브러리라는 것이다. 왜 그것이 가장 큰 차이점이라고 얘기하는가? 라고 묻는다면 그것은 미국의 문화이기 때문이다. 즉, GTK+는 스타벅스, 맥도날드, 헐리우드 영화와 같은 ‘아메리칸 스타일’이다.설계와 구조아메리칸 스타일이라고 한 것처럼 설계에서부터 그런 면모를 보이고 있다. 쉽게 얘기하자면 넓은 대륙, 풍부한 자원, 여유 있는 시간이 전제되는 미국인의 아메리칸 스타일식 생각이 설계의 기본 컨셉이다. 우선 GTK+로 만든 GNOME과 Qt로 만든 KDE 데스크톱 환경의 라이브러리 의존성을 보자면 왜 아메리칸 스타일이란 얘기가 나왔는지 한눈에 알 수 있을 것이다.어떤가? 아주 심플한 KDE와는 달리 서로 떨어지면 안 되는 관계의 족보를 보는 듯한 GNOME은 독자 여러분에게 익숙하지 않을 것이다. 이것이 쉽게 말해 미국인들과 유럽인들의 생각의 차이라고 하겠다. 유럽에서 만든 Qt나 KDE는 최대한 한곳에 집중시켜서 만들고 나눌 부분은 적게 가져가려 한다. 하지만 미국에서 시작된 GTK+와 GNOME은 일단 무조건 좋은 것을 가져다 붙이고 잘 돌아가면 효율과 최적화는 생각하지 않는다. GTK+가 추구하는 목표도 그렇다. GTK+에서 지원하는 기술이 이미 존재한다면 잘 돌아가는 라이브러리를 끌어다가 쓴다. 그렇게 해서 붙여지기 시작한 라이브러리들이 ATK, Cairo이다. ATK는 장치 지원 관련, Cairo는 벡터 렌더링 쪽이다.이 설계 컨셉은 오픈소스 프로젝트 진영에선 항상 이야기되며 미국인들이 주로 시작하고 참여하는 프로젝트들은 위에서 설명한 GTK+와 GNOME과 같은 설계와 스타일, 유럽 쪽은 Qt와 KDE 스타일이다. 리눅스도 유럽에서 시작한 프로젝트라 효율성이 우선되어진 모놀리딕 커널(monolithic kernel) 설계를 하고 있다. 아마 미국인들 같으면 무조건 최신기술을 써야 된다며 마이크로 커널로 설계했을 것이다. 그 예로 1990년에 시작하여 20년 가까이 0.2버전을 발표하며 제대로 릴리즈도 못하고 있는 리차드 스톨만의 hurd 커널이 있다. GNOME도 취지는 너무 현실을 외면한 면이 적잖아 있고 최신기술의 적용만을 우선시하는 편이라 해커들만의 데스크톱으로 치부되기도 한다. 그래도 서로 장단점을 가져가는 발전을 통해 선택의 폭이 넓어지고 경쟁으로 인한 코드의 빠른 개발과 코드 흡수는 유저 입장에서 매우 바람직한 일이다.다시 정리해보면 다음과 같다.GTK+는 모든 확장성을 처음 버전부터 염두해 두고 설계 되었다.GTK+는 기본적으로는 C로 만들어서 C++을 비롯하여 Obj-C, Pascal, Perl, php 등 현존하는 거의 대부분 언어에 대한 바인딩을 지원한다. 또한, 2.X버전에 와서는 장치접근을 위한 부분을 ATK로 나눠서 입력과 출력에 대한 부분을 다르게 할 수 있도록 하는 부분도 추가되었다. 시스템 하부의 의존적인 타입과 함수는 GLIB로 디스플레이의 의존적인 부분은 GDK로 그리고 가장 위의 ToolKit 레벨은 GTK로 분리한 후 멀티 플랫폼을 생각하여 작성되었다. 이 부분이 고려한 결과로 영향을 받는 건 임베디드 플랫폼도 해당된다. 이 부분은 장점이 되지만 역시 단점도 된다. 커스터마이징이 가능하여 필요 없는 모듈과 기능은 빼고 컴파일 하면 사이즈를 줄이고 최적화가 가능하다. 하지만 그러기 위해서는 연관 부분에 대하여 기술적으로 알아야 되는 부분이 많고 의존되는 버전과 타 라이브러리 간의 호환성 차이가 발생한다.풍부한 자원을 지원하기 위해 가능한 모든 최신 기술은 다 집어넣고 있다.ATK를 비롯하여 현재는 Cairo 라이브러리로 렌더링엔진을 바꿔서 벡터 이미지인 SVG와 OpenGL을 처리하고 있다. 그리고 추가적인 다국어 입력기를 위하여 자체적인 입력방식도 지원한다. 이렇듯 현재 존재하여 지원 가능한 최신 기술은 다 들어가 있다. 하지만, 그만큼 복잡하며 의존되는 라이브러리가 많이 엮여져 있기에 이것을 파악하려는 사람은 무척 어려움을 느낀다. 또한 GTK+의 기본 방침은 새로운 기능을 위하여 하위 호환성은 생각하지 않기에 0.1 마이너 버전이 바뀔 때 마다 하위 호환성이 크게 변하여 코드레벨부터 호환성이 보장되지 않고 바이너리 호환도 간혹 달라진다. 그 얘기는 그만큼 많이 기능을 추가하고 바꿨다는 말이지만 함수 이름과 타입도 수시로 바뀌어서 그에 따른 엄청난 deprecate(쓰지 말것을 권고하는) 함수를 새로운 코드로 고쳐야 되는 버거움이 프로그래머에게 남겨진다. 뒤에 설명할 Qt는 호환성을 마이너 버전은 물론 메이저 릴리즈가 바뀌어도 최대한 유지하는 정책을 펴고 있다. 따라서 지금 쓰는 Qt4에서도 과거 Qt3의 코드를 추가적으로 호환할 수 있도록 지원하고 있다.빠른 개발보다는 여유 있게 시간을 갖고 완벽하게 커스터마이징하여 개발하는 것에 적합하게 설계되어 있다.GTK+는 위젯의 코드를 최대한 분리시켜 놓은 구조이다. 예를 들어 메시지 창 하나도 최소한의 코드로 구성되어 있으며 나머지는 직접 자기 마음대로 구성, 디자인하여 쓰는 식으로 모든 부분이 최소한의 코드로 분리되어 있다. 따라서, 사용하는 프로그래머가 직접 원하는 방식으로 합쳐서 구미에 맞게 쓰도록 만들어져 있으며 커스터마이징 하기에 아주 좋다. 할 수 있는 거의 모든 부분을 수정할 수 있고 만들어 넣을 수 있기 때문이다. 하지만, 대부분의 경우 그 정도까지는 필요 없고 빠른 구현이 우선시 될 때는 할 일의 양이 많아진다. 그걸 커버하려면 상위의 GNOME 라이브러리를 쓰면 되지만, 그것은 데스크톱 애플리케이션에서나 해당되므로 임베디드 플랫폼에서는 닭 잡는데 소 잡는 칼 쓰는 격이 된다. 몇 십 메가의 GNOME 라이브러리를 가져오면 배보다 배꼽이 더 커질 수 있으므로 해당사항이 아닐 것이다.위의 얘기를 보면 왜 GTK+가 아메리칸 스타일인지 알 수 있을 것이다. 이제 다음 연재에서 얘기할 Qt는 그 반대라고 생각하면 되겠다.실제 코드항상 지루하게 Hello World!를 출력하는 예제는 쓰지 않겠다. 다음은 메뉴와 버튼으로 구성된 내부의 대표적인 GTK 소스 코드 내의 공식적인 예제이다. 이 정도 코드만으로도 GTK+ 애플리케이션의 기본 골격은 다 갖고 있다고 볼 수 있다. 아무리 복잡한 애플리케이션도 여기서 확장해 나가는 것이다.#include #include static gboolean button_press (GtkWidget *, GdkEvent *);static void menuitem_response (gchar *);int main( int argc, char *argv[] ){ GtkWidget *window; GtkWidget *menu; GtkWidget *menu_bar; GtkWidget *root_menu; GtkWidget *menu_items; GtkWidget *vbox; GtkWidget *button; char buf[128]; int i; gtk_init (&argc, &argv); /* create a new window */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_size_request (GTK_WIDGET (window),200, 100); gtk_window_set_title (GTK_WINDOW (window), “GTKMenu Test”); g_signal_connect (G_OBJECT (window), “delete_event”, G_CALLBACK (gtk_main_quit), NULL); /* Init the menu-widget, and remember -- never * gtk_show_widget() the menu widget!! * This is the menu that holds the menu items, the one that * will pop up when you click on the “Root Menu” in the app */ menu = gtk_menu_new (); /* Next we make a little loop that makes three menu-entries for “test-menu”. * Notice the call to gtk_menu_shell_append. Here we are adding a list of * menu items to our menu. Normally, we’d also catch the “clicked” * signal on each of the menu items and setup a callback for it, * but it’s omitted here to save space. */ for (i = 0; i < 3; i++) { /* Copy the names to the buf. */ sprintf (buf, “Test-undermenu - %d”, i); /* Create a new menu-item with a name... */ menu_items = gtk_menu_item_new_with_label (buf); /* ...and add it to the menu. */ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_items); /* Do something interesting when the menuitem is selected */ g_signal_connect_swapped (G_OBJECT (menu_items), “activate”, G_CALLBACK (menuitem_response), (gpointer) g_strdup (buf)); /* Show the widget */ gtk_widget_show (menu_items); } /* This is the root menu, and will be the label * displayed on the menu bar. There won’t be a signalhandler attached, * as it only pops up the rest of the menu whenpressed. */ root_menu = gtk_menu_item_new_with_label (“Root Menu”); gtk_widget_show (root_menu); /* Now we specify that we want our newly created “menu” to be the menu * for the “root menu” */ gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu); /* A vbox to put a menu and a button in: */ vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); /* Create a menu-bar to hold the menus and add it to our main window */ menu_bar = gtk_menu_bar_new (); gtk_box_pack_start (GTK_BOX (vbox), menu_bar, FALSE, FALSE, 2); gtk_widget_show (menu_bar); /* Create a button to which to attach menu as a popup */ button = gtk_button_new_with_label (“press me”); g_signal_connect_swapped (G_OBJECT (button),“event”, G_CALLBACK (button_press), G_OBJECT (menu)); gtk_box_pack_end (GTK_BOX (vbox), button, TRUE,TRUE, 2); gtk_widget_show (button); /* And finally we append the menu-item to the menu-bar -- this is the * “root” menu-item I have been raving about =) */ gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), root_menu); /* always display the window as the last step so it all splashes on * the screen at once. */ gtk_widget_show (window); gtk_main (); return 0;}/* Respond to a button-press by posting a menu passed in as widget. * * Note that the “widget” argument is the menu being posted, NOT * the button that was pressed. */static gboolean button_press( GtkWidget *widget, GdkEvent *event ){ if (event->type == GDK_BUTTON_PRESS) { GdkEventButton *bevent = (GdkEventButton *) event; gtk_menu_popup (GTK_MENU (widget), NULL, NULL, NULL, NULL, bevent->button, bevent->time); /* Tell calling code that we have handled this event; the buck * stops here. */ return TRUE; } /* Tell calling code that we have not handled this event; pass it on. */ return FALSE;}/* Print a string when a menu item is selected */static void menuitem_response( gchar *string ){ printf (“%sn”, string);}#include #include GtkWidget를 사용하기 위해 기본적으로 gtk.h 헤더를 포함한다. 각 위젯마다 헤더가 따로 존재하여 해당하는 헤더만 포함해도 되지만 gtk.h는 모든 위젯에 해당하는 헤더를 포함하므로 이것 하나만 포함하면 다른 헤더를 포함하는 것에 대해 신경 안 써도 된다. 하지만, 불필요한 헤더 코드를 다 포함하게 되면 증가하는 컴파일 속도와 바이너리 코드의 사이즈는 감수해야 한다. 따라서 프로젝트가 커지게 되면 사용한 각각의 헤더 파일만을 포함하도록 최적화 한다.static gboolean button_press (GtkWidget *, GdkEvent *);static void menuitem_response (gchar *);이 파일 안에서만 쓰려고 static 한 것을 빼면 그다지 특징은 없는 전형적인 C 함수 프로토 타입 선언이다. 하지만 이 두 함수 모두 콜백 함수로 해당 이벤트에 따라 불러지기에 리턴 값과 들어갈 변수 아규먼트의 타입은 이미 정해져 있으며 바꿔서 선언할 수 없다. GtkWidget *window; GtkWidget *menu; GtkWidget *menu_bar; GtkWidget *root_menu; GtkWidget *menu_items; GtkWidget *vbox; GtkWidget *button;GtkWidget으로 만들 포인터 변수들이다. 주목해야 될 부분이 바로 window든 menu_bar로 쓸 위젯이든 모두 똑같은 포인터 타입이라는 것이다. C에서는 C++에서처럼 dynamic_cast나 static_cast와 같은 타입 안정성을 보장하는 캐스팅 연산자가 따로 존재하지 않으므로 GTK+에서는 내부적으로 직접 타입 캐스팅 매크로를 불러 써야 한다. 만약 타입이 달라서 캐스팅이 안 될 경우는 에러가 발생하기에 주의해야 한다. 이 부분은 C를 C++처럼 쓰려는 프로그래머 입장에서는 어렵고 귀찮아질 수 있는 부분이다.gtk_init (&argc, &argv);....gtk_widget_show(window);gtk_main();gtk 관련 초기화는 메인 함수에서 아규먼트들을 받아 시작한다. 여기서는 X-Window나 뒤에 오는 기본적인 인자들의 옵션을 처리하고 이상 없는지 확인하는 작업을 거친다. 그리고 원하는 코드를 처리하고 gtk_widget_show하여 화면에 그린 후에 마지막으로 이벤트 처리를 위하여 gtk_main()으로 무한 루프에 들어간다. window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_size_request (GTK_WIDGET (window), 200, 100); gtk_window_set_title (GTK_WINDOW (window), “GTK Menu Test”);위의 코드에서도 말한 것처럼 주목해야 될 타입 캐스팅 부분이다. 항상 해당 위젯을 쓸 때마다 GTK_WIDGET과 GTK_ WINDOW로 원하는 스타일을 캐스팅하고 있다. C++의 경우 상속받으면 자동으로 상위 클래스는 모두 내부적으로 public과 private, protected로 나눠서 들어가져서 해당멤버에 접근만 하면 되기 때문에, 이 부분은 기존의 C++ 프로그래머들에겐 이해되지 않는 부분이라고 하겠다. g_signal_connect (G_OBJECT (window), “delete_event”, G_CALLBACK (gtk_main_quit), NULL);이 부분은 바로 GUI 프로그래밍의 핵심인 이벤트 처리 부분이다. 즉 유저의 마우스나 키보드 등 입력 이벤트를 감지하여 해당 콜백 함수에 연결하는 것이다.GUI 프로그래밍은 사용자의 입력에 따라 프로그램이 흘러가는데 GUI 환경에서는 마우스나 키보드, 터치스크린, 타블렛 등을 통한 많은 입력방식이 있다. 그런데 그 입력에 해당하는 이벤트가 언제 어디서 나타날지 모르기에 우리는 무한루프 안에서 항상 해당경우를 계속 체크하며 기다려야 한다. GTK에서의 이벤트 방식도 모두 최신의 설계 같지만 아쉽게도 MOTIF나 MFC의 메시지 맵과 같은 전통적인 콜백 방식이다. 잘 모르는 독자들을 위해 설명하자면 해당 함수를 등록한 후에 다시 뒤에서 부른다고 생각하면 된다.위의 코드에서 보듯이 미리 해당 이벤트에 대하여 부를 함수를 등록해서 정해놓고 해당하는 이벤트가 GUI 무한 루프를 돌면서 해당된다면, 그 함수 포인터를 부르고 다시 루프로 돌아오는 것이다. 이런 함수 포인터 호출 방식은 지금까지 거의 모든 GUI 라이브러리에서 쓰이고 있는 구조로 구현은 약간 달라도 순서는 다 똑같다.혹시 함수 포인터에 이런 것이 가능하냐고 궁금해 할 독자들을 위해 간단히 추가 설명하자면, C에는 포인터가 있다는 것을 알 것이다. 그 중 함수 포인터로 이 모든 것이 가능해지며, 미리 해당 위젯의 내부엔 해당 이벤트에 이름으로 함수 포인터 변수가 존재한다. 그리고 그 포인터 변수에 해당 이벤트를 처리할 함수를 만든 후 등록시키는 것이다. 여기서 등록이란 그 포인터 변수에다가 만들어진 해당 함수 어드레스를 넘기는 것이다. 컴퓨터를 내부적인 어셈코드로 보면 함수 어드레스로 JUMP한다고 할 수 있다.이 방식의 장점은 별 문제가 없이 빠르고 안정적인 점이다. 단점은 미리 해당 이벤트를 등록하여야 되기 때문에 새로운 이벤트를 만들 때 유연하게 대처하지 못하고 이벤트를 받는 쪽과 보내는 쪽이 서로 반드시 미리 등록하고 알아야 된다. 따라서 OOP의 이론 중 중요한 점인 은닉성(Information Hiding) 즉, GUI 객체 간 분리가 안 된다는 문제점이 있다. 하지만 이미 오랜 기간 써왔고 그에 따른 많은 레퍼런스가 쌓였기에 이제 더 이상 문제라고 하기엔 어렵다. 하지만 Qt는 이런 문제를 해결할 방법을 구현하였다.C언어로 OOP?GTK+의 가장 큰 차이점으로 기존의 다른 GUI 라이브러리 프로그래머들이 가장 이해하기 어려워하는 부분이다. GTK+는 기본적으로 C언어로 설계되었고 개발된다고 얘기했었다. 하지만 OOP로 설계되어 다른 언어로의 바인딩도 지원하며 다양한 언어로도 개발할 수 있다. OOP 관련 책을 한 권이라도 읽어본 독자라면 뭔가 앞뒤가 안 맞는다고 느낄 것이다. OOP는 C++, JAVA와 같은 OOP를 지원하게 설계된 언어에서만 가능하다고 대부분의 책에 설명되어 있다.그러나 C 언어가 OOP를 지원을 안 한다는 것이지 OOP 지원을 못한다는 것은 아니다. 즉, OOP로 보이는 것처럼 프로그래머의 테크닉에 따라 OOP적인 개발은 할 수 있다. 사실 C++이란 언어도 내부적으로 보면 기본적으로 C언어에서 이런 기능을 언어 자체적으로 지원하는 것뿐이다. 쉽게 쓰게 해놨냐 아니면 어렵게 쓰게 해놨냐의 차이지 OOP가 되고 안 되는 문제가 아닌 것이다. 사실 포인터만 지원되면 OOP는 모두 구현할 수 있다. 필자도 대단하다고 느낀 부분인데, C언어로 포인터와 스트럭처를 구성하여 클래스를 똑같이 흉내내고 상속과 같은 OOP의 대표적인 속성(상속성, 다형성, 은닉성)을 구현한 대표적인 라이브러리가 바로 GTK+이다. 그런 면에서 GTK+는 OOP 관련 스터디용으로도 최적이라 하겠다.그렇다고 GTK+를 앞에서 말한 것처럼 C로만 개발할 수 있는 건 아니다. gtkmm이란 C++바인딩이 이미 활발히 개발되어서 쓰이고 있고, 대표적인 스크립트 OOP언어인 python부터 Perl을 이용하면 OOP 문법을 잘 사용해서 보다 깔끔하게 코딩할 수 있다. 하지만 참고할 코드가 대부분이 C로 작성된 것이라 다른 언어로 개발한다는 것은 시행착오를 겪어야 하기에 좀 힘들다고 하겠다. 그리고 임베디드 플랫폼에서 사용하기 위해서는 C나 C++을 써야 하고 나머지 스크립트 언어들은 고려 대상이 아니다.클래스스트럭처 struct에서 포인터로 상속을 구현하고 함수 포인터로 클래스화 시킨다. 다음은 GTK+에서 C로 클래스를 만든 예이다./* vim: set ai et ts=4 sw=4: */#ifndef __sidebar_h__#define __sidebar_h__#include “sidebar-item.h”#define SIDEBAR(obj) GTK_CHECK_CAST(obj, sidebar_get_type(), Sidebar)#define SIDEBAR_CLASS(klass) GTK_CHECK_CLASS_CAST(klass, sidebar_get_type(), SidebarClass)#define IS_SIDEBAR(obj) GTK_CHECK_TYPE(obj, sidebar_get_type())typedef struct _Sidebar { GtkVButtonBox parent; GList* items; GSList* items_rbg; // radio button group gchar* filename;} Sidebar;typedef struct _SidebarClass { GtkVButtonBoxClass parent_class;} SidebarClass;GtkType sidebar_get_type(void);GtkWidget* sidebar_new(void);void sidebar_read(Sidebar* sidebar);void sidebar_write(Sidebar* sidebar);void sidebar_add_item(Sidebar* sidebar, SidebarItem* item);void sidebar_remove_item(Sidebar* sidebar, SidebarItem* item);SidebarItem* sidebar_get_active_item(Sidebar* sidebar);void sidebar_refresh(Sidebar* sidebar);void sidebar_show_dialog(Sidebar* sidebar);#endif /*sidebar.h*/물론 C++의 클래스와 모양새는 확연히 틀리다. 하지만 부분적으로 잘 나눠서 보면 똑같다는 것을 알 수 있다.#define SIDEBAR(obj) GTK_CHECK_CAST(obj, sidebar_get_type(), Sidebar)#define SIDEBAR_CLASS(klass) GTK_CHECK_ CLASS_CAST(klass, sidebar_get_type(), SidebarClass)#define IS_SIDEBAR(obj) GTK_CHECK_TYPE(obj, sidebar_get_type())오브젝트 타입 캐스팅을 위한 매크로이다. 쉽게 말하자면 C++에서 static_cast와 dynamic_cast에 해당한다고 보면 되겠다.typedef struct _Sidebar { GtkVButtonBox parent; GList* items; GSList* items_rbg; // radio button group gchar* filename;} Sidebar;class 문법이 없기에 struct 안에 상속할 parent를 넣고 멤버 변수들을 넣었다. 물론 private와 public, protected 같은 속성은 직접 못 주지만 그건 자신이 직접 쓰면서 구별하면 되기에 큰 차이는 없다고 하겠다. parent는 즉, 상속받은 상위 클래스를 뜻하며 나머지 멤버변수를 포함하고 있다.typedef struct _SidebarClass { GtkVButtonBoxClass parent_class;} SidebarClass;GTK+내부에서 쓰이는 클래스 관리를 위한 클래스 struct이다.GtkType sidebar_get_type(void);GtkWidget* sidebar_new(void);void sidebar_read(Sidebar* sidebar);void sidebar_write(Sidebar* sidebar);void sidebar_add_item(Sidebar* sidebar, SidebarItem* item);void sidebar_remove_item(Sidebar* sidebar, SidebarItem* item);SidebarItem* sidebar_get_active_item(Sidebar* sidebar);void sidebar_refresh(Sidebar* sidebar);void sidebar_show_dialog(Sidebar* sidebar);해당 함수들이 클래스라면 안에 있을텐데 스트럭처 이기에 밖으로 나와 있다. private, public, protected같은 구별은 없지만 아마 C++같으면 이렇게 되었을 것이다.class Sidebar : public GtkVButtonBox{ public: Sidebar(); ~Sidebar(); private: void read(); void write(); public: void add_item(SidebarItem* item); void remove_item(SidebarItem* item); SidebarItem* get_active_item(); void refresh(); void show_dialog();};분명 C++에서 멤버 함수 참조로 바로 -> 연산자만으로 쓸 수 있다는 건 코드 길이가 짧고 보기가 좋을 수도 있다. 하지만 OOP 관점에서는 표현의 차이이지 구현의 차이가 아니라는 점을 알아야 한다.mono몇 년 전부터 필자가 개인적으로 관심 있게 보고 있는 프로젝트인데, 이 프로젝트는 임베디드 플랫폼의 GUI를 위해 만들어진 프로젝트는 아니지만 지금도 임베디드 GUI 개발에 유용하게 쓰일 수 있다. 당장은 아니더라도 미래 가치가 있기 때문에 상당 부분을 할애해서 소개하겠다. 간혹 타 잡지와 칼럼에 소개가 되고는 있지만 그 분량이 적고 대부분의 독자 분들에겐 접할 기회가 없었을 것이다.mono는 스페인어로 원숭이란 뜻이며 프로젝트 리더인 미구엘이 원숭이를 닮아서 그런지 몰라도 이 사람이 하는 프로젝트에는 원숭이와 관련된 이름이 많다. mono의 목표는 마이크로소프트사의 .NET 프레임워크와 C#의 오픈소스 구현이다. 여기서 소개하는 이유는 GTK+가 C#으로도 구현되었기 때문이다.독자 분들을 위해 시작된 경위를 설명하자면 마이크로소프트사는 썬이 출시한 자바를 처음엔 그다지 신경 안 쓰며 Visual J++로도 지원하며 상생(?)하는 전략을 가져갔으나 시간이 갈수록 자사의 전략제품과 경쟁하며 시장을 점유해버리는 결과가 나타나 큰 수익 악화를 가져오게 된다. 그래서 이제는 자바를 경쟁자로 간주하고 윈도우내의 익스플로러 지원에서 빼버리게 되어 결국 경쟁제품을 만들 필요를 느낀다. 그것이 바로 .NET 프레임워크이며 언어는 C#이다..NET 프레임워크와 언어는 별개이다. .NET 프레임워크는 윈도우 개발을 위하여 WinForm과 WinFX 등 여러 기술이 올라가 있는 구현 환경이며 우리가 주의 깊게 봐야할 부분은 그런 윈도우 의존과 관련 없는 C#이다. C#는 친절하게도 마이크로소프트사가 ECMA에 표준으로 공개해서 제출하여 윈도우 이외의 플랫폼에서도 스펙만 맞춰서 구현하여 쓸 수 있다.이제 임베디드 플랫폼에서의 개발은 역사적으로 어셈에서 C로 그리고 현재는 C++로 변화하는 중이며 해외는 이미 C++로 거의 전환이 되었다. 따라서 그 다음이 무엇이 될 것인가에 초점이 맞춰지고 있다. 그것이 바로 데스크톱 환경에서 문제와 똑같은 자바와 C#의 경쟁이다.자바의 처음 목표는 임베디드 장비를 위하여 설계되었다. 원래 가전제품 안에 들어가서 제어하기 위한 용도였는데 현재 그 목표보다는 기업용 애플리케이션 시장을 점령하는데 성공하였다. 전 세계적으로도 자바가 j2ee를 앞세워 미들웨어와 웹의 통합으로 대부분의 개발을 통일했다고 봐도 된다. 때문에 자바는 임베디드 차세대 개발 언어로서 색이 바랜 상태고 남은 것은 C#이 된다. 지금도 어느 정도 하드웨어 스펙이 충분한 임베디드 플랫폼(IPTV, 셋톱박스, DVR 등)의 경우는 써도 될 정도의 퍼포먼스를 보여준다. GNOME 프로젝트의 창립 리더이자 현재 mono 프로젝트로 .NET 프레임워크와 C#을 오픈소스 프로젝트로 구현하려는 멕시코 출신 해커 미구엘 드 이카자는 다음과 같이 말하고 mono 프로젝트를 시작했다.‘아직도 C로 대규모 애플리케이션 개발 프로젝트를 진행하는 건 적합하지 않다. 마치 시대에 뒤떨어지게 어셈을 아직도 C보다 낫다고 얘기하는 것과 같다. 골치 아픈 메모리 문제는 이제 더 이상 상위 레벨의 프로그래머가 고려해야 하는 문제로 남아 있어서는 안 된다.’필자는 C#이 자바보다 좀 더 임베디드 플랫폼에 맞다고 생각하는데 그 이유는 다음과 같다.ECMA 표준으로 구현자바는 많이 쓰여서 표준으로 인식되지만 국제 표준은 아니며, 썬사에서 GPL로 오픈한다고 하지만 아직 소유권은 갖고 있다. 그래서 누가 따로 자바언어 스펙을 구현하지 못한다. ECMA는 유럽 컴퓨터 표준 협회로 미국의 ANSI와 비슷한 단체인데 국제적으로 보면 ANSI같은 표준은 미국에서만 영향력이 있고 ECMA가 국가로 보나 크기로 보나 영향력이 강하다고 하겠다.이미 오픈소스 프레임워크와 언어 레벨에서 검증되어 구현현재 1.0버전이 릴리즈 된지 오래 되었으며 1.2버전이 출시돼 있다. 리눅스를 비롯하여 FreeBSD, Solaris, MacOS는 물론 윈도우에서도 돌아가며 Monodevelop란 통합개발환경까지 나와 있다.자바보다 적은 리소스와 시스템 의존성 불필요자바는 사실 VM으로 이론적으로는 다 똑같이 돌아간다 하지만, 시스템 의존으로 인해 문제되는 부분이 상당히 많아 현실적으로 멀티 플랫폼의 역할을 제대로 못 보여준다. 하지만, mono로 구현된 C#에서는 거의 완벽하게 JIT로 멀티 플랫폼이 돌아간다. 둘의 설계와 실행방식이 좀 틀리기에 직접 비교는 무리지만 같은 시스템에서 C#가 좀 더 빠르다.윈도우의 아웃룩을 똑같이 구현하기 위해 개발한 에볼루션은 1.0버전 릴리즈를 하기 위한 개발 기간이 총 2년이 걸렸다. 물론 마이크로소프트사가 수천 명의 프로그래머를 보유하고 있는 데 비해 에볼루션 개발팀은 몇 명에 지나지 않는다. 하지만 미구엘은 그 개발기간 중에 실제 설계와 구현에 소비되는 시간보다 C언어 자체의 메모리 관리와 디버깅에 소모되는 시간이 많았다고 말한다. 그래서 그는 문제점은 프레임워크와 언어 차원에서 해결하도록 맡기고 프로그래머는 구현에만 신경쓸 수 있도록 자바와 같은 C#과 GTK# 을 구현하였다. 그의 말에 따르면 C#으로 C보다 10배는 빠른 개발이 가능하다고 한다. 그럼 24개월/10= 2.4개월이다. 이정도의 개발기간 단축은 하드웨어 비용의 증가를 충분히 상쇄하고도 남을 것이다. 개발 주기는 대부분 처음에는 완만한 증가곡선을 그리다가 프로젝트 마무리로 갈수록 디버깅 시간이 엄청나게 증가된다. C로 개발된 코드는 대부분이 메모리 관리 문제에서 최적화와 누수 때문에 시간을 소모하게 된다.임베디드 개발 언어가 과거 어셈블리에서 C로 변화되었고 현재는 C++로 넘어가고 있는 추세이다. 해외는 이미 C++로 거의 전환되었으며 C#이든 자바이든 곧 다음 세대 언어가 주류가 될 것이다.이미 대표적인 임베디드 프로세서인 ARM 플랫폼을 이용한 노키아의 리눅스 탑재 스마트폰 Nokia 770에서 차세대 언어를 개발 패키지로 사용하고 있는 것을 보면 이용을 위한 환경은 충분하다 하겠다.GPE:The GPE Palmtop EnvironmentGPE는 상당히 잘 짜여지고 임베디드 기기에 맞게 구성된 프레임워크로 비교하자면 Qt로 만든 Qtopia에 해당한다. Qtopia와 아주 흡사하게 만들어서 사실 베낀 거 같은 느낌이 들 정도다. 기본적으로는 스마트폰과 같은 휴대전화 UI에 맞게 구성되어 있으나 꼭 휴대전화에서만 쓰게 만들어 진 것은 아니다. 입력장치와 출력장치가 제한된 상황에서 애플리케이션을 이용하고 통합할 수 있는 환경을 제공하는 프레임워크라 하겠다.회사에서 신입과 경력 개발자 간의 개발 속도 차이는 미리 참고할 레퍼런스 자료를 누가 더 알고 있느냐 하는 것에서 발생한다. 당연히 처음 접하고 해보는 사람은 어디에 뭐가 있고 무엇을 가져다가 어떻게 하면 되는지 알 수가 없기 때문에, 자료 검색에도 시간이 걸리고 그것을 찾아 검증하고 테스트하고 자신이 이해하는 데에도 시간이 걸린다. 하지만 경력자는 이미 그런 작업을 몇 번 해본 사람들이기에 그 시간을 절약할 수 있는 것이다. 여기서 GPE를 소개하는 것도 그런 의미이다.GUI 애플리케이션이 적용되는 대표적인 임베디드 기기는 PMP, Car Navigation, PDA, 휴대전화 등인데 이들의 공통점이 있다. 애플리케이션이 딱 하나만 적용되지 않는다는 것이다. 최소 두 개 이상이 선택되어 유저가 상황에 따라 전환하여 쓰며 동시에 보여지며 구동되기도 한다. 또한 입력장치가 키보드나 마우스가 아니고 터치스크린 혹은 외부 버튼 몇 개로 조작해야 한다.이런 부분을 모두 개발하기엔 너무 할 일이 많아진다. 누구나 그렇듯 처음부터 모든 걸 다시 만든다는 것은 빠르게 변화하는 현실에서는 시간 낭비이다. 일단 잘 짜여진 코드를 참고하여 문제를 해결하는 것이 가장 시행착오를 줄일 수 있는 방법이다.그림 8은 GPE 프레임워크의 의존성 트리 구조이다.상당히 복잡하며 필요한 패키지도 많고 여러 가지 기술이 쓰였음을 알 수 있다. 메시지 교환은 dbus부터 sqlite로 데이터베이스 관리를 하고 GStreamer로 미디어 관련 처리를 맡기며 Cairo로 SVG 벡터 이미지 처리를 하고 각 위젯을 libgpe로 구성하여 서로 엮여져 있다. 이 안의 패키지만으로도 사실상 임베디드 플랫폼에서의 GUI가 필요로 하는 모든 것을 처리하고 원하는 코드를 찾아낼 수 있다. 하지만 그림 8의 거미줄과 같은 의존성을 잘라내고 따로 필요한 부분만을 가져다가 쓴다는 것 자체가 상당히 어려운 일이다. 따라서 여기서는 대표적으로 어느 부분을 어떻게 가져다가 쓰고 참고하면 되는지를 알아보도록 하자.가상 키보드스크린 키보드라고도 하며 거의 대부분의 임베디드 기기에서 키보드를 처리할 때 스크린 상에서 키보드 모양을 표시하고 터치에서 클릭하게 하여 처리한다. 그림 7의 아래 왼쪽에 보면 xkbd라고 있다. X11 환경 즉, X-Window 상에서 대표적인 X11 Event 처리 프로그램이다. X11을 안 띄우고 프레임버퍼로 직접 그리는 경우에는 직접 쓰지 못하지만 어떻게 내부적으로 설계가 될 수 있는지 참고할 수 있다.애플리케이션 간의 통신임베디드 플랫폼에서의 GUI는 최근 여러 가지 프로그램을 동시에 띄우고 지원하는 추세이다. 예를 들면 냉장고의 컨트롤 GUI 애플리케이션에 냉장고의 상태와 제어만을 하는 프로그램을 넣을 수도 있지만 요즘 냉장고에는 LCD도 크게 집어넣고 거기서 직접 웹브라우징을 할 수 있게 만든다. 모든 가전기기와 전자제품이 인터넷에 연결되는 것이 미래의 추세임에는 확실하다. 그렇기에 서로 동시에 띄워진 애플리케이션이 웹브라우저면 웹브라우저, 컨트롤 프로그램, 미디어 플레이어 간 통신이 가능하여야 한다. 현재 유저가 선택하고 활성화된 프로그램이 뭔지 알아내고 hide시키거나 show하는데 있어 가장 쉽게 생각할 수 있는 방법이 TCP/IP 기반의 소켓 통신이 될 수도 있고, IPC를 이용한 방법일수도 있다. 하지만 그건 로컬 상에서만 통신하는 데에 있어서 너무 오버헤드가 크고 적당치 않은 방법이라고 하겠다. 보통 이런 경우는 이미 dbus라 불리는 표준화된 방법이 있다.SVG, OpenGL 지원이젠 3D 지원은 어느 플랫폼에서나 선택이 아닌 필수가 되어버렸다. 휴대전화의 겨우 2인치 정도의 LCD에서도 3D를 구현하려고 3D 가속 칩셋이 들어가는 현실이다(사실 그게 3D로 느껴지는지 의문이다). SVG와 같은 벡터이미지 처리도 많이 쓰이고 있다. 우리가 많이 접하고 있는 플래시와 같은 것인데, 플래시는 특정회사의 제품이라 그것을 표준으로 삼고 지원할 수는 없기 때문에 SVG를 업체들이 표준으로 지정하고 많이 내세우고 있는 실정이다. 그런데 SVG 이미지 렌더링 자체를 OpenGL로 가속을 하기 때문에 3D 지원이 들어가는 것이다. GTK+ 같은 경우는 2.6 버전 이후로 Cairo 라이브러리를 백 엔드 렌더링 엔진으로 사용하여 SVG와 OpenGL을 지원하고 있다.미디어 플레이어동영상 파일(avi, mpg, wmv, asf etc..)과 음악 파일(mp3, ogg, etc..)을 지원하는 것은 이젠 아주 당연한 것이다. 따라서 GTK+에서 이용할 수 있는 통합 프레임워크가 존재한다. 바로 GStreamer이라는 프레임워크이다. 물론 이것을 이용 안 하고 다른 라이브러리를 직접 이용해도 되고 직접 만들어 써도 된다. 하지만 우리는 그렇게까지 시간이 없으므로 GTK+에 맞고 가장 최적화된 것을 이용하는 것이 효율적이다.어디에, 어떤 용도에 쓸까?위의 소개와 몇 가지 예를 보며 정리하자면 GTK+가 적합한 용도와 개발조건은 다음과 같다.C++은 오버헤드가 크고 라이브러리 의존성으로 쓸 수 없다.가장 주요한 이유라 할 수 있다. 분명히 C++은 C로 만든 바이너리 보다 실행 속도가 느리고 링크와 C++ 함수 네임 맹글링으로 훨씬 많은 오브젝트 코드의 증가 및 실행 바이너리 파일사이즈 자체 크기가 크다. 그러나 우리는 현재 엄청난 하드웨어 스펙의 발전으로 인하여 웬만한 PC 플랫폼에서는 그런 차이를 거의 느끼지 못한다. 그 정도 속도는 상쇄하고도 남을 만큼 CPU 속도가 빠르고 메모리에 스왑과 캐쉬로 사용할 라이브러리를 충분히 다 올려놓고 써도 여유 있게 남기 때문이다. 현재 대부분 리눅스 배포판에서는 prelink 처리로 미리 링크 어드레스 재배치를 끝낸 후에 실행시키기 때문에 그 차이를 상쇄하고도 남는 것이다. 그러나 임베디드 플랫폼에서는 다르다. 메가 단위의 libstdc++ 라이브러리와 그에 딸려오는 Qt 라이브러리 및 추가적인 라이브러리들은 추가로 플래시 메모리 칩셋이 하나 더 붙느냐 마느냐의 차이를 갖고 올 수도 있다. 그건 곧 휴대전화과 같이 기본 수 십만 대의 물량을 가진 제품에는 엄청난 원가 상승이며 회사차원에서 경영진이 결정할 문제로 넘어간다. 그리고 요즘에는 임베디드 CPU들도 상당히 빨라져서 200MHz 이상을 주로 쓰므로 아주 드물지만 100MHz 이하의 CPU를 사용하는 스펙에서는 처음 실행 파일을 로딩 하는 속도 자체가 몸으로 느껴질 만큼 몇 초까지도 차이가 난다. 하지만 그 시간 차이가 아주 치명적인 문제가 되는 장비인 경우에는 쓸 수가 없는 것이다. 예를 들자면 의료기기 장비 같은 경우 환자가 위급하여 빨리 장비를 작동시켜 상태를 보는데 몇 초차이로 생사가 갈릴 수도 있다. 이런 경우는 GUI의 화려함이 중요한 게 아니라 빠른 실행과 정확도가 우선시 되어야 할 것이다.비용적인 측면으로 상업 벤더 제품을 라이선스 비용을 물며 쓸 수 없다.필자도 많은 업체들과 컨설팅과 개발을 하며 느끼는 면인데, 회사에서는 비용 지출을 가장 민감하게 생각한다. 그런데 아이러니컬하게도 경영진들은 하드웨어에 대한 지출비용은 크다고 생각 안하고 아낌없이 투자하지만 소프트웨어 라이선스 비용은 조금만 지불해도 엄청나게 큰 것처럼 느끼며 어떻게 해서든 줄이려 애쓴다. 하드웨어는 돈으로 사면 눈으로 그 효과가 보이고 보이는 물건으로 있으니 심리적으로 만족스럽지만 소프트웨어는 눈으로 보이지 않는데 돈이 드니 불만족스럽게 생각할 것으로 예상된다. 하지만, 이것은 아주 잘못된 시각으로 하드웨어는 단순히 성능을 높여 해결하는 문제를 소프트웨어로는 그 비용의 1/10로도 해결할 수도 있으며 남는 돈을 개발자의 월급으로 추가해서 더 경력 있고 능력 있는 사람을 쓴다면 몇 배의 효과로 해결할 수도 있는 것이다. 허나 대부분의 국내 회사들은 주로 반대로 하고 있다. 사람에 대한 투자는 최소한으로 가져가며 소프트웨어 구입은 그 다음으로 최소이며 하드웨어는 가장 지출을 많이 하는 쪽이다. 필자 개인적으로는 하드웨어 비용을 늘릴 바엔 1차적으로 소프트웨어를 통한 해결을 하려는 것이 회사 입장에서 훨씬 이득이라고 생각한다. 하지만 현실은 틀리고 경영진의 생각은 다르니 개발자가 우긴다고 해결될 문제는 아니다.GTK+로 된 여러 오픈소스 진영의 레퍼런스 코드를 참고해야 한다.위에서 소개한 GPE와 같이 잘 만들어진 프레임워크의 코드는 상당히 참고할 부분이 많다. 그러나 그전에 라이선스가 어떠냐에 따라 해결해야 할 문제가 많다. GPL이 사실상 대부분인 오픈소스 프로젝트의 코드들을 그대로 가져다가 쓴다는 것은 법률적인 문제 이전의 프로그래머 세계에서 양심의 가책이 느껴지는 일이다. 그대로 코드를 가져다 쓰는 것은 라이선스 위반이지만 알고리즘과 여러 가지 테크닉으로 해결방법은 참조할 수 있다. 내가 현재 겪고 있는 문제는 전 세계의 누군가는 해결했거나 지금 동시에 같이 겪고 있는 문제인 경우가 많다. 상업 벤더의 제품인 경우에는 기술 지원 서비스를 받아 돈을 내고 비용을 지출해서 해결해야 하지만 이미 GTK+같은 경우는 10년 넘게 전 세계의 프로그래머 해커들이 만들어 놓은 코드와 문서들, 그리고 커뮤니티 차원에서 답변을 해줄 고수들이 기다리고 있다. 물론 그 사람들이 돈을 내는 것도 아닌데 알아서 해결해주진 않는다. 하지만 물어보고 해결책을 찾고자 한다면 반드시 해결할 수 있을 거라 생각한다. 어차피 돈을 내고 기술지원을 받는 경우도 그 정도 이상을 해주는 것은 아니다. 기껏해야 이메일 답변과 그 상위레벨의 엔지니어와 전화통화 정도가 끝이다. 코드를 직접 가르쳐주거나 직접 1:1로 해결해 주는 경우는 아주 많은 비용을 지출해야 한다. 하지만 자유 소프트웨어 진영의 해커들은 코드를 돈도 안 받고 알려주기도 하니 쓰는 입장에서는 돈을 내고 지원을 받나 안내고 지원을 받나 결과는 똑같다. 단지 법률적으로 큰 문제가 발생했을 때 그 책임을 떠넘길 대상이 있느냐 없느냐는 것이 큰 것인데, 그것은 마이크로소프트사에 윈도우가 불안하게 자꾸 다운되어서 손해 봤으니 책임지라고 소송을 걸어 배상하라는 얘기와 같다. 그게 가능하다고 생각하는 독자 여러분은 단 한명도 없을거라 생각한다.이제 겨울이 지나가고 봄이 오고 있다. 한미 FTA가 3월 달에 체결된다 하는데 필자와 독자 여러분들이 일하는 이 IT업계에 어떤 영향을 끼치게 될지 의문이다. 지인 몇몇 분은 FTA로 분명 IT업계는 붐이 일 것이라고 하지만 다른 업계는 그게 아닐 것이다. 결국엔 어떻게든 진행될 양극화에 씁쓸함을 남긴다. 우리들은 언제나 이번 연재의 라이브러리처럼 선택을 잘 해야 될 것이다.다음 회에서는 이번에 소개한 미국의 GTK+에 견줘서 유럽을 대표하는 경쟁자이자 상업적인 제품으로 항상 비교되고 있는 노르웨이 오슬로에 위치한 트롤테크사의 Qt 라이브러리에 대해 알아보도록 하겠다.
이 기사를 공유합니다
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지