2011년 11월 28일 월요일
2011년 11월 23일 수요일
다음 DevOn에 참가합니다
그놈 한국 부스에 오시면, 그놈 로고가 찍힌 공책도 하나 드립니다 :-)
2011년 8월 13일 토요일
데스크탑 서밋 (Desktop Summit) 2011, 그놈 아시아(GNOME Asia)
안녕하세요.
데스크탑 서밋 소식이 늦었습니다. 발표 내용이나 소식은 데스트탑 서밋 홈페이지와 그놈 플래넷을 보시면 잘 알 수 있을 것 같고, 우선, 그놈 아시아 관련 소식을 전하고 싶습니다.
그놈 아시아(GNOME Asia)는 처음 2008년 중국을 시작으로 베트남, 대만에서 열렸고 올해는 인도에서 행사가 진행되었습니다. Emily Chen란 분이 그놈 아시아 행사를 이끄는데, 직접 그놈 아시아에 대한 발표도 해주셨습니다. 그놈 아시아가 시작된 이유와 함께 그 동안 진행된 행사를 소개해주어서 그놈 아시아 행사를 이해하는데, 도움이 되었습니다. 아쉽게도, 한국 커뮤니티에 대한 언급이 없었지만, 사실 알기도 쉽지 않을 것 같습니다. :-) 내년에는 홍콩에서 열린다고 하니, 그놈 한국 커뮤니티에서 많이 참석해서 서로 교류가 있었으면 좋겠네요. 2013년에는 서울 유치를 목표로 뛰어볼까요? 그 분이 말하는 국격이 확 돋을 것 같네요.
재미있는 동영상을 하나 소개합니다. 매년 State of the union이라는 재미있는 만담이 진행됩니다. 위 동영상 보면 알겠지만, 히틀러를 통해 그놈3 출시에 대한 커뮤니티의 시각을 재미있게 풍자하였습니다.참고로, 작년 State of the union은 완전하게 공개되어 있네요.
아, 그리고 KDE에서 오신 한국분을 만나서 한국 그놈, KDE 서밋(?)을 가졌습니다. 한국이 아니 해외에서만 이 분을 만나니 참 재밌네요. 홀로 번역을 책임지느라 고생이 많은 것 같습니다. 정말 한국에서 두 커뮤니티가 한 번 만나면 좋을 것 같네요.
시간이 되는데로 기술적인 내용들도 소개하도록 하겠습니다.
데스크탑 서밋 소식이 늦었습니다. 발표 내용이나 소식은 데스트탑 서밋 홈페이지와 그놈 플래넷을 보시면 잘 알 수 있을 것 같고, 우선, 그놈 아시아 관련 소식을 전하고 싶습니다.
그놈 아시아(GNOME Asia)는 처음 2008년 중국을 시작으로 베트남, 대만에서 열렸고 올해는 인도에서 행사가 진행되었습니다. Emily Chen란 분이 그놈 아시아 행사를 이끄는데, 직접 그놈 아시아에 대한 발표도 해주셨습니다. 그놈 아시아가 시작된 이유와 함께 그 동안 진행된 행사를 소개해주어서 그놈 아시아 행사를 이해하는데, 도움이 되었습니다. 아쉽게도, 한국 커뮤니티에 대한 언급이 없었지만, 사실 알기도 쉽지 않을 것 같습니다. :-) 내년에는 홍콩에서 열린다고 하니, 그놈 한국 커뮤니티에서 많이 참석해서 서로 교류가 있었으면 좋겠네요. 2013년에는 서울 유치를 목표로 뛰어볼까요? 그 분이 말하는 국격이 확 돋을 것 같네요.
재미있는 동영상을 하나 소개합니다. 매년 State of the union이라는 재미있는 만담이 진행됩니다. 위 동영상 보면 알겠지만, 히틀러를 통해 그놈3 출시에 대한 커뮤니티의 시각을 재미있게 풍자하였습니다.참고로, 작년 State of the union은 완전하게 공개되어 있네요.
아, 그리고 KDE에서 오신 한국분을 만나서 한국 그놈, KDE 서밋(?)을 가졌습니다. 한국이 아니 해외에서만 이 분을 만나니 참 재밌네요. 홀로 번역을 책임지느라 고생이 많은 것 같습니다. 정말 한국에서 두 커뮤니티가 한 번 만나면 좋을 것 같네요.
시간이 되는데로 기술적인 내용들도 소개하도록 하겠습니다.
라벨:
데스크탑 서밋,
Desktop Summit
2011년 7월 15일 금요일
GNOME 개발 설명서 / 동영상
오랜만에 GNOME 개발자 센터를 들어갔더니, 모양 뿐 아니라 내용도 확 바뀌어 있습니다. 예전에도 있었는데 제가 몰랐던 건지도 모르지만, 10분짜리 설명서(10-minute tutorials)가 가장 먼저 눈에 띄었습니다. (접속한 페이지의 실제 이름은 GNOME 개발자 플랫폼 데모입니다) 이미지 보기, 기타 튜너, 메시지 보드 등과 같은 여러 예제를 통해 단순한 GTK+ 위젯 라이브러리 사용법만 보여주는것 뿐 아니라, 말 그대로 GNOME 플랫폼의 중심이 되는 GTK+ / Clutter / GStreamer / WebKitGtk 라이브러리 등을 이용하여 유용하게 참고할 수 있는 간단한 응용 프로그램을 Anjuta 통합 개발 도구, Glade UI 편집기를 이용하여 개발하는 방법을 설명합니다. 또한 C / C++ / JavaScript / Python / Vala 등과 같은 언어별 예제도 각각 제공하고 있습니다.
최근 계속 연재되는 파이썬(Python) 언어와 GTK+ / Clutter / GStreamer 라이브러리를 이용한 GNOME 개발 동영상(screencast)도 볼만합니다. 몇몇 예제는 그놈 개발자 사이트 데모 프로그램과 겹치는 것도 있습니다.
gedit 텍스트 편집기와 Glade UI 편집기만을 이용해 파이썬 언어의 간결함과 GNOME 플랫폼 라이브러리의 강력함을 잘 보여주고 있습니다. 여담이지만, 영어가 짧아 단어만 알아듣는 본인도 코드만 보고 이해할 수 있었습니다. :)
물론 이 글에서 소개한 설명서는 대부분 아마도 많은 개발자에게 GNOME 플랫폼의 우수성을 전파하고 사용을 독려하기 위해 매우 기본적인 내용만 맛보기로 소개하기 때문에 더 심각하고(?) 자세한 내용을 알고 싶다면 각 기술에 대한 심층적인 공부가 필요합니다.
또한 GNOME 플랫폼 라이브러리라는 제목을 달고 있지만 대부분의 기술이 반드시 GNOME 환경에서만 동작하는 게 아니므로 리눅스 관련 개발자라면 한 번 들여다보는 것도 좋을 것 같습니다.
최근 계속 연재되는 파이썬(Python) 언어와 GTK+ / Clutter / GStreamer 라이브러리를 이용한 GNOME 개발 동영상(screencast)도 볼만합니다. 몇몇 예제는 그놈 개발자 사이트 데모 프로그램과 겹치는 것도 있습니다.
- GNOME 스크린캐스트 - 01. 첫번째 GTK+ 어플리케이션 (2011-06-16): 파이썬을 이용해 기본 기능을 가진 GTK+ 프로그램 만들기
- GNOME 스크린캐스트 - 02. 화려한 사진 어플리케이션 만들기 (2011-06-22): 파이썬과 GTK+를 이용해 간단한 사진 프로그램 만들기
- GNOME 스크린캐스트 - 03. 멋진 계산기 만들기 (2011-06-29): 파이썬과 GTK+를 이용해 간단한 계산기 프로그램 만들기
- GNOME 스크린캐스트 - 04. 우아한 기타 튜너 만들기 (2011-07-07): 파이썬과 GTK+, GStreamer를 이용해 기타 튜너 프로그램 만들기
- GNOME 스크린캐스트 - 05. 매력적인 동영상 재생기 만들기 (2011-07-15): 파이썬과 GTK+, GStreamer, Clutter를 이용해 동영상 재생기 만들기
gedit 텍스트 편집기와 Glade UI 편집기만을 이용해 파이썬 언어의 간결함과 GNOME 플랫폼 라이브러리의 강력함을 잘 보여주고 있습니다. 여담이지만, 영어가 짧아 단어만 알아듣는 본인도 코드만 보고 이해할 수 있었습니다. :)
물론 이 글에서 소개한 설명서는 대부분 아마도 많은 개발자에게 GNOME 플랫폼의 우수성을 전파하고 사용을 독려하기 위해 매우 기본적인 내용만 맛보기로 소개하기 때문에 더 심각하고(?) 자세한 내용을 알고 싶다면 각 기술에 대한 심층적인 공부가 필요합니다.
또한 GNOME 플랫폼 라이브러리라는 제목을 달고 있지만 대부분의 기술이 반드시 GNOME 환경에서만 동작하는 게 아니므로 리눅스 관련 개발자라면 한 번 들여다보는 것도 좋을 것 같습니다.
(이 글은 개인 블로그에 함께 게재되어 있습니다)
2011년 7월 10일 일요일
GObject 객체 지향 프로그래밍 (4)
이전 글에 계속 이어집니다.
객체 속성 정보 얻기
EdcHost 객체의 속성 정보를 실행 중에 얻어볼까 합니다.
왜 또 갑자기 불필요한 예제를 꺼내냐고 물어보실 분이 있을 것 같아 말하자면, 가끔 요긴한 경우가 있기 때문입니다. 예를 들어 EdcHost 객체를 상속받은 EdcHostDoosan, EdcHostKia, EdcHostLitte 객체가 여러 개 존재할 경우, 이 객체들은 EdcHost의 공통 속성 뿐 아니라 본인의 속성도 따로 가집니다. 이러한 여러 객체를 관리할때, 특정 속성이 있는지 여부를 검사해서 관련 UI를 활성 / 비활성하거나, 편집 UI 자체를 속성 스펙과 목록을 이용해 100% 자동화하는 게 가능합니다. (Glade 처럼 말이죠) 물론 옵션 같은 플래그(flags) 변수를 정의하는 방법 등 여러가지 대안이 가능하겠지만, 최초 객체 설계시 고려하지 못했던 기능이나 속성을 나중에 계속 추가해 나가야 하는 경우 기존에 만든 객체를 매번 다시 수정하고 업그레이드하는 것보다 더 안전하고 깔끔한 방법이 될 수 있습니다. 그리고 당연히 더많은 응용이 있겠지만, 일단 알아두면 나중에 어떤 식으로든 도움이 되리라 생각합니다.
일단, 다음 코드는 객체가 가지고 있는 속성 이름과 각 속성의 현재 값을 출력합니다.
위 코드에서 분명하게 이해해야 하는 점은, 객체 인스턴스가 아닌 객체 클래스에게 속성 정보를 질의한다는 점입니다. 모든 속성의 스펙을 얻기 위해
객체에 어떤 속성이 있는지 알아보려면
속성 변경 알림 시그널 이용하기
객체의 속성 값을 변경할 때
참고로 이 기능은, 디자인 패턴에서 말하는 관찰자(observer) 패턴일 수도 있고, GObject 매뉴얼에서 사용하는 것처럼 일종의 메시징 시스템 역할도 합니다. 예를 들어 모델(model)의 값이 변경되면 자동으로 뷰(view) 역할을 하는 GUI에 반영하는 코드를 작성할 경우 기존 객체 구현 코드를 수정하지 않고, 다시 말해 의존성을 추가하지 않고 기능을 구현할 수 있게 도와주어 객체간 결합도를 없애 줍니다.
자 그런데, 위 예제에서
하지만 이 방법은 약간의 오버헤드가 있을 수 있습니다. 두번째 방법은,
시그널이 중복 발생할 경우를 염려할 필요는 없습니다. 시그널은 GObject 내부적으로 알아서 잘 정리되어 한 번 변경하면 한 번만 시그널이 발생합니다.
오늘은 여기까지입니다... :)
객체 속성 정보 얻기
EdcHost 객체의 속성 정보를 실행 중에 얻어볼까 합니다.
왜 또 갑자기 불필요한 예제를 꺼내냐고 물어보실 분이 있을 것 같아 말하자면, 가끔 요긴한 경우가 있기 때문입니다. 예를 들어 EdcHost 객체를 상속받은 EdcHostDoosan, EdcHostKia, EdcHostLitte 객체가 여러 개 존재할 경우, 이 객체들은 EdcHost의 공통 속성 뿐 아니라 본인의 속성도 따로 가집니다. 이러한 여러 객체를 관리할때, 특정 속성이 있는지 여부를 검사해서 관련 UI를 활성 / 비활성하거나, 편집 UI 자체를 속성 스펙과 목록을 이용해 100% 자동화하는 게 가능합니다. (Glade 처럼 말이죠) 물론 옵션 같은 플래그(flags) 변수를 정의하는 방법 등 여러가지 대안이 가능하겠지만, 최초 객체 설계시 고려하지 못했던 기능이나 속성을 나중에 계속 추가해 나가야 하는 경우 기존에 만든 객체를 매번 다시 수정하고 업그레이드하는 것보다 더 안전하고 깔끔한 방법이 될 수 있습니다. 그리고 당연히 더많은 응용이 있겠지만, 일단 알아두면 나중에 어떤 식으로든 도움이 되리라 생각합니다.
일단, 다음 코드는 객체가 가지고 있는 속성 이름과 각 속성의 현재 값을 출력합니다.
static void
print_properties (GObject *object)
{
GObjectClass *oclass;
GParamSpec **specs;
guint n;
guint i;
oclass = G_OBJECT_GET_CLASS (object);
specs = g_object_class_list_properties (oclass, &n);
for (i = 0; i < n; i++)
{
GParamSpec *spec;
GValue value = { 0 };
gchar *str;
spec = specs[i];
g_value_init (&value, spec->value_type);
g_object_get_property (G_OBJECT (object),
spec->name,
&value);
str = g_strdup_value_contents (&value);
g_print ("property '%s' is '%s'\n",
spec->name,
str);
g_value_unset (&value);
g_free (str);
}
g_free (specs);
}
{
EdcHost *host;
/* ... */
host = g_object_new (
EDC_TYPE_HOST,
"address", "demo.emstone.com",
"port", 8081,
NULL);
print_properties (G_OBJECT (host));
g_object_unref (host);
/* ... */
}위 코드에서 분명하게 이해해야 하는 점은, 객체 인스턴스가 아닌 객체 클래스에게 속성 정보를 질의한다는 점입니다. 모든 속성의 스펙을 얻기 위해
g_object_class_list_properties() 함수를 사용하고, GValue 객체에 속성 값을 가져온 다음, 문자열로 출력하기 위해 g_strdup_value_contents() 함수를 이용해 변환하고 있습니다.객체에 어떤 속성이 있는지 알아보려면
g_object_class_find_property() 함수를 이용하면 됩니다.속성 변경 알림 시그널 이용하기
객체의 속성 값을 변경할 때
g_object_set() 함수를 이용하면 좋은 점은, 값을 변경하면 자동으로 시그널(signal)이 발생한다는 점입니다. GObject 시스템에서 시그널은 특정 사건(event)이 일어나면 발생(emit)합니다. 대부분의 경우 시그널은 객체 클래스 초기화시에 정의해야 하지만, 다행히도 속성 값이 변경될때 발생하는 시그널은 특별한 작업을 해주지 않아도 기본적으로 동작합니다. 따라서 "notify::property-name" 형식의 이름을 가지는 시그널에 콜백 함수를 연결하면 객체 값이 변경될때 자동으로 호출되는 함수를 등록할 수 있습니다.
static void
property_notified (GObject *object,
GParamSpec *pspec,
gpointer data)
{
GValue value = { 0 };
gchar *str;
g_value_init (&value, pspec->value_type);
g_object_get_property (object, pspec->name, &value);
str = g_strdup_value_contents (&value);
g_print ("property '%s' is set to '%s'\n",
pspec->name, str);
g_value_unset (&value);
g_free (str);
}
{
EdcHost *host;
host = g_object_new (EDC_TYPE_HOST, NULL);
g_signal_connect (host,
"notify::address",
G_CALLBACK (property_notified),
NULL);
g_signal_connect (host,
"notify::port",
G_CALLBACK (property_notified),
NULL);
g_object_set (host,
"address", "192.168.0.1",
"port", 8087,
NULL);
edc_host_set_address (host, "192.168.0.22");
g_object_unref (host);
}참고로 이 기능은, 디자인 패턴에서 말하는 관찰자(observer) 패턴일 수도 있고, GObject 매뉴얼에서 사용하는 것처럼 일종의 메시징 시스템 역할도 합니다. 예를 들어 모델(model)의 값이 변경되면 자동으로 뷰(view) 역할을 하는 GUI에 반영하는 코드를 작성할 경우 기존 객체 구현 코드를 수정하지 않고, 다시 말해 의존성을 추가하지 않고 기능을 구현할 수 있게 도와주어 객체간 결합도를 없애 줍니다.
자 그런데, 위 예제에서
edc_host_set_address() 를 사용할 때는 콜백함수가 호출이 안되는 문제점이 있습니다. 왜냐하면 이 함수는 내부 address 변수를 직접 수정하기 때문에 값이 변경되었는지 여부를 GObject 시스템이 알 방법이 없기 때문입니다. 따라서 기존 코드를 수정해야 하는데, 첫번째 방법은 접근자를 이용하더라도 내부적으로 g_object_set() 을 호출하도록 하는 겁니다. (여기서는 'address' 관련 API만 보여드립니다)void
edc_host_set_address (EdcHost *host,
const gchar *address)
{
g_return_if_fail (EDC_IS_HOST (host));
g_return_if_fail (address != NULL);
g_object_set (host,
"address", address,
NULL);
}하지만 이 방법은 약간의 오버헤드가 있을 수 있습니다. 두번째 방법은,
g_object_notify() 함수를 이용해 직접 알려주는 겁니다.void
edc_host_set_address (EdcHost *host,
const gchar *address)
{
EdcHostPrivate *priv;
g_return_if_fail (EDC_IS_HOST (host));
g_return_if_fail (address != NULL);
priv = EDC_HOST_GET_PRIVATE (host);
g_free (priv->address);
priv->address = g_strdup (address);
g_object_notify (G_OBJECT (host), "address");
}edc_host_set_property() 함수 안에서 중복되는 코드도 정리해 봅시다.static void
edc_host_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
EdcHost *host = EDC_HOST (object);
EdcHostPrivate *priv;
priv = EDC_HOST_GET_PRIVATE (host);
switch (property_id)
{
case EDC_HOST_PROP_NAME:
edc_host_set_name (host, g_value_get_string (value));
break;
case EDC_HOST_PROP_ADDRESS:
edc_host_set_address (host, g_value_get_string (value));
break;
case EDC_HOST_PROP_PORT:
edc_host_set_port (host, g_value_get_int (value));
break;
case EDC_HOST_PROP_USER:
edc_host_set_user (host, g_value_get_string (value));
break;
case EDC_HOST_PROP_PASSWORD:
edc_host_set_password (host, g_value_get_string (value));
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
property_id,
pspec);
break;
}
}시그널이 중복 발생할 경우를 염려할 필요는 없습니다. 시그널은 GObject 내부적으로 알아서 잘 정리되어 한 번 변경하면 한 번만 시그널이 발생합니다.
오늘은 여기까지입니다... :)
(이 글은 개인 블로그에 함께 게재되어 있습니다)
GObject 객체 지향 프로그래밍 (3)
속성 (Properties) 추가하기
이제, GObject 속성(properties) 기능을 추가하려고 하는데, 왜 쓸데없이 일을 만들어서 하냐고 물으면 할 말이 있어야할 것 같아서, GObject 속성의 특징을 요약해 봤습니다.
물론 이미 많은 언어와 라이브러리가 그 이상의 기능을 지원하기도 하고, 일정 능력 이상의 개발자라면 직접 구현하는게 아주 어려운 것도 아닙니다. 하지만 이미 잘 구현되어 검증받은 라이브러리가 있는데 굳이 새로운 바퀴를 만들 필요는 없겠지요? 아무튼, 정확한 내용은 글을 적으면서 하나씩 설명해 나가겠습니다.
GObject 객체에 속성을 추가하려면 속성의 값(value)이 어떤 형(type)인지, 이름이 무엇인지, 값의 범위는 어떻게 되는지, 기본값은 무엇인지 등을 정의해서 알려주어야 합니다. (C++이나 Java에서 클래스 멤버 변수를 정의하는 것과 비슷합니다) 이러한 정보를 줄임말로 스펙(spec.)이라고 한다면, 속성을 추가한다는 건 다른 말로, 스펙으로 명시한 속성 정보를 클래스에 설치(install)하는 것을 의미합니다. 객체 인스턴스마다 속성의 실제 값(value)은 모두 다르겠지만, 어떤 속성이 있는지 그 속성은 어떻게 구성되어는지는 모두 동일하겠지요. (참고로 GObject 관련 API를 훑어보시면 정확히 모르더라도 지금 언급한 개념의 단어로 이루어진 API가 꽤 많은 걸 아시게 될 겁니다) 그렇기 때문에, 속성을 추가하는 작업은 클래스 초기화 함수에서 이루어집니다.
다음은 기존 예제에서 속성을 추가한 코드입니다. (변경된 부분만 보여드립니다)
edc-host.c
제일 먼저 정의된 열거형 타입에 대해 설명하자면, 클래스 내부에서 속성은 정수형 숫자로 관리됩니다. 예를 들어 1번 속성, 3번 속성처럼 직접 정수형을 사용해도 되지만, 관례적으로 가독성을 위해 열거형으로 정의합니다. 이렇게 정의한 번호를 클래스에 속성을 설치할때 지정하면 [
재정의된
여기까지 이해하셨다면 아시겠지만, GObject 시스템은 속성에 전반적인 틀과 관리 체계만 제공할 뿐 실제 속성을 다루는 작업은 대부분 직접 구현해야 합니다. 이는 프로그래머의 자유도를 높여 주기도 하지만, 불필요한 반복 작업을 유발하기도 합니다. 그리고 이 때문에 Vala 같은 GObject 기반 언어가 새로 만들어지기도 했습니다.
속성 (Properties) 사용하기
이렇게 정의한 속성을 객체 외부에서 사용하기 위해 몇 가지 방법이 있지만, 가장 쉽고 많이 사용하는 방법은
눈여겨 보신 분은 아시겠지만,
이렇게 대략 GObject 속성 기본 사용법을 설명한 것 같습니다. 물론 이 예제 코드에는 몇 가지 오류가 남아있는데, 이는 위에서 언급한 것처럼 객체 속성을 다루는 다른 부분을 설명하면서 보완해 나갈 예정입니다.
오늘은 여기까지입니다.
이제, GObject 속성(properties) 기능을 추가하려고 하는데, 왜 쓸데없이 일을 만들어서 하냐고 물으면 할 말이 있어야할 것 같아서, GObject 속성의 특징을 요약해 봤습니다.
- 단일 API로 모든 속성 값을 얻어오거나 변경하기
- 속성 변경시 자동으로 호출되는 함수 등록하기 (시그널 이용)
- 실행 중에 속성에 대한 정보 얻어내기
물론 이미 많은 언어와 라이브러리가 그 이상의 기능을 지원하기도 하고, 일정 능력 이상의 개발자라면 직접 구현하는게 아주 어려운 것도 아닙니다. 하지만 이미 잘 구현되어 검증받은 라이브러리가 있는데 굳이 새로운 바퀴를 만들 필요는 없겠지요? 아무튼, 정확한 내용은 글을 적으면서 하나씩 설명해 나가겠습니다.
GObject 객체에 속성을 추가하려면 속성의 값(value)이 어떤 형(type)인지, 이름이 무엇인지, 값의 범위는 어떻게 되는지, 기본값은 무엇인지 등을 정의해서 알려주어야 합니다. (C++이나 Java에서 클래스 멤버 변수를 정의하는 것과 비슷합니다) 이러한 정보를 줄임말로 스펙(spec.)이라고 한다면, 속성을 추가한다는 건 다른 말로, 스펙으로 명시한 속성 정보를 클래스에 설치(install)하는 것을 의미합니다. 객체 인스턴스마다 속성의 실제 값(value)은 모두 다르겠지만, 어떤 속성이 있는지 그 속성은 어떻게 구성되어는지는 모두 동일하겠지요. (참고로 GObject 관련 API를 훑어보시면 정확히 모르더라도 지금 언급한 개념의 단어로 이루어진 API가 꽤 많은 걸 아시게 될 겁니다) 그렇기 때문에, 속성을 추가하는 작업은 클래스 초기화 함수에서 이루어집니다.
다음은 기존 예제에서 속성을 추가한 코드입니다. (변경된 부분만 보여드립니다)
edc-host.c
/* ...[snip]... */
enum
{
EDC_HOST_PROP_0, /* ignore */
EDC_HOST_PROP_NAME,
EDC_HOST_PROP_ADDRESS,
EDC_HOST_PROP_PORT,
EDC_HOST_PROP_USER,
EDC_HOST_PROP_PASSWORD
};
/* ...[snip]... */
static void
edc_host_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
EdcHost *host = EDC_HOST (object);
EdcHostPrivate *priv;
priv = EDC_HOST_GET_PRIVATE (host);
switch (property_id)
{
case EDC_HOST_PROP_NAME:
g_value_set_string (value, priv->name);
break;
case EDC_HOST_PROP_ADDRESS:
g_value_set_string (value, priv->address);
break;
case EDC_HOST_PROP_PORT:
g_value_set_int (value, priv->port);
break;
case EDC_HOST_PROP_USER:
g_value_set_string (value, priv->user);
break;
case EDC_HOST_PROP_PASSWORD:
g_value_set_string (value, priv->password);
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
property_id,
pspec);
break;
}
}
static void
edc_host_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
EdcHost *host = EDC_HOST (object);
EdcHostPrivate *priv;
priv = EDC_HOST_GET_PRIVATE (host);
switch (property_id)
{
case EDC_HOST_PROP_NAME:
g_free (priv->name);
priv->name = g_value_dup_string (value);
break;
case EDC_HOST_PROP_ADDRESS:
g_free (priv->address);
priv->address = g_value_dup_string (value);
break;
case EDC_HOST_PROP_PORT:
priv->port = g_value_get_int (value);
break;
case EDC_HOST_PROP_USER:
g_free (priv->user);
priv->user = g_value_dup_string (value);
break;
case EDC_HOST_PROP_PASSWORD:
g_free (priv->password);
priv->password = g_value_dup_string (value);
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
property_id,
pspec);
break;
}
}
/* ...[snip]... */
/* class initializer */
static void
edc_host_class_init (EdcHostClass *klass)
{
GObjectClass *gobj_class;
GParamSpec *pspec;
gobj_class = G_OBJECT_CLASS (klass);
gobj_class->finalize = edc_host_finalize;
gobj_class->set_property = edc_host_set_property;
gobj_class->get_property = edc_host_get_property;
g_type_class_add_private (gobj_class,
sizeof (EdcHostPrivate));
pspec =
g_param_spec_string ("name", /* name */
"Name", /* nick */
"the name of a host", /* blurb */
"", /* default */
G_PARAM_READWRITE);
g_object_class_install_property (gobj_class,
EDC_HOST_PROP_NAME,
pspec);
pspec = g_param_spec_string ("address",
"Address",
"the address of a host",
"",
G_PARAM_READWRITE);
g_object_class_install_property (gobj_class,
EDC_HOST_PROP_ADDRESS,
pspec);
pspec = g_param_spec_int ("port",
"Port",
"the port number of a host",
0, /* minimum */
65535, /* maximum */
0, /* default */
G_PARAM_READWRITE);
g_object_class_install_property (gobj_class,
EDC_HOST_PROP_PORT,
pspec);
pspec = g_param_spec_string ("user",
"User",
"password for authetication",
"",
G_PARAM_READWRITE);
g_object_class_install_property (gobj_class,
EDC_HOST_PROP_USER,
pspec);
pspec = g_param_spec_string ("password",
"Password",
"password for authetication",
"",
G_PARAM_READWRITE);
g_object_class_install_property (gobj_class,
EDC_HOST_PROP_PASSWORD,
pspec);
}제일 먼저 정의된 열거형 타입에 대해 설명하자면, 클래스 내부에서 속성은 정수형 숫자로 관리됩니다. 예를 들어 1번 속성, 3번 속성처럼 직접 정수형을 사용해도 되지만, 관례적으로 가독성을 위해 열거형으로 정의합니다. 이렇게 정의한 번호를 클래스에 속성을 설치할때 지정하면 [
g_object_class_install_property()], edc_host_{get/set}_property() 속성 읽기 / 쓰기 함수의 인자로 `property_id'가 전달되는데, 이 ID가 바로 속성 번호입니다. 물론 속성 번호는 g_object_class_override_property() 같은 다른 API에서도 사용합니다.edc_host_class_init() 클래스 초기화 함수를 보면, g_param_spec_*() 함수를 이용하여 각 속성의 스펙을 정의해서 g_object_class_install_property()함수를 이용해 클래스 객체에 설치합니다. 그리고,속성 읽기 /쓰기 메쏘드를 재정의합니다. 참고로 API 문서를 확인하시면, 다양한 형(type)을 위한 스펙 정의 함수가 있는 걸 알 수 있습니다. 속성 스펙을 정의할때 마지막에 넣어주는 플래그(flags)는 속성의 특성을 정의하는데, GParamFlags 설명을 한 번 읽어보시면 어렵지 않게 이해할 수 있습니다. 여기서는 모든 속성을 읽고 쓰기 가능하게 했습니다.재정의된
edc_host_{get/set}_property() 속성 읽기 / 쓰기 메쏘드 함수를 보면, 접근자(accessor) 함수와 동일한 작업을 합니다. 다른 점이라면 속성 ID에 따라 GValue 객체에서 값을 읽거나, 값을 할당한다는 점입니다. GValue 객체는 쉽게 말해 어떤 형(type)의 값이라도 담을 수 있는 일반적인 값(generic values)입니다. 참고로 이 역시 다양한 형(type)을 위한 g_value_{set,get}_*() 형태의 함수가 존재하므로 이를 그대로 이용하면 됩니다. (물론 더 능숙하게 사용하려면 API 문서를 한 번 훑어보는게 좋겠지요)여기까지 이해하셨다면 아시겠지만, GObject 시스템은 속성에 전반적인 틀과 관리 체계만 제공할 뿐 실제 속성을 다루는 작업은 대부분 직접 구현해야 합니다. 이는 프로그래머의 자유도를 높여 주기도 하지만, 불필요한 반복 작업을 유발하기도 합니다. 그리고 이 때문에 Vala 같은 GObject 기반 언어가 새로 만들어지기도 했습니다.
속성 (Properties) 사용하기
이렇게 정의한 속성을 객체 외부에서 사용하기 위해 몇 가지 방법이 있지만, 가장 쉽고 많이 사용하는 방법은
g_object_get() / g_object_set() 함수를 이용하는 겁니다.{
EdcHost *host;
gchar *address;
gint port;
g_type_init ();
host = edc_host_new ();
g_object_set (host,
"address", "192.168.0.100",
"port", 8080,
NULL);
address = edc_host_get_address (host);
g_assert_cmpstr (address, ==, "192.168.0.100");
g_free (address);
g_object_get (host,
"address", &address,
"port", &port,
NULL);
g_assert_cmpstr (address, ==, "192.168.0.100");
g_assert_cmpint (port, ==, 8080);
g_free (address);
g_object_unref (host);
}g_object_new() 함수를 이용하여 객체를 생성할때 아예 속성을 함께 지정할 수도 있습니다.{
EdcHost *host;
gchar *address;
gint port;
g_type_init ();
host = g_object_new (EDC_TYPE_HOST,
"address", "demo.emstone.com",
"port", 8081,
NULL);
g_object_get (host,
"address", &address,
"port", &port,
NULL);
g_assert_cmpstr (address, ==, "demo.emstone.com");
g_assert_cmpint (port, ==, 8081);
g_free (address);
g_object_unref (host);
}눈여겨 보신 분은 아시겠지만,
edc_host_new() 함수는 g_object_new (EDC_TYPE_HOST, NULL) 호출로 만들어진 객체를 돌려주는 역할만 합니다.이렇게 대략 GObject 속성 기본 사용법을 설명한 것 같습니다. 물론 이 예제 코드에는 몇 가지 오류가 남아있는데, 이는 위에서 언급한 것처럼 객체 속성을 다루는 다른 부분을 설명하면서 보완해 나갈 예정입니다.
오늘은 여기까지입니다.
(이 글은 개인 블로그에 함께 게재되어 있습니다)
GObject 객체 지향 프로그래밍 (2)
첫번째 글이 당연한 내용을 너무 길게 설명했다는 의견이 있어서, 이번 글부터는 더 짧고 간결하게 정리해 보려고 노력하고 있습니다. 그리고, 이 글의 대상은 한 번이라도 GTK+ / GLib 라이브러리를 사용한 경험이 있는 개발자입니다. 그래서 정말로 기초적인 내용은 피하고 있습니다.
접근자 (Accessors)
소프트웨어 공학에서 모듈이나 객체 설계시 기본적으로 강조하는 정보은닉(information hiding), 캡슐화(encapsulation), 결합도(coupling) 등과 같은 개념에 의하면, C 언어처럼 구조체의 필드 변수를 외부로 직접 공개하는 건 좋지 않다고 합니다. 그리고 대부분의 경우 직접 접근 방식보다 읽고 쓰는 접근자(accessors)를 제공하는 게 여러모로 좋다고 하지요. 물론 성능 문제로 직접 접근 방식을 고려해야 하는 경우도 있지만, 지금까지 경험에 비춰보면, 병목을 일으키는 부분은 프로파일러를 돌려서 정확하게 파악한 다음에 해결하는 게 대부분 좋기 때문에 처음부터 그럴 필요는 없을 것 같습니다.
참고로 현재 개발 중인 GTK+ 3.0에서도 기존에 공개되었던 변수들을 모조리 안으로 숨기고, GTK+ 2.x 어플리케이션의 이전(migration)을 위해 GSEAL() 매크로를 2.14 버전부터 제공하고 있습니다.
아무튼 그래서, 일단 지난 글에서 예제로 사용한 호스트 객체의 필드를 숨기고 접근 API를 구현해 보았습니다. (변경되거나 수정한 부분만 보여드립니다)
edc-host.h
edc-host.c
먼저 헤더 파일을 보면, EdcHost 구조체에서 공개되었던 객체 변수가 모두 사라지고, 대신
부가적으로 조금만 더 설명하면, 모든 문자열을 넘겨주는 API는 문자열을 복사해서 넘겨주어 원본 문자열을 보호합니다. 따라서 API 문서에 넘겨받은 문자열을 반드시 해제하라고 명시되어 있어야 하겠죠. 또한 지난 글에서 잠시 언급한 것처럼, 공개된 함수 진입 시점에서 인수 적합성 검사를 할때
참고로 위 예제에서 비공개(private) 객체에 접근하기 위해 사용한 방식은 오버헤드가 조금 있어서 성능이 중요한 경우 조금 다른 방식을 사용해야 합니다. 이에 대한 내용은 이전 포스트를 참고하시기 바랍니다.
이렇게 해서 기본적인 객체 속성에 대한 접근자를 구현했습니다. 물론 이게 다는 아니고, 다음에 설명할 GObject 속성(properties) 기능을 이용하면 사실 접근자를 구현할 필요도 없습니다. 하지만, GTK+와 같은 대부분의 GObject 기반 객체는 함수 API 기반의 접근자를 동시에 제공하고 있으므로 관례를 따르는 게 나쁘지는 않겠지요.
글머리에서 언급했듯이, 계속 적다 보면 내용도 길어지고 포스팅 주기도 길어질 것 같아 오늘은 일단 여기까지만 적습니다. 다음에는 본격적으로 GObject 속성(properties)을 추가할 예정인데, 설명할 게 많아서... ;)
접근자 (Accessors)
소프트웨어 공학에서 모듈이나 객체 설계시 기본적으로 강조하는 정보은닉(information hiding), 캡슐화(encapsulation), 결합도(coupling) 등과 같은 개념에 의하면, C 언어처럼 구조체의 필드 변수를 외부로 직접 공개하는 건 좋지 않다고 합니다. 그리고 대부분의 경우 직접 접근 방식보다 읽고 쓰는 접근자(accessors)를 제공하는 게 여러모로 좋다고 하지요. 물론 성능 문제로 직접 접근 방식을 고려해야 하는 경우도 있지만, 지금까지 경험에 비춰보면, 병목을 일으키는 부분은 프로파일러를 돌려서 정확하게 파악한 다음에 해결하는 게 대부분 좋기 때문에 처음부터 그럴 필요는 없을 것 같습니다.
참고로 현재 개발 중인 GTK+ 3.0에서도 기존에 공개되었던 변수들을 모조리 안으로 숨기고, GTK+ 2.x 어플리케이션의 이전(migration)을 위해 GSEAL() 매크로를 2.14 버전부터 제공하고 있습니다.
아무튼 그래서, 일단 지난 글에서 예제로 사용한 호스트 객체의 필드를 숨기고 접근 API를 구현해 보았습니다. (변경되거나 수정한 부분만 보여드립니다)
edc-host.h
typedef struct _EdcHostClass EdcHostClass;
typedef struct _EdcHost EdcHost;
struct _EdcHost
{
GObject parent;
};
struct _EdcHostClass
{
GObjectClass parent_class;
};
GType edc_host_get_type (void) G_GNUC_CONST;
EdcHost *edc_host_new (void);
gchar *edc_host_get_name (EdcHost *host);
void edc_host_set_name (EdcHost *host,
const gchar *name);
gchar *edc_host_get_address (EdcHost *host);
void edc_host_set_address (EdcHost *host,
const gchar *address);
gint edc_host_get_port (EdcHost *host);
void edc_host_set_port (EdcHost *host,
gint port);
gchar *edc_host_get_user (EdcHost *host);
void edc_host_set_user (EdcHost *host,
const gchar *user);
gchar *edc_host_get_password (EdcHost *host);
void edc_host_set_password (EdcHost *host,
const gchar *password);edc-host.c
#include "edc-host.h"
typedef struct _EdcHostPrivate EdcHostPrivate;
struct _EdcHostPrivate
{
gchar *name;
gchar *address;
gint port;
gchar *user;
gchar *password;
};
#define EDC_HOST_GET_PRIVATE(host) \
G_TYPE_INSTANCE_GET_PRIVATE (host, EDC_TYPE_HOST, EdcHostPrivate)
G_DEFINE_TYPE (EdcHost, edc_host, G_TYPE_OBJECT);
EdcHost *
edc_host_new (void)
{
return EDC_HOST (g_object_new (EDC_TYPE_HOST, NULL));
}
/* object initializer */
static void
edc_host_init (EdcHost *host)
{
EdcHostPrivate *priv;
priv = EDC_HOST_GET_PRIVATE (host);
priv->name = g_strdup ("");
priv->address = g_strdup ("");
priv->port = 0;
priv->user = g_strdup ("");
priv->password = g_strdup ("");
}
/* object finalizer */
static void
edc_host_finalize (GObject *self)
{
EdcHost *host = EDC_HOST (self);
EdcHostPrivate *priv;
priv = EDC_HOST_GET_PRIVATE (host);
g_free (priv->name);
g_free (priv->address);
g_free (priv->user);
g_free (priv->password);
/* call our parent method (always do this!) */
G_OBJECT_CLASS (edc_host_parent_class)->finalize (self);
}
/* class initializer */
static void
edc_host_class_init (EdcHostClass *klass)
{
GObjectClass *gobj_class;
gobj_class = G_OBJECT_CLASS (klass);
gobj_class->finalize = edc_host_finalize;
g_type_class_add_private (gobj_class, sizeof (EdcHostPrivate));
}
gchar *
edc_host_get_name (EdcHost *host)
{
EdcHostPrivate *priv;
g_return_val_if_fail (EDC_IS_HOST (host), NULL);
priv = EDC_HOST_GET_PRIVATE (host);
return g_strdup (priv->name);
}
void
edc_host_set_name (EdcHost *host,
const gchar *name)
{
EdcHostPrivate *priv;
g_return_if_fail (EDC_IS_HOST (host));
g_return_if_fail (name != NULL);
priv = EDC_HOST_GET_PRIVATE (host);
g_free (priv->name);
priv->name = g_strdup (name);
}먼저 헤더 파일을 보면, EdcHost 구조체에서 공개되었던 객체 변수가 모두 사라지고, 대신
edc_host_{get,set}_* () 형태의 API 선언이 추가되었습니다. 소스 파일에는 새로 EdcHostPrivate 구조체를 정의하고 모든 비공개 변수를 집어 넣은 뒤, 클래스 초기화 함수[edc_host_class_init ()] 마지막 부분에서 이 크기만큼의 공간을 확보하도록 합니다.[g_type_class_add_private()] 그리고 모든 함수에서 이 구조체를 쉽게 얻어오기 위해 정의한EDC_HOST_GET_PRIVATE() 매크로를 사용해 필요한 작업을 수행합니다.부가적으로 조금만 더 설명하면, 모든 문자열을 넘겨주는 API는 문자열을 복사해서 넘겨주어 원본 문자열을 보호합니다. 따라서 API 문서에 넘겨받은 문자열을 반드시 해제하라고 명시되어 있어야 하겠죠. 또한 지난 글에서 잠시 언급한 것처럼, 공개된 함수 진입 시점에서 인수 적합성 검사를 할때
EDC_IS_HOST() 매크로를 사용해 NULL 여부 뿐 아니라 정확하게 해당 객체인지 검사하도록 합니다.참고로 위 예제에서 비공개(private) 객체에 접근하기 위해 사용한 방식은 오버헤드가 조금 있어서 성능이 중요한 경우 조금 다른 방식을 사용해야 합니다. 이에 대한 내용은 이전 포스트를 참고하시기 바랍니다.
이렇게 해서 기본적인 객체 속성에 대한 접근자를 구현했습니다. 물론 이게 다는 아니고, 다음에 설명할 GObject 속성(properties) 기능을 이용하면 사실 접근자를 구현할 필요도 없습니다. 하지만, GTK+와 같은 대부분의 GObject 기반 객체는 함수 API 기반의 접근자를 동시에 제공하고 있으므로 관례를 따르는 게 나쁘지는 않겠지요.
글머리에서 언급했듯이, 계속 적다 보면 내용도 길어지고 포스팅 주기도 길어질 것 같아 오늘은 일단 여기까지만 적습니다. 다음에는 본격적으로 GObject 속성(properties)을 추가할 예정인데, 설명할 게 많아서... ;)
(이 글은 개인 블로그에 함께 게재되어 있습니다)
피드 구독하기:
글 (Atom)