發表文章

目前顯示的是有「Glib」標籤的文章

Release GContext Node.js Module!

How to write a native desktop application in Node.js? Perhaps you know many modules can do that easily, something's like node-gui and jsdx-toolkit. In order to develop system program, DBus module is the most important thing you must have as well. With Dbus module, you can handle all communications between programs. Furthermore, you can control low-level functions system provided via DBus, which includes hardware, networking and operation system features. Unfortunately, all things do not work at all since new version of Node.js. Many system libraries are based on GLib, and work with GLib event loop. For writing a node.js binding with such libraries, libev is the only way to integrate event loop between node.js event loop and GLib main context. The problem is that libev is deprecated since 0.8 node.js cause modules which depends on libev will be broken on latest and higher version. Nothing works if there is no libev. In fact, upstream announced that libuv is being only one way to...

探究如何整合 GLib Main Event Loop 和 Node.js 的 libuv

圖片
在普通情況下,整合 GLib Main Event Loop 和 libuv 不是件平常人會做的事,因為,一般人使用著 GTK+、Clutter、DBus 等等函式庫(Library)時,永遠只會使用 GLib 而不會使用到第二套事件引擎。但是,在 Node.js 中,其事件引擎並不是 GLib,而是使用自己的『libuv』,想同時運行兩套事件引擎是不可能的,所以這將注定我們無法以 Node.js 去引入 GTK+、Clutter、DBus 等函式庫來使用。不過,天下文章一大抄,世界上所有事件引擎的設計差異不大,在理解 GLib Context 的運作後,我們還是可以嘗試將兩者整合在一起,協同運行。 簡單理解一下事件引擎,其說白了就是一個跑到天荒地老的無窮迴圈,不停的去檢查是否有事件被喚醒。所以,由此可知,兩套事件引擎不能被同時跑起來,因為任何一個事件處理的無窮迴圈,都將導致另一個事件處理的迴圈無法正常運作。所以,首要解決的課題,就是讓兩個無窮迴圈可以同時運作。 為了解決這樣的問題,你可能會想到去使用 Thread (執行緒/線程),只要在建立兩個 Thread,然後各自跑不同的事件引擎就可以了。但是這樣做,問題馬上就會出現。由於程式正在運作的過程中,事件數量相當多,事件被安插和喚醒的次數非常頻繁,這將導致兩個 Thread 之間很難維持穩定的資料交換,你得實作 Mutex Lock 等機制來達成 Thread-safe,更還要想辦法解決兩邊各類大小資料交換的需求。種種原因,都會造成兩個事件引擎大量的彼此等待,而效能不彰。此外,也不易同時控制兩邊的事件觸發順序,以及事件被喚醒時間的精確度,如計時器(Timer)。 既然使用 Thread 是不好的做法,是否有其他方式可以解決我們一開始的課題?解決方法是有的,從事件引擎的運作細節,我們可以發現一些端倪。 一般來說,大部份的事件引擎都包括了幾個部份: 從 Initial 出發, Event Loop 的迴圈每運行一次,都經歷了『prepared』、『Polling』和『Dispatching』三種狀態,若用白話來說,他們所代表的意義就是『已準備好事件清單』、『監控事件』、『喚醒、分配並處理事件』。 如果你去參考事件引擎程式碼,會發現在三個狀態之間,分別有『prepare()』、『query(...

GLib 就是懶.GThreadPool 高效的執行緒池

開發複雜的程式時,執行緒(Thread)隨處可見,在高負載的應用程式中,就常會使用到多執行緒所構成的演算法,如 Apache 此類有許多流量及使用者操作的程式,就運用 Thread 做負載的分配。但是,每當 Thread 的建立和摧毀,會造成額外的系統資源開銷,若是有大量 Thread 增減將會造成程式效能不彰,所以最好的辦法便是回收並重新運用已經被建立的 Thread。因此,GLib 提供了 GThreadPool 的機制,可重復利用 Thread 以減少不必要的開銷。 這是一個使用 GThreadPool 的範例程式: void mythread(gpointer data, gpointer user_data) {     printf("%s\n", data);     printf("%s\n", user_data); } void main() {     gchar *data = "this is data";     gchar *user_data = "this is user_data";     GThreadPool *pool;     if(!g_thread_supported())          g_thread_init(NULL);      /* create thread pool */      pool = g_thread_pool_new(mythread, user_data, -1, FALSE, NULL);      if (pool==NULL)          printf("ERROR\n");     /* start three threads */      g_thread_pool_push(pool, data, NU...

GLib 就是懶.g_timeout 週期性的做壞事

要是你有撰寫 JavaScript 的經驗,應該對 setTimeout() 一點都不陌生,簡而言之,該功能就是要『N 秒後』去呼叫某一個 function,該功能可以用到的地方很多,如寫一支時鐘程式時,每秒去更新顯示一次時間。當然,給懶人用的 GLIB 也有提供類似的功能,你可以在官方文件的『The Main Event Loop』一章找到詳細說明。 這是一段簡單的範例,每秒會 print 出一行 BAD! 字串: #include <stdio.h> #include <glib.h> gboolean showme(gpointer user_data) { printf("BAD!\n"); /* TRUE 就繼續每秒跑一次;若是 return FALSE,則不再繼續執行 */ return TRUE; } int main(int argc, char **argv) { GMainContext *main_context; /* 每秒跑一次 show() */ g_timeout_add(1000, showme, NULL); /* 進入程式 Loop,例如:gtk_main() */ g_main_loop_run(g_main_loop_new (main_context, FALSE)); } 雖然 g_timeout 真正的底層實作並非如此,你還是可以想像它是建立一個新的 Thread,用 select() 去做 polling,至少在使用上有同等意義。此外,因為 g_timeout 是實作於 GLIB Main Loop 中,它無法非常精準的在多少秒後呼叫 function,通常會有一些極微小的延遲(幾乎感覺不到),不過對於極大多數的應用來說,非常足夠了。

cross-compile 密技 - 閃避 libtool eval '|' 的 BUG

這幾天手邊同時在做 ARM 和 MIPS Architecture 的工作,當然免不了一直重覆 cross-compile 的忙錄,這性感又危險的工作,常伴隨著無盡的 Error 和 Failed,讓人既有成就感又有挫折感,需要去一一排除萬難才能順利達成 Make 的任務。說著說著,又碰上了一個莫明奇妙的 bug: libtool: eval: line 964: syntax error near unexpected token `|' 這是在編譯 glib2.0-2.20.3(在 Debian Sid 上使用 apt-get source 抓下來的 sourcecode)時所遇上的錯誤,目標是要 cross-compile 給 mipsel 所使用(尚未實驗 arm 是否也有同樣問題),Debug 的過程也不多說,因為可直接使用 Tricky 的方法閃過這個錯誤: USE_ARCH=32 NM=nm CC="mipsel-linux-gnu-gcc ${BUILD32}" ./configure \ --with-gnu-ld \ --target=mipsel-linux \ --host=mipsel-linux \ CC=mipsel-linux-gnu-gcc \ --prefix=/usr

小心移除 GList 裡的 Node

最近忙著開發新的 LXNM(Lightweight Network Manager),但在開發的過程中碰到了一個 Critical Bug,該 Bug 會讓 LXNM Daemon Crashes。難過的是,已經費盡心思卻仍然找不出問題所在,以致浪費好多時間在 debug。最後多虧了 Paulliu 的協助,終於找出了程式中臭蟲,令人想不到的是,問題居然是出在 GList 的操作。 這問題出在於移除 GList 裡的 Node 之後,再讀取下一個 Node 時會 Crash,這有一個簡化後的錯誤範例: GList *node; for (node=mylist;node;node=g_list_next(node)) { mylist = g_list_delete_link(mylist, node); } 這樣表面看起來或許沒有錯誤,但以 Link-list 的基礎觀點來看,卻馬上可以發現到問題所在。該程式中,當迴圈進行到第二輪時,會因為 g_list_next() 無法取得下一個 Node 而死在那裡,因為供參考的 Node 已經在第一輪迴圈就被刪除,我們無法取得其 Node->next,當然就會發生錯誤。因此,需要稍微修改,以避免掉這樣的問題: GList *node; GList *next_node; for (node=mylist;node;node=next_node) { next_node = g_list_next(node); mylist = g_list_delete_link(mylist, node); } GLib 提供的 API 包裝,讓懶惰的我們可以很輕鬆的建立和操作 Link-list,當然難免就會不小心寫出像這樣有問題的 Code 來。因此,對程式開發者而言,就算 GLib 再方便好用,資料結構和各種演算法的理論還是無法完全拋棄。

心得分享簡報上線:桌面開發、快速開機、快速開發

本次分享活動應熱烈,差點沒把敝人搾乾。而簡報以『作中學』為主要訴求,對像以對程式開發有興趣的普通人為主,應要求,『領進門』是本次分享的重點,所以不談論太高深的進階內容,並附上些簡單上手之實例。 本次三個簡報和相關範例檔案可在此取得: 桌面開發 desktop_dev.pdf easy-ui-design.tgz 快速開機 fastboot.pdf 快速開發 fastdev.pdf fastdev.tgz Autotool 懶人包: project_example.tgz 若有更多疑問,可直接與本人連繫和交流,能力所及,將不吝答覆。

Glib 就是懶.資料處理好手 - GList 雙向鏈結(Doubly-Linked)

許多資深的『慣C』老手,對雙向鏈結串列(Doubly-Linked List)應該並不陌生,這種資料結構被大量地應用在資料記錄,但這對於其他 Scripts Language 或一些高階語言的慣用者來說,卻是一個無法理解的存在,因為對他們來說,動態增減 Array 的數量和內容,是再平常不過且應該要存在的東西。 但對 C 語言卻不是如此,同樣的功能要利用『記憶體的控制技巧(俗稱資料結構)』,才能達成。但由於 GLib 的 GList 整合,使用鏈結串列時,已經不需要再自己對指標(Pointer)做許多低階的處理,這對『慣C』一族和 C 語言的新手來說,毫無疑問地是件好事。 這裡是個使用 GList 的範例,大致說明了基本的操作: #include <glib.h> typedef struct { gchar *name; gchar *tel; } ContactNode; int main() { GList *contactlist = NULL; GList *list = NULL; ContactNode *node; /* Fred's contact information */ node = (ContactNode *)g_new0(ContactNode, 1); node->name = g_strdup("Fred Chien"); node->tel = g_strdup("0926333xxx"); /* add the node to the list */ contactlist = g_list_append(contactlist, node); /* Penx contact information */ node = (ContactNode *)g_new0(ContactNode, 1); node->name = g_strdup("Penx"); node->tel = g_strdup("09xxxxxxxx"); /* add the node to the l...

GLib 就是懶.為一些 G stuffs 加上多國語言支援

一般來說,實作一個多國語言的應用程式,會在需要被翻譯的字串上做特殊標注或處理。為了減少標注所浪費的字元數,在 『gi18n.h』中更近一步定義了 _(String) 和 N_(String) ,用以取代原本的 gettext(String)。 事實上,多國語言支援的內部實作,就是呼叫 gettext() 去處理並依情況而填入不同語言的字串,當找不到符合系統語言的翻譯檔時,便會使用開發時所標注的原始語言。使用 #define 去定義成 _(String) 這種形式,也只是圖個方便使用罷了。 然而,N_(String) 又是什麼?和 _(String) 使用的時機不一樣,是用來針對一些 static variable 所提供的多國語言支援方法。之前說到,其實 _(String) 是使用 gettext() 這 function 做字串處理,這在多數狀況下都是可行的,但是在某種情況下,直接使用 gettext() 去對字串做翻譯替換確是有很大的問題。 用一些例子會更為清楚,宣告一個靜態的陣列,裡面事先填好一些字串〔這裡使用 GtkItemFactoryEntry 的資料結構當範例〕: static GtkItemFactoryEntry menu_items[] = { { N_("/_File"), NULL, NULL, 0, "<branch>" }, { N_("/_File/_New Window"), NEW_WINDOW_ACCEL, newwindow, 1, "<stockitem>", GTK_STOCK_ADD } }; 其中,『/_File』和『/_File/_New Window』分別使用 N_(String) 被標示成需要被翻譯。為什麼在這不使用 _(String) 而是使用 N_(String) ?因為這是一個靜態的變數陣列,無法要求這陣列被存取時,使用 function 去動態取得字串資料然後再填入陣列,這本身並不符合陣列的行為和定義。可是,不能使用 _(String) 就意味著靜態陣列裡的字串,沒辦法支援多國語言。因此,N_(String) 就是在這樣的問題發生之下,被創造出來的方法,在這特殊的情況中被用來代替 _(St...

GLib 就是懶.用 GKeyFile 存取設定檔

過去曾撰文寫過『GLib』相關文章『 初探 Glib Programing - I/O 處理事件化 g_io_channel 』,其中講到 GLib 提供了許多功能的包裝,除了能讓程式開發者『輕易』實現『重覆又無聊』的功能外,又能確保其功能的穩定性,讓程式開發更容易且更簡潔有效率。如諧音『 G.Lib - 就是.懶』,將它說是應用程式開發者的『懶人包』,可是一點也不為過呀。 Glib Utilities 中有個函式包裝『 GKeyFile 』,此包裝提供一個存取設定檔的界面,讓程式開發者不再需要為了讀取設定檔,而被迫自行撰寫許多程式來處理檔案資料流和分析設定檔,是個非常省時省力的函式包裝。 GKeyFile 存取的設定檔,就是一個 .ini 檔的形式,大致上像: [group1] mykey1=66 mykey2=Hello World [group2] yourkey1=yes yourkey2=3.1415926 想使用 GKeyFile 撰寫程式讀取並顯示設定檔的內容,程式碼如下: #include <stdio.h> #include <glib.h> void main(void) { GKeyFile *keyfile; GKeyFileFlags flags; GError *error = NULL; /* 初始化 GKeyFile */ keyfile = g_key_file_new(); flags = G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS; /* 讀取 config file */ if (!g_key_file_load_from_file (keyfile, "helloworld.conf", flags, &error)) return; /* 讀取設定並顯示 */ printf("[Group1]\n"); printf("mykey1: %d\n", g_key_file_get_integer(keyfile, "group1", ...

活用 g_object_weak_ref 避免 memory leak

這是一個常見又惱人的問題 - Memory Leak 記憶體洩漏。常見的情況是軟體用一段時間後,記憶體使用量肥大,在許多功能複雜的軟體之中,不免會看到這類的情形。不過我們不能完全怪罪軟體設計者〔雖然他們還是佔很重要的因素〕,對程式開發者來說,要達成完全 Leak-free ,幾乎是不可能的事,因為除了自己寫的程式之外,各種 system call 都有可能造成 memory leak 的發生〔如 pango〕。 不過話說回來,system 裡各種 library 所造成的 memory leak 畢竟還是少量,軟體開發者所造成的問題佔著大多數,一個真實的例子:從 Firefox 每次 release 就一直在修正 memory leak 就可以發現修也修不完。 來探討 memory leak 的形成原因,顧名思義就是漏水,記憶體外漏產生了無法控制的記憶體區塊。舉個簡單的例子: void func() { char *p; p = (char *)malloc(12); p = (char *)malloc(10); free(p); } 此例的程式碼會出現 12 bytes 的 memory leak,因為第一次 malloc 取得的記憶體區塊未被釋放。換句話說,在第二次 malloc 之後,我們已經失去前一次記憶體區塊的位址了,該區塊對我們來說,已經是再也無法控制的記憶體區塊〔因為不知道位址〕,但他仍然會在程式結束前活在那。如此一來,要是我們重複呼叫此 function ,每次都會造成 12 bytes 的浪費。 當然,這是明顯可以看出 memory leak 存在的例子,有很多情況是很難發現問題的,尤其是在結構複雜的程式中。 有一個情況是這樣,在 Gtk+ 程式的設計中,常會臨時建立一個 structure 傳給 Widget event 的 callback function,程式碼大致如下: s = malloc(struct cb_s); g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(cb_func), s); 和傳統的程式不同,我們不能在 g_signal_connect 之後就用 g_free() 去釋放 s,因為在 menu_item 這 ...

初探 Glib Programing - I/O 處理事件化『 g_io_channel』

C語言本身沒什麼困難,只要好好弄懂指標與記憶體位置之間的關係,大體上『應該』就算會 C 語言了。真正令人感到怯步的部份,反倒是藏身其中的各種演算法、資料結構和作業系統機制問題,要經過長年累月的 coding 才能盡數體會。 相信許多人都曾經歷過自己硬幹『串列』的美好時光,造這種基本的資料結構似乎也成為了寫 C 的樂趣之一。但是,你可曾想過哪一天寫 C Program 時,再也不需要自製串列結構、再也不用使用 select() /poll 去操作 I/O、再也不用怕處理 String 遺漏許多安全細節、再也… ,一切『原始』的結構和機制,如果都不用再自己去刻劃,這樣的輕鬆的一刻到來,會是多麼愉快呀!『Glib』的出現,使所有夢想得以成真,讓 C Program 的開發更容易、更簡潔有效率且更穩定安全,如果說 Glib 是 libc 的加強補完計畫也不為過!〔雖然少了很多 coding 的樂趣 :( 〕 Glib 的眾多包裝之中,『I/O 處理事件化』是一個很特別部份,我們可以等待 I/O 的回應而不需要再用到 select() + loop。只要用『 g_io_channel 』設定好 I/O watch 後,讓 Glib 自己觸發 R/W 的事件 handler 就可以了,就如同 gtk 中觸發各種事件一樣的簡單。 基本 I/O 事件的 handler 宣告和設計: static gboolean gio_in (GIOChannel *gio, GIOCondition condition, gpointer data) { GIOStatus ret; GError *err = NULL; gchar *msg; gsize len; /* 若 IO HUP,直接返回 */ if (condition & G_IO_HUP) return FALSE; /* 讀取 IO 的資料 */ ret = g_io_channel_read_line (gio, &msg, &len, NULL, &err); if (ret == G_IO_STATUS_ERROR) g_error ("Error reading: %s\...