GObject學習教程---第十章:GObject 的訊號機制——概覽
本文是學習學習他人的部落格的心得(具體詳見“樓主見解”),如果源網站可訪問的話,建議直接訪問源網站:
樓主見解:
主要講解訊號機制的實現,分下邊幾步
第一:繼承GObject,在class的inite函式中,建立一個訊號file_changed.函式g_signal_new比較複雜參考 g_signal_new () 引數的解釋。
g_signal_new ("file_changed", MY_TYPE_FILE, G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
第二:觸發訊號的函式,傳送訊號。
g_signal_emit_by_name(self, "file_changed");
第三:註冊訊號接收者,其中file就是上邊類的例項控制代碼。
static void
file_print (gpointer gobject, gpointer user_data)
{
g_printf ("invoking file_print!\n");
}
g_signal_connect (file, "file_changed", G_CALLBACK (file_print), NULL);
最後簡單介紹了解構函式的使用,具體類似於get_property/set_property,解構函式對應的函式指標是dispose\finalize,具體下一章介紹。
GObject 的訊號機制——概覽
手冊所述,GObject 訊號(Gignal)主要用於特定事件與響應者之間的連線,它與作業系統級中的訊號沒有什麼關係。例如,當我向一個檔案中寫入資料的時候,我期望能夠有一個或多個函式響應這個“向檔案寫入資料”的事件,這一期望便可基於 GObject 訊號予以實現。
為了更好的理解 GObject 訊號機制的內幕,我們需要從回撥函式開始。
基於回撥函式與可變引數的事件響應
首先,寫出事件的製造者,它是一個向檔案寫入資料的函式 file_write:
1 2 3 4 5 6 7 |
|
向檔案寫入資料完畢之後,我們希望有一個函式能夠將檔案全部的內容在終端打印出來,所以我們又增加了一個函式 file_print,並對 file_write 函式進行一點修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
但是,作為設計者應當儘可能的考慮更多更復雜的變化。單純增加一個 file_print 函式,並在 file_write 函式中呼叫,固然可以實現“檔案變化時便通知 file_print 函式去執行列印任務”,但是這只是我們的一廂情願的想法,也許 file_write 函式的其他使用者希望在向檔案寫入資料後能夠將檔案內容以 XML、TeX 或者別的甚麼格式打印出來呢?
為了應對更多的使用者的需求,我們需要使用回撥函式來隔離變化,例如:
1 2 3 4 5 6 7 8 |
|
這樣,如果 file_write 的使用者僅需要在檔案內容發生變動後列印檔案的原始資料,那麼就可以將前文中的 file_print 函式作為引數傳遞於 file_write 函式。如果 file_write 的使用者希望在檔案內容發生變動後以 XML 格式列印檔案,那麼他可以寫一個 file_print_xml 函式並將其傳遞於 file_write 函式。
如果進一步考慮更多的變化,例如在 file_write 向檔案寫入資料後,我們希望能夠一舉“通知”檔案原始資料列印、XML 格式列印、TeX 格式列印等函式,這應當如何處理?如果使用 C 語言的可變引數功能,這個問題很好解決。例如,可以將 file_write 函式定義為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
這樣,在使用 file_write 函式的時候,可傳遞多個函式供其呼叫,例如:
1 2 3 4 5 |
|
基於回撥函式與可變引數實現特定“事件”的多個“響應”,這種方案是最有效的,但不是最好的。例如,受到函式棧空間的大小限制,可變引數用盡之時。此外,這種方式使用起來也不夠直觀。
基於 GObject 訊號的事件響應
對於上一節的示例所解決的問題,基於 GObjet 訊號的解決方案大致像下面這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
上述程式碼的含義如下:
- 在 file_write 函式中,檔案資料寫入操作完畢後,就這一事件向外發射一個“CHANGED”訊號,告訴所有響應者,檔案內容改變了。至於哪些函式是這一訊號的響應者,file_write 函式不必知道。
- file_write 函式的使用者,如果希望哪些函式用於響應 file_write 函式修改檔案內容這一事件,那麼就使用 g_signal_connect 函式(實際上它是一個巨集)將響應函式與訊號掛接到一起。這樣,一旦事件的對應訊號被 g_signal_emit 所發射,這些響應函式便會被自動呼叫。
為了實現上述的“訊號/響應”模擬,那麼 file_write 函式的引數便不可能再是 FILE 型別的檔案指標了,而是我們自定義的 File 型別的物件,其中封裝了“訊號/響應”功能。事實上,GObject 類的內部便封裝了這些功能,所有經由 GObject 子類化而產生的物件,便可擁有這些功能。
GObject 子類物件的訊號處理
首先,我們定義 GObject 子類 MyFile。這個過程,我們應當已經不再陌生,參考文件 [1]。
my-file.h 標頭檔案內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
my-file.c 原始檔內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
|
MyFile 類的使用者——main.c 檔案內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
雖然 GObject 子類化以及物件私有屬性等知識均已有所介紹,但是上述的 MyFile 類的實現依然有許多細節需要加以解釋。
首先,是在 MyFile 類的類結構題初始化函式 my_file_class_init 中,除了設定類屬性之外,我們呼叫了 g_signal_new 函式用於建立 MyFile 型別與 "file_changed" 訊號的關聯。至於究竟是何種關聯,那不是我們所關心的!還有,g_signal_new 函式的引數有很多,很複雜,推薦閱讀文件 [2]。
其次,是 MyFile 物件的解構函式。在 my-file.c 原始檔中,函式 my_file_dispose 與 my_file_finalize 構成了 MyFile 物件的解構函式,前者用於解除 MyFile 物件對其它物件(是指那些具有引用計數且被 GObject 庫的型別系統所管理的物件)的引用,後者用於 MyFile 物件屬性的記憶體釋放。至於分何要分為兩個階段進行 GObject 子類物件析構以及相關細節知識,還是另外開一篇文章來討論吧,否則問題會被越搞越複雜。或者,也可閱讀文件 [3]。
小結
當我剛開始寫這篇文章的時候,我期望能夠理清 GObject 訊號與閉包的關係,但是現在不得不宣佈很失敗。還是冷靜幾天再捲土重來吧。
這篇文章,寫了一整天。現在我不得不告訴你,其實 GObject 真的很複雜。不過,從我向自己丟擲了第一個謊言之後,一直堅持到現在。儘管複雜,但是我們正在一點一點克服它。但是,最大的敵人不是 GObject,而是我自己。因為在這個過程中,我經常無法抗拒一種解剖 GObject 的慾望。它導致我經常陷入一個又一個的技術細節,而忘記了當初的目標。這種慾望之所以出現,是因為 GObject 是開源的,它賦予了我們每個人可以窺視它內部實現的權力。
我需要再次糾正一下認識。對於 GObject 牌的汽車,我現在只需要學習如何駕駛它,根本不需要去了解它的發動機是如何工作的。
參考文件
[1] 溫故而知新