11/17/2011

如何正確停止「live555 RTSP串流伺服器」

前一篇文章裡,我介紹如何利用live555撰寫RTSP串流伺服器。文章的原始碼範例,並無針對一些例外訊號例如:「Ctrl+C(SIGINT)」進行捕捉處理。 在Linux中,這些例外訊號若沒有處理,其預設行為將是立刻終止程式,進而導致資源遺漏(resource leak)。
在Linux裡,我們可以透過signal()於程式中設立處理常式(signal handler),於對應之處理常式中,我們將可正確地停止RTSP伺服器。

11/06/2011

YAML簡介

什麼是YAML
YAML是YAML Ain't Markup Language的遞迴縮寫(一種來自Opensource界的奇怪幽默,如果你懂,你就...)。顧名思義,它設計的目的是作為表達、傳遞資料的一種資料描述方式(data serialization language)。與XML或類似的用途的語言相比,它著重於資料於人們的可(易)閱度讀性。
例如,以下兩個YAML的例子:
--- # 最喜愛的電影
- Casablanca
- North by Northwest
- Notorious
-
  name: Mark McGwire
  hr:   65
  avg:  0.278
-
  name: Sammy Sosa
  hr:   63
  avg:  0.288

11/04/2011

移植lwIP至U-Boot

U-Boot是嵌入式系統上被廣為運用的boot loader,它擁有極為活躍的開發社群,也支援許多不同類型的CPU核心架構。U-Boot目前並不支援完整的TCP/IP,僅有限度地支援TFTP,方便開發者上傳韌體或是網路開機(BOOTP)。U-Boot身為boot loader非嵌入式作業系統,依據開放原始碼社群秉持地「精簡就是美」原則,不支援TCP/IP是可以理解的。萬一真的需要網路功能,移植一套完整的TCP/IP協定堆疊的任務雖然富有挑戰性,實務上亦是可行之道。

lightweight IP(lwIP)是一個輕巧的開放原始碼之TCP/IP通訊協定之實作。lwIP首先由Adam Dunkels發表,目前則是以Kieran Mansley為計畫主持人,帶領開放原始碼社群進行該專案的維護與開發。
目前lwIP已實做的協定如下:
  • IP (Internet Protocol) including packet forwarding over multiple network interfaces
  • ICMP (Internet Control Message Protocol) for network maintenance and debugging
  • IGMP (Internet Group Management Protocol) for multicast traffic management
  • UDP (User Datagram Protocol) including experimental UDP-lite extensions
  • TCP (Transmission Control Protocol) with congestion control, RTT estimation and fast recovery/fast retransmit
  • Raw/native API for enhanced performance
  • DNS (Domain names resolver)
  • SNMP (Simple Network Management Protocol)
  • DHCP (Dynamic Host Configuration Protocol)
  • AUTOIP (for IPv4, conform with RFC 3927)
  • PPP (Point-to-Point Protocol)
  • ARP (Address Resolution Protocol) for Ethernet
  • Optional Berkeley-like socket API 
lwIP設計理念主要是希望於支援TCP/IP協定的同時,於執行時期也能精簡記憶體之運用,以達成支援小型的嵌入式裝置、平台或即時作業系統(RTOS)的目標;除此之外,lwIP也已經移植到眾多RTOS平台上,例如:eCos...等等。由此這些特性來看,使用lwIP搭配U-Boot是一個好的選擇。

9/04/2011

How to Install Ubuntu11.04 on Mac mini

This guide will explain how to install Ubuntu 11.04 on an external USB HDD.
  1. Download and install rEFIt under OSX.
  2. Insert Ubuntu Installation CD to an external USB CD drive.
  3. Reboot and hold Alt key to boot from the CD drive.
  4. Before installing Ubuntu, press F6 and select each options except the last one.
  5. Some options, noapci nopic nomodeset, should be turned off otherwise Linux will halt during the installation.There options would be disabled as well after the installation.
  6. Install Ubuntu on the USB HDD drive and do remember to install grub on the USB HDD as well.
  7. After the first reboot of installation, rEFIt should detect Linux.
  8. Choice to boot from Linux, GRUB will be loaded.
  9. Press "E" to edit boot options of GRUB.
  10. Find and add options to Linux boot arguments:
    linux /boot/vmlinuz-2.6.32-24-generic \
    root=UUID=xxxx-xxx-xxx-xxxx ro quiet \
    nosplash noapci nopci nomodeset reboot=apci
    
    Press Ctrl+X to boot Ubuntu after the modification.
  11. Install NVIDIA proprietary driver to enable 3D/OpenGL.
  12. Enjoy Ubuntu 11.04.

9/03/2011

How to transfer Ununtu11.04/Pandaboard to USB HDD

The pre-built Ubuntu 11.04/Pandaboard image is designed to be installed on a SD card. It could be transfer to the USB HDD with some extra efforts. Typically, a bootable OMAP4/Ubuntu SD card contains two partitions: boot and rootfs. Only rootfs should immigrate to a USB drive since Pandaboard requires booting form SD card.
  1. Prepares a bootable Ubuntu11.04 SD card. howto
  2. Create a ext3/4 partition on a USB HDD labeled "ubuntu".
  3. # /dev/sdXX should be the partition to be formatted,
    # for example /dev/sdc2
    $ sudo mkfs.ext3 -L ubuntu /dev/sdXX
    
  4. Copy rootfs on the SD card to USB HDD
  5. # Suppose USB is mounted on /mnt/ubuntu
    # and rootfs of SD is on /mnt/sd
    $ cd /mnt/ubuntu
    $ sudo cp -a /mnt/sd/* .
    
  6. Boot from SD card
  7. In order to mount USB HDD as rootfs, It'd update parameters that passed to the kernel. The configuration file is located at /boot/boot.script
    $ sudo vi /boot/boot.script
    
    You'd find bootargs have something like this:
    root=UUID=XXXX-XXXX-XXXX-XXXX
    
    Replace it to
    root=LABEL=ubuntu
    
    Then update boot.scr for uboot using:
    $ sudo flash-kernel
    
  8. Reboot it and Ubuntu should use USB HDD as root filesystem. :)

8/31/2011

To install Ubuntu 11.04 on PandaBoard

1. Download a prebuilt Ubuntu 11.04 image from here.
2. Create a bootable SD card(capacity >= 4GB).
    zcat ubuntu-11.04-preinstalled-netbook-armel+omap4.img.gz | \
    sudo dd of=/dev/sdX
    ## sdX should be the actual device node of SD card
3. Bring up PandaBoard with the SD card and enjoy it.

6/28/2011

To cross-compile glib to arm-linux-gnueabi platform

Some variables should be given during configuration stage in order to cross-compile glib to ARM/linux platform using the tool chain(s) that come from Linaro or CodeSourcery.
> export glib_cv_stack_grows=no; \
  export glib_cv_uscore=no; \
  export ac_cv_func_posix_getpwuid_r=no; \
  export ac_cv_func_posix_getgrgid_r=no; \
  CFLAGS=-I$(TARGET_DIR)/usr/include \
  LDFLAGS=-L$(TARGET_DIR)/usr/lib \
  ./configure --host=arm-linux-gnueabi --prefix=$(TARGET_DIR)/usr

More information can be found at here.

6/25/2011

GObject 入門 (Part 7) - 發送與接收訊息

訊息發送系統(messaging system)是GObject system內建功能之一,它讓物件於事件發生時,用來發送訊息給其他註冊該訊息者。這樣的設計可以避免系統或物件過於耦合(coupling),大型系統裡常可見到這樣的設計(Mediator pattern)。


在GObject system裡,物件(GObject)可透過g_signal_new()g_signal_newv()註冊新的信號;並以g_signal_emit()發送信號。爾後,經由g_signal_connect()即可連結(connect)或接收某物件發出的信號。

6/19/2011

Compressing Images into WebP format using libwebp

libwebp is a software implementation of WebP, a lossy compression picture format which is introduced by Google. It provides a set of lean APIs that are easy to encode and decode WebP images. libwebp comes with two demo program: cwebp and dwebp. It could use cwebp to transform JPEG/ PNG images into WebP format.  And dwebp takes decoding jobs in contrast.
cwebp is a good example to study encoder part of libwebp. Here is a handy class to do the same job.

WebP/HTTP Push on Google Chrome

Google Chrome browser, and it borther Chromium browser, seems the only web browser that supports WebP server push at this moment. Opera 11 can display WebP image frame by frame but not in server push mode according to my experiments. By the way, I found that Opera 11 cannot recognize MIME type: image/webp.

6/18/2011

JPEG/HTTP PUSH how-to

JPEG/HTTP server side push is done by adopting "multipart/x-mixed-replace" MIME type. More detail information could be found on Wikipedia:

HTTP server push
HTTP server push (also known as HTTP streaming) is a mechanism for sending data from a web server to a web browser. HTTP server push can be achieved through several mechanisms.
Generally the web server does not terminate a connection after response data has been served to a client. The web server leaves the connection open such that if an event is received, it can immediately be sent to one or multiple clients. Otherwise the data would have to be queued until the client's next request is received. Most web servers offer this functionality via CGI (e.g. Non-Parsed Headers scripts on Apache).
Another mechanism is related to a special MIME type called multipart/x-mixed-replace, which was introduced by Netscape in 1995. Web browsers would interpret this as a document changing whenever the server felt like pushing a new version to the client.[1] It is still supported by FirefoxOpera and Safari today, but ignored by Internet Explorer.[2] It can be applied to HTML documents, but also for streaming images in webcam applications.
The WHATWG Web Applications 1.0 proposal[3] included a mechanism to push content to the client. On September 1, 2006, the Opera web browser implemented this new experimental technology in a feature called "Server-Sent Events."[4][5] It is now being standardized as part of HTML5.[6] Another related part of HTML5 is the WebSockets API, which allows a web server and client to communicate over a full-duplex TCP connection. 

Streaming JPEG images over RTP using live555 streaming media

Live555 streaming media  is a software library that provides RTP/RTSP streaming features mainly. It supports many popular video and audio codecs. At this moment, JPEG is still the most popular lossy compression image format. It says that Live555 supports JPEG as well but not directly, see herehere and here. Live555 implements JPEG's RTP payload however it doesn't come with any JPEG parser. According to common  Live555 streaming media programming flow, a user that gonna to stream JPEG images through  it should(here):
(i) "JPEGVideoRTPSink", which will be fed by
(ii) a *subclass* of "JPEGVideoSource".

6/17/2011

To cross compile Live555 streaming media for ARM/Linux

To compile Live555 streaming media for ARM/Linux is quite easy.
It needs only few steps:
> cd live
> ./genMakefiles armlinux
> make

Pre-built binary package:
live.2011.06.16-dev-armlinux.tar.bz2

6/12/2011

使用live555 streaming media建立RTSP串流伺服器

Live555 Streaming Media是一套優秀的開放程式碼函示庫,擁有完整的RTSP/RTP實作,除了支援RTP over UDP外,尚支援RTP over RTSP以及RTP/RTSP over HTTP,可用於傳輸多種主流的影音壓縮格式如:H.264、MPEG4、MP3....等。知名的播放軟體VLC的串流功能就是基於此函式庫開發。
利用它我們只要短短幾行程式碼即可建立自己的串流伺服器。

6/11/2011

GObject 入門 (Part 6) - 宣告及實作介面

GObject system僅允許「單一繼承加上多重介面實作」的類別宣告方式。這種方式相信對熟悉Java或C#的人來說並不陌生,單一繼承的顧名思義是指一個類別僅能繼承自一種父類別,在這種物件模型下,有時會讓物件的抽象化變得比較麻煩。舉例來說:如果我們的想要將前幾篇短文提及的圖形(shape)系統導入畫圖的功能,讓Rectangle與Circle類別都能於顯示器上畫出形狀,最直覺的方式當然就是在Shape類別宣告一個方法,姑且命名為draw(),再由Rectangle與Circle類別分別改寫(override)該函式,即可輕鬆達到目的;倘若Shape類別與Rectangle、Circle類別是兩組人馬開發維護,而Rectangle、Circle類別開發小組無法自由修改Shape的定義,前述的方法就變得不可行,僅能設法於Rectangle、Circle類別分別宣告並實作draw函式,這樣一來draw()就無法擁有物件導向系統多型(polymorphism)的益處,喪失執行時期的動態連結(dynamic binding)能力。為了彌補這個缺點擺脫單一繼承的限制,GObject system讓使用者可以隨意添加多個介面(interface),交由類別實作之。在此,可將介面(interface)想像成特化的類別,它僅能宣告法方但不能包含類別資料,但亦擁有執行時期的類別驗證(RTTI, Runtime Type Inedtification)的能力。

6/09/2011

GObject 入門 (Part 5) - 存取物件屬性

一般來說,在C語言允許使用者直接存取資料結構(data structure)的成員(member),前提是使用者必須知道該資料結構的定義(definition),當資料被封裝起來時,使用者就必須可能就必須透過額外的介面存取。例如以下情況:
... ... ...

typedef struct _CirclePrivate CirclePrivate;

    ... ... ...

struct _Circle
{
    Shape parent;

    CirclePrivate* priv;
};

struct _CircleClass
{
    ShapeClass parent;
};

    ... ... ...

void  circle_set_radius(Circle* circle, guint radius);
guint circle_get_radius(Circle* circle);

    ... ... ...
在上段程式碼中,CirclePrivate僅提供型別的宣告(declaration)並不提供定義(definition),使用者無法直接存取priv變數的內容,面對此不公開(opaque)的資料結構,使用者被迫只能靠程式介面來存取資料內容(在上面的例子中就是circle_set_radius與circle_get_radius),如此一來原設計者就輕易達成資料封裝的目的。
GObject system實做了一個泛用的物件屬性讀寫機制(generic get/set mechanism for object properties),此機制可讓使用者在知道物件屬性的名稱的狀況下透過GObject的g_object_get_property讀取或以g_object_set_property寫入資料。這套機制很強大,但同時也容易令人卻步,因為如果你設計的類別要支援這個功能,將會需要額外撰寫不少如裹腳布般的程式碼,況且GObject system也沒有規定一定要實作此功能程式才能運作,像前幾篇短文(GObject 入門 Part2Part3Part4)中提出的範例並無相關實作亦能正常運行無誤,基於程式設計族群懶惰的天性,很多人就當沒這回事。是否一定要在專案中導入此功能個人覺得可以審慎評估,若是想要降低專案中各元件的耦合度(coupling degree)或是為將來方便撰寫其它程式語言連結(binding to other language)鋪路,此功能值得深思,像GStreamer架構中允許元件(plugin)於執行時期載入並動態設定參數的特性,依靠的就是GObject提供的功能。GStreamer將GLIb與GObject發揮得淋漓盡致,本身功能亦十分強大,將來會另有專題介紹。:)

6/06/2011

GObject 入門 (Part 4) - 物件的建構與解構

基本上所有屬於GObject之物件均配置於堆疊(heap)上,加之GObject system本身並無記憶體垃圾回收(memory garbage collection)機制,所以物件的管理就要稍微留心,不然可能會產生記憶體洩漏(memory leakage),甚至資源洩漏(resource leakage)的危機。

參考計數器(reference counter)
為了追蹤物件的生命週期以有效地進行記憶體管理(memory management),GObject引用一套參考計數器(reference counter)達成上述目的。它的運作原理並不複雜,當每個GObject物件產生後(透過g_object_new),該物件之參考計數(reference counter)為1,之後物件就可不斷被呼叫、操作,當物件之參考計數(reference count)歸零時,GObject system將立即解構(destruction)該物件。在這期間,使用該物件者可透過g_object_ref增加該物件的參考數(reference counter),以防該物件被突然解構;反之也可以由g_object_unref解除對該物件的參考。所有GNOME下運用到GObject的函式庫,例如GTK+、GStreamer等都大量運用這種方式進行物件管理。
在上一篇的例子裡,就有這樣的使用範例。
int main(int argc, char** argv)
{
    Rectangle* rect;
    Circle* circle;
 
    g_type_init();
 
    rect = rectangle_new(4, 5);
    g_print("Area of Rectangle(0x%08x) is %u.\n",
            (guint)rect, shape_calculate_area(SHAPE(rect)));
    rectangle_free(rect);
 
    circle = circle_new(6);
    g_print("Area of Circle(0x%08x) is %u.\n",
            (guint)circle, shape_calculate_area(SHAPE(circle)));
    circle_free(circle);
 
    return 0;
}
... ... ... ...

void rectangle_free(Rectangle* rect)
{
    g_assert(rect != NULL);
    g_return_if_fail(IS_RECTANGLE(rect));
 
    g_object_unref(G_OBJECT(rect));
}

    ... ... ... ...

void circle_free(Circle* circle)
{
    g_assert(circle != NULL);
    g_return_if_fail(IS_CIRCLE(circle));
 
    g_object_unref(G_OBJECT(circle));
}

    ... ... ... ...
其中,rectangle_free()與circle_free()都包裝了g_object_unref(),在本例中這僅僅是吾人為了符合C programming language的使用慣例。如果去深入追蹤GTK或是GStreamer的原始碼,可以發現程式碼中對g_object_unref()之直接呼叫。若今日系統欲搭配其他函式庫可以試著變更如下,以配合其他函式庫的使用慣例。
int main(int argc, char** argv)
{
    Rectangle* rect;
    Circle* circle;
 
    g_type_init();
 
    rect = rectangle_new(4, 5);
    g_print("Area of Rectangle(0x%08x) is %u.\n",
            (guint)rect, shape_calculate_area(SHAPE(rect)));
    g_object_unref(G_OBJECT(rect));
 
    circle = circle_new(6);
    g_print("Area of Circle(0x%08x) is %u.\n",
            (guint)circle, shape_calculate_area(SHAPE(circle)));
    g_object_unref(G_OBJECT(circle));

    return 0;
}
值得一提的是,GObject system的實作方式有時會讓C programming language編譯時期(compilation time)的型別檢定(type checking)無法運作,導致編譯器(compiler)抱怨連連,你可以選擇忽略或是強制轉型(type casting)以滿足編譯器的型別檢查機制。

GObject物件之建構
當屬於物件的記憶體被配置後,GInstanceInit()會立即被呼叫以進行初始化(initialization),接著建構子(constructor)會喚起,至此物件的建構就大致完成了。以下節錄自官方文件的說明。


Once g_object_new has obtained a reference to an initialized class structure, it invokes its constructor method to create an instance of the new object. Since it has just been overridden by maman_bar_class_init tomaman_bar_constructor, the latter is called and, because it was implemented correctly, it chains up to its parent's constructor. In order to find the parent class and chain up to the parent class constructor, we can use the maman_bar_parent_class pointer that has been set up for us by the G_DEFINE_TYPE macro.
Finally, at one point or another, g_object_constructor is invoked by the last constructor in the chain. This function allocates the object's instance' buffer through g_type_create_instance which means that the instance_init function is invoked at this point if one was registered. After instance_init returns, the object is fully initialized and should be ready to answer any user-request. When g_type_create_instance returns,g_object_constructor sets the construction properties (i.e. the properties which were given tog_object_new) and returns to the user's constructor which is then allowed to do useful instance initialization...
GObject物件之解構
當物件的參考計數(reference counter)歸零後,GObject會將解構(destruction)。在概念上,解構流程與物件的建構流程相反,解構子(destructor)會先被喚起,最後將記憶體歸還堆疊(heap)。話雖如此,在GObject system中,其實並無明確的解構子(destructor)與建構子(constructor)對應,GObject將解構流程拆分為dispose與finalize兩個動作。如果物件成員(object's member)有參考到其他物件,在dispose階段,要記得解除對該物件的參考( 說白了就是呼叫g_object_unref);在finalize階段可以釋放針對物件配置的資源。

... the first phase, executed in the dispose handler is supposed to release all references to other member objects. The second phase, executed by the finalize handler is supposed to complete the object's destruction process. Object methods should be able to run without program error (that is, without segfault :) in-between the two phases.
關於上面的敘述,直接參考官方文件上的範例就可一目了然。
... ... ... ...

static void maman_bar_dispose(GObject *gobject)
{
  MamanBar *self = MAMAN_BAR (gobject);

  /* 
   * In dispose, you are supposed to free all types referenced from this
   * object which might themselves hold a reference to self. Generally,
   * the most simple solution is to unref all members on which you own a 
   * reference.
   */

  /* dispose might be called multiple times, so we must guard against
   * calling g_object_unref() on an invalid GObject.
   */
  if (self->priv->an_object)
    {
      g_object_unref (self->priv->an_object);

      self->priv->an_object = NULL;
    }

  /* Chain up to the parent class */
  G_OBJECT_CLASS (maman_bar_parent_class)->dispose (gobject);
}

static void maman_bar_finalize(GObject *gobject)
{
  MamanBar *self = MAMAN_BAR (gobject);

  g_free (self->priv->a_string);

  /* Chain up to the parent class */
  G_OBJECT_CLASS (maman_bar_parent_class)->finalize (gobject);
}

    ... ... ... ...
綜合以上內容,試著將Shape、Rectangle與Circle之實作補充得更完整。在此列舉Circle之內容。
... ... ... ...

static gpointer circle_parent_class = NULL;

... ... ... ...

static void circle_class_init(CircleClass* klass)
{
    g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);

    circle_parent_class = g_type_class_peek_parent(klass);

    G_OBJECT_CLASS(klass)->constructor = circle_constructor;
    G_OBJECT_CLASS(klass)->dispose     = circle_dispose;
    G_OBJECT_CLASS(klass)->finalize    = circle_finalize;

    SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;

    g_type_class_add_private(klass, sizeof(CirclePrivate));
}

static GObject* circle_constructor(GType                  gtype,
                                   guint                  n_properties,
                                   GObjectConstructParam* properties)
{
    GObject* object;

    {
        /* Always chain up to the parent constructor */
        object = G_OBJECT_CLASS(circle_parent_class)->constructor(gtype,
                                                                  n_properties,
                                                                  properties);
    }

    /* update the object state depending on constructor properties */

    g_print("Circle(0x%08x)::constructor\n", (guint)object);

    return object;
}

static void circle_dispose(GObject* object)
{
    g_print("Circle(0x%08x)::dispose\n", (guint)object);

    /* Chain up to the parent class */
    G_OBJECT_CLASS(circle_parent_class)->dispose(object);
}

static void circle_finalize(GObject* object)
{
    g_print("Circle(0x%08x)::finalize\n", (guint)object);

    /* Chain up to the parent class */
    G_OBJECT_CLASS(circle_parent_class)->finalize(object);
}

    ... ... ... ...
執行結果:

原始碼下載

6/05/2011

GObject 入門 (Part 3) - 繼承類別

了解GObject system如何管理物件生命週期後,我們對GObject system裡宣告類別的方式就比較容易理解了。在第一篇已經宣告了Shape型別,接著可將Rectangle與Circle實作出來。
Rectangle型別宣告如下:
rectangle.h
#ifndef __GOBJECT_TEST_RECTANGLE_CLASS_INCLUDED__
#define __GOBJECT_TEST_RECTANGLE_CLASS_INCLUDED__


#include <glib-object.h>

#include "shape.h"


G_BEGIN_DECLS


#define RECTANGLE_TYPE (rectangle_get_type())
#define IS_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), RECTANGLE_TYPE))
#define RECTANGLE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), RECTANGLE_TYPE, Rectangle))
#define RECTANGLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), RECTANGLE_TYPE, RectangleClass))
#define IS_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), RECTANGLE_TYPE))
#define RECTANGLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), RECTANGLE_TYPE, RectangleClass))


typedef struct _Rectangle        Rectangle;
typedef struct _RectangleClass   RectangleClass;
typedef struct _RectanglePrivate RectanglePrivate;


struct _Rectangle
{
    Shape parent;

    RectanglePrivate* priv;
};

struct _RectangleClass
{
    ShapeClass parent;
};


GType      rectangle_get_type();

Rectangle* rectangle_new(guint width, guint height);
void       rectangle_free(Rectangle* rect);

void  rectangle_set_width(Rectangle* rect, guint width);
void  rectangle_set_height(Rectangle* rect, guint height);
guint rectangle_get_width(Rectangle* rect);
guint rectangle_get_height(Rectangle* rect);


G_END_DECLS


#endif /* __GOBJECT_TEST_RECTANGLE_CLASS_INCLUDED__ */
rectangle.c
#include "rectangle.h"


struct _RectanglePrivate
{
    guint width;
    guint height;
};
#define RECTANGLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \
                                    RECTANGLE_TYPE, RectanglePrivate))


static void rectangle_base_init(RectangleClass* klass);
static void rectangle_base_finalize(RectangleClass* klass);
static void rectangle_class_init(RectangleClass* klass);
static void rectangle_init(Rectangle* rect);

static guint rectangle_calculate_area(Rectangle* self);


GType rectangle_get_type()
{
    static GType type = 0;

    if (type == 0) {
        static const GTypeInfo info = {
            sizeof(RectangleClass),
            (GBaseInitFunc)rectangle_base_init,
            (GBaseFinalizeFunc)rectangle_base_finalize,
            (GClassInitFunc)rectangle_class_init,
            NULL,
            NULL,
            sizeof(Rectangle),
            0,
            (GInstanceInitFunc)rectangle_init,
            NULL
        };

        type = g_type_register_static(SHAPE_TYPE, "Rectangle", &info, 0);
    }

    return type;
}

static void rectangle_base_init(RectangleClass* klass)
{
    g_print("Rectangle(0x%08x)::base::initialize\n", (guint)klass);
}

static void rectangle_base_finalize(RectangleClass* klass)
{
    g_print("Rectangle(0x%08x)::base::finalize\n", (guint)klass);
}

static void rectangle_class_init(RectangleClass* klass)
{
    g_print("Rectangle(0x%08x)::class::initialize\n", (guint)klass);

    SHAPE_CLASS(klass)->calculateArea = (void*)rectangle_calculate_area;

    g_type_class_add_private(klass, sizeof(RectanglePrivate));
}

static void rectangle_init(Rectangle* self)
{
    RectanglePrivate* priv;

    g_print("Rectangle(0x%08x)::instance::initialize\n", (guint)self);

    self->priv = priv = RECTANGLE_GET_PRIVATE(self);

    priv->width  = 0;
    priv->height = 0;
}

Rectangle* rectangle_new(guint width, guint height)
{
    Rectangle* rect;

    rect = g_object_new(RECTANGLE_TYPE, NULL);
    g_assert(rect != NULL);

    rect->priv->width  = width;
    rect->priv->height = height;

    g_print("Rectangle(0x%08x, %ux%u) is created.\n",
            (guint)rect, rect->priv->width, rect->priv->height);

    return rect;
}

void rectangle_free(Rectangle* rect)
{
    g_assert(rect != NULL);
    g_return_if_fail(IS_RECTANGLE(rect));

    g_object_unref(G_OBJECT(rect));
}

static guint rectangle_calculate_area(Rectangle* self)
{
    g_print("Rectangle(0x%08x)::calculateArea\n", (guint)self);

    return self->priv->width * self->priv->height;
}

void rectangle_set_width(Rectangle* rect, guint width)
{
    g_return_if_fail(IS_RECTANGLE(rect));

    rect->priv->width = width;
}

void rectangle_set_height(Rectangle* rect, guint height)
{
    g_return_if_fail(IS_RECTANGLE(rect));

    rect->priv->height = height;
}

guint rectangle_get_width(Rectangle* rect)
{
    g_assert(IS_RECTANGLE(rect));

    return rect->priv->width;
}

guint rectangle_get_height(Rectangle* rect)
{
    g_assert(IS_RECTANGLE(rect));

    return rect->priv->height;
}
Circle型別宣告如下:
circle.h
#ifndef __GOBJECT_TEST_CIRCLE_CLASS_INCLUDED__
#define __GOBJECT_TEST_CIRCLE_CLASS_INCLUDED__


#include <glib-object.h>

#include "shape.h"


G_BEGIN_DECLS


#define CIRCLE_TYPE (circle_get_type())
#define IS_CIRCLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), CIRCLE_TYPE))
#define CIRCLE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), CIRCLE_TYPE, Circle))
#define CIRCLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), CIRCLE_TYPE, CircleClass))
#define IS_CIRCLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), CIRCLE_TYPE))
#define CIRCLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), CIRCLE_TYPE, CircleClass))


typedef struct _Circle        Circle;
typedef struct _CircleClass   CircleClass;
typedef struct _CirclePrivate CirclePrivate;


struct _Circle
{
    Shape parent;

    CirclePrivate* priv;
};

struct _CircleClass
{
    ShapeClass parent;
};


GType      circle_get_type();

Circle* circle_new(guint radius);
void    circle_free(Circle* circle);

void  rectangle_set_radius(Circle* circle, guint radius);
guint rectangle_get_radius(Circle* circle);


G_END_DECLS


#endif /* __GOBJECT_TEST_CIRCLE_CLASS_INCLUDED__ */
circle.c
#include "circle.h"


struct _CirclePrivate
{
    guint radius;
};
#define CIRCLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \
                                 CIRCLE_TYPE, CirclePrivate))


static void circle_base_init(CircleClass* klass);
static void circle_base_finalize(CircleClass* klass);
static void circle_class_init(CircleClass* klass);
static void circle_init(Circle* circle);
static guint circle_calculate_area(Circle* self);


GType circle_get_type()
{
    static GType type = 0;

    if (type == 0) {
        static const GTypeInfo info = {
            sizeof(CircleClass),
            (GBaseInitFunc)circle_base_init,
            (GBaseFinalizeFunc)circle_base_finalize,
            (GClassInitFunc)circle_class_init,
            NULL,
            NULL,
            sizeof(Circle),
            0,
            (GInstanceInitFunc)circle_init,
            NULL
        };

        type = g_type_register_static(SHAPE_TYPE, "Circle", &info, 0);
    }

    return type;
}

static void circle_base_init(CircleClass* klass)
{
    g_print("Circle(0x%08x)::base::initialize\n", (guint)klass);
}

static void circle_base_finalize(CircleClass* klass)
{
    g_print("Circle(0x%08x)::base::finalize\n", (guint)klass);
}

static void circle_class_init(CircleClass* klass)
{
    g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);

    SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;

    g_type_class_add_private(klass, sizeof(CirclePrivate));
}

static void circle_init(Circle* self)
{
    CirclePrivate* priv;

    g_print("Circle(0x%08x)::instance::initialize\n", (guint)self);

    self->priv = priv = CIRCLE_GET_PRIVATE(self);

    priv->radius = 0;
}

Circle* circle_new(guint radius)
{
    Circle* circle;

    circle = g_object_new(CIRCLE_TYPE, NULL);
    g_assert(circle != NULL);

    circle->priv->radius = radius;

    g_print("Circle(0x%08x, r=%u) is created.\n",
            (guint)circle, circle->priv->radius);

    return circle;
}

void circle_free(Circle* circle)
{
    g_assert(circle != NULL);
    g_return_if_fail(IS_CIRCLE(circle));

    g_object_unref(G_OBJECT(circle));
}

static guint circle_calculate_area(Circle* self)
{
    g_print("Circle(0x%08x)::calculateArea\n", (guint)self);

    return self->priv->radius * self->priv->radius * 3;
}

void circle_set_radius(Circle* circle, guint radius)
{
    g_return_if_fail(IS_CIRCLE(circle));

    circle->priv->radius = radius;
}

guint circle_get_width(Circle* circle)
{
    g_assert(IS_CIRCLE(circle));

    return circle->priv->radius;
}
上面的程式碼有幾個比較值得注意的地方。首先,由於Rectangle與Circle計算面積的方法不同,所以他們都必須覆寫(override)父類別(parent class)的calculateArea()方法。覆寫的最簡單方式就是在型別初始化時就設定正確的函數指標(function pointer),所以依據上一篇提過的觀念,於GClassInit()被喚起時進行設定是較正確的作法,也是官方文件建議的作法。當然,也可以選擇在GBaseInit()被喚起時設定函數指標(function pointer),但採取這種方式會增加些許程式碼撰寫的工程,觀念上亦與GObject system的架構略有不符。
... ... ... ...

static void circle_class_init(CircleClass* klass)
{
    g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);

    SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;

    g_type_class_add_private(klass, sizeof(CirclePrivate));
}

    ... ... ... ...
此外,上面的程式碼亦導入RectanglePrivate與CirclePrivate結構,他們在標頭檔(header file)裡只有宣告沒有實作,這是一個常用的設計手法,透過這種方式,類別的資料內容可以被封裝保護得更完整,缺點是會增加程式撰寫的難度。GObject system建議但並不強迫使用這種作法,如果在類別宣告裡使用此作法,GObject system對類別私有資料的命名慣例為priv(不要使用 private,因為此名稱為C++的關鍵字)。至於私有資料結構的產生,只要於GClassInit時呼叫g_type_class_add_private(),爾後在該型別的物件初始化時期,GObject將自動加以配置;當該物件銷毀時,也會連帶註銷。至於物件可透過G_TYPE_INSTANCE_GET_PRIVATE()取得屬於該私有資料的指標。
... ... ... ...

static void circle_class_init(CircleClass* klass)
{
    g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);

    SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;

    g_type_class_add_private(klass, sizeof(CirclePrivate));
}

static void circle_init(Circle* self)
{
    CirclePrivate* priv;

    g_print("Circle(0x%08x)::instance::initialize\n", (guint)self);

    self->priv = priv = CIRCLE_GET_PRIVATE(self);

    priv->radius = 0;
}

    ... ... ... ...
最後,我們可以增添一個介面叫shape_calculate_area(),針對不同的Sahpe子類別(subclass)呼叫相應的calculateArea()。
... ... ... ...

guint shape_calculate_area(Shape* shape)
{
    if (!IS_SHAPE(shape)) return 0;

    if (SHAPE_GET_CLASS(shape)->calculateArea == NULL) {
        g_print("Shape::calculateArea is not implemented\n");

        return 0;
    }

    return SHAPE_GET_CLASS(shape)->calculateArea(shape);
}

    ... ... ... ...
最後,透過一個簡單的測試程式,確認是否calculateArea()有被正確派送(dispatch)並處理。
測試碼:
#include <glib.h>

#include "shape.h"
#include "rectangle.h"
#include "circle.h"


int main(int argc, char** argv)
{
    Rectangle* rect;
    Circle* circle;

    g_type_init();

    rect = rectangle_new(4, 5);
    g_print("Area of Rectangle(0x%08x) is %u.\n",
            (guint)rect, shape_calculate_area(SHAPE(rect)));
    rectangle_free(rect);

    circle = circle_new(6);
    g_print("Area of Circle(0x%08x) is %u.\n",
            (guint)circle, shape_calculate_area(SHAPE(circle)));
    circle_free(circle);

    return 0;
}
測試碼執行結果:

完整原始碼下載

GObject 入門 (Part 2) - 物件之生命週期

在GObject system若要型別的註冊主要是透過g_type_register_static()完成。若能理解如何使用g_type_register_static(),GObject system的使用方式可說了解絕大部分。
觀察g_type_register_static()的原型(prototype)可以發現,本函數涉入的主要參數為const GTypeInfo *info
GTypeInfo的宣告如下:
struct _GTypeInfo
{
  /* interface types, classed types, instantiated types */
  guint16                class_size;
  
  GBaseInitFunc          base_init;
  GBaseFinalizeFunc      base_finalize;
  
  /* interface types, classed types, instantiated types */
  GClassInitFunc         class_init;
  GClassFinalizeFunc     class_finalize;
  gconstpointer          class_data;
  
  /* instantiated types */
  guint16                instance_size;
  guint16                n_preallocs;
  GInstanceInitFunc      instance_init;
  
  /* value handling */
  const GTypeValueTable *value_table;
};
GTypeInfo資料結構涉及了多個callback functions,可謂環環相扣。總的來說,在型別註冊至GObject system後,系統將透過這些callback functions來管理類別及物件的生命週期。在GObject system架構下,物件自產生至銷毀其流程大致可分為幾個步驟:
Figure#1 Life cycle of a object
於每個型別初次創建之時,GObject system會循著類別樹遞迴呼叫GBaseInitFunc;GClassInitFunc則於各個型別資料(metadata)建立時,被呼叫一次。在實務上會應用到GBaseInitFunc的機會較少,多數情形下於註冊型別時可選擇不提供此callback function。 相較之下,GClassInitFunc顯得重要許多,因為子類別若有必要覆寫(override)父類別的函式,則需要在此時指派(assign)指標。
GBaseFinalizeFunc、GClassFinalizeFunc則分別與GBaseInitFunc、GClassInitFunc對應,在類別創建各階段若有動態配置資源者,必須於相應的解構(destruction)周期中釋放該資源。

Figure#2 Callback functions involved in the life cycle of a object

為了驗證,我們可以利用上一篇宣告的Shape類別加以驗證。
#include <glib.h>

#include "shape.h"

int main(int argc, char** argv)
{
 Shape* shape1;
 Shape* shape2;

 g_type_init();

 shape1 = g_object_new(SHAPE_TYPE, NULL);
 g_print("shape1(0x%08x) is created.\n", (guint)shape1);
 g_object_unref(shape1);

 shape2 = g_object_new(SHAPE_TYPE, NULL);
 g_print("shape2(0x%08x) is created.\n", (guint)shape2);
 g_object_unref(shape2);

 return 0;
}
編譯後,執行的結果:



至於GInstanceInitFunc()則可視為物件的預設建構子(default constructor),當系統分配到屬於該物件的記憶體區塊時此函式將被呼叫,使該物件有機會被指派預設值。若該類別有提供額外建構子(constructor),且於g_object_new()呼叫時有提供相關的參數值,則建構子將於GInstanceInitFunc()完成後執行。官方文件上對此機制的說明如下:
Finally, at one point or another, g_object_constructor is invoked by the last constructor in the chain. This function allocates the object's instance' buffer through g_type_create_instance which means that the instance_init function is invoked at this point if one was registered. After instance_init returns, the object is fully initialized and should be ready to answer any user-request. When g_type_create_instance returns,g_object_constructor sets the construction properties (i.e. the properties which were given tog_object_new) and returns to the user's constructor which is then allowed to do useful instance initialization...
官方文件上的文字描述實在令人有點霧裡看花的感覺,但是實際上的概念並不複雜,之後將有完整範例展示。

6/04/2011

GObject 入門 (Part 1) - 宣告類別

GObject功能強大,相對的其學習曲線也頗為陡峭,能夠找到的文件不多。官方網站有提供參考文件,個人覺得僅適用於對系統有一定程度瞭解的人,其導覽教學文件,並無提供完整的原始碼,僅憑些許程式碼片段(source code snippets)很難快速入手及測試。雖然可以透過glib、gtk+的原始碼研究參考,也是枝節頗多,增添困擾。

在這一系列的文章中,我將分享GObject system的使用方法,還有簡易的測試原始碼。

本文範例簡介
在本文中,我將建立一個叫Shape之類別。Shape類別將擁有一個方法calculateArea()用來計算面積,由於Shape為抽象類別(abstraction class),並不實作calculateArea()。之後的文章,我將加入Circle及Rectangle,他們將繼承自Shape並擁有各自的面積計算方法。
figure-01: Class diagram of the simple shape system 
宣告類別
類別的宣告在GObject system裡有特殊的規範,這是由於C programming language在語法(syntax)中並無支援OO,只能從語意(semantics)上支援。在GObject中建立類別,需要同時宣告該類別的描述資料(metadata)以及屬於該類別的資料,GObject system會在執行時期將兩者結合。
figure-02: Composition of a typical object of GObject
在實作中要如何達到上述的結果,可由下列原始碼以較直觀的方式來說明。

Shape類別之宣告:
typedef struct _Shape      Shape;
typedef struct _ShapeClass ShapeClass;

struct _Shape
{
    GObject parent;
};

struct _ShapeClass
{
    GObjectClass parent;

    guint (*calculateArea)(Shape* shape);
};
struct _Shape裡面宣告的是Shape物件的資料內容,也就是上圖所說的instance data,因為Shape並無父類別(parent class),所以在此需要將它的父類別宣告為GObject,而在Shape物件的資料宣告中也要將其放入。注意,不管是metadata或是instance data的宣告,都要將其相對的parent宣告擺在第一個位置。Shape的公共函式(public function)宣告於ShapeClass中。順帶一提的是,可以將struct _Shape定義為Shape,還有把struct _ShapeClass定義為ShapeClass,此乃GObject的命名慣例。
有了上面的宣告後,Shape尚無法運作,因為GObject需要有更多的資料才能將產生Shape物件。而這些資料都需要實作者提供。首先,我們需要向GObject system註冊Shape型別。型別註冊後會取得一個型別代號,透過此型別代號於執行時期才可以建立物件。型別的註冊方式如下:
#define SHAPE_TYPE (shape_get_type())

typedef struct _Shape      Shape;
typedef struct _ShapeClass ShapeClass;

struct _Shape
{
    GObject parent;
};

struct _ShapeClass
{
    GObjectClass parent;

    guint (*calculateArea)(Shape* shape);
};

GType shape_get_type();
static void shape_base_init(ShapeClass* self);
static void shape_base_finalize(ShapeClass* self);
static void shape_class_init(ShapeClass* self);

GType shape_get_type()
{
    static GType type = 0;

    if (type == 0) {
        static const GTypeInfo info = {
            sizeof(ShapeClass),
            (GBaseInitFunc)shape_base_init,
            (GBaseFinalizeFunc)shape_base_finalize,
            (GClassInitFunc)shape_class_init,
            NULL,
            NULL,
            sizeof(Shape),
            0,
            NULL,
            NULL
        };

        type = g_type_register_static(G_TYPE_OBJECT, "Shape", &info, 0);
    }

    return type;
}

static void shape_base_init(ShapeClass* self)
{
    g_print("Shape(0x%08x)::base::initialize\n", (guint)self);
}

static void shape_base_finalize(ShapeClass* self)
{
    g_print("Shape(0x%08x)::base::finalize\n", (guint)self);
}

static void shape_class_init(ShapeClass* klass)
{
    g_print("Shape(0x%08x)::class::initialize\n", (guint)klass);

    klass->calculateArea = NULL;
}
我們可以發現光要註冊型別就讓原始碼爆增不少,不過仔細觀察可以發現這些原始碼主要是圍繞著g_type_register_static,透過g_type_register_static可向GObject註冊取得型別代碼。之後,就可以由g_object_new產生隸屬該型別之物件。其中,由實作者提供之shape_base_init、shape_base_finalize、shape_class_init乃是供GObject system於管理物件生命週期時使用。使用法將於下篇文章探討。

Shape類別公開函式(public function)之呼叫:
在Shape類別裡面我們定義了一個虛擬公開函式(virtual public function)叫calculateArea,所有從屬於Shape(包含棋子類別)的物件,都可透過此方法計算面積。實作上,我們可以定義一個介面,使用者可透過此介面呼叫虛擬函式(virtual function),達成資料封裝的效果。
#define IS_SHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SHAPE_TYPE))
#define SHAPE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SHAPE_TYPE, ShapeClass))

struct _Shape
{
    GObject parent;
};

struct _ShapeClass
{
    GObjectClass parent;

    guint (*calculateArea)(Shape* shape);
};

GType shape_get_type();

guint shape_calculate_area(Shape* shape);
...

guint shape_calculate_area(Shape* shape)
{
    if (!IS_SHAPE(shape)) return 0;

    if (SHAPE_GET_CLASS(shape)->calculateArea == NULL) {
        g_print("Shape::calculateArea is not implemented\n");

        return 0;
    }

    return SHAPE_GET_CLASS(shape)->calculateArea(shape);
}

...
由於,calculateArea()是虛擬函數,在此GObject system透過函式指標的方式,達成子類別覆寫(override)的效果。shape_calculate_area()提供一個封裝過的介面,隱藏許多實作上的細節,這也是GObject systeme官方建議的作法。

GObject之類別宣告需要許多冗長的原始碼,初時容易令人望之卻步,但是仔細觀察可發現其實都有脈絡可循。也由於宣告方式大同小異,只要剪剪貼貼就可達成目的,反而容易因不之其所以然,導致許多的後遺症。瞭解概念後,剪貼也會比較有效率(大誤),未來若要除錯(debug)也會簡單許多。

本文範例原始碼

GObject 簡介


GObject是GLib提供的物件系統,主要目的在透過此系統為C programming language加入物件導向的功能。此外,它還定義了一套資料交換(property setter/getter)的介面,加強其他程式語言跟基於GObject/GLib撰寫的函式庫間的互通性(interoperability)。尤其是預計隨GTK+ 3.0公佈的GObject Introspection,將大大簡化GLib與其他程式語言的連結性。GObject可說是GLib與GTK+的基石,諸如:GIO、GTK+提供之的各項元件均大量運用GObject。至於,GObject內建之事件系統(messaging system)也能幫助使用者輕鬆開發由事件驅動(event driven)之程式。

GObject之特性:
  1. 單一繼承與多重介面
  2. 統一的屬性存取方法
  3. 內建訊號處理機制
單一繼承與多重介面
與單一繼承與多重繼承優劣之相關爭論在物件導向程式設計領域由來已久,GObject目前僅支援單一繼承加上多重介面的形式。簡單說,所有的承襲自GOjbject的物件僅能有一個父類別,而GObject類別自然位於此繼承體系的頂端;在類別繼承之外,GObject系統亦允許使用者為類別實作多個的介面。

統一的屬性存取方式
如果某類別有改寫(override)GObject類別之set_property()以及get_property(),則可以透過g_object_set_propertyg_object_get_property利用屬性名稱存取物件之屬性。這樣的操作雖然有點繁瑣,但是透過這樣的方式可以完整地實現資料封裝,如果有考慮替程式添加與其他程式語言(例如:Python, Ruby, Lua... etc.)之連結(binding),實做這樣的介面將為撰寫language binding節省不少時間。目前許多programming language業已實做GObject introspection,未來甚至不需要為特定語言再撰寫binding, 只要提供gir即可。

內建訊號處理模型
GObject提供一組類別封裝了callback函式,稱作Closure。可於執行時期,與特定訊號(signals)連結,爾後當訊號發送時,GObject系統將自動派送並喚起相應的closure。