What is G-object?
—不少人被灌輸了這樣一種概念:要寫面向對象程序,那麼就須要學習一種面向對象編程語言,例如C++、Java、C#等等,而C語言是用來編寫結構化程序的。
—事實上,面向對象只是一種編程思想,不是一種編程語言。換句話說,面向對象是一種遊戲規則,它不是遊戲。
—Gobject,亦稱Glib對象系統,是一個程序庫,它能夠幫助咱們使用C語言編寫面向對象程序;它提供了一個通用的動態類型系統(GType)、一個基本類型的實現集(如整型、枚舉等)、一個基本對象類型-Gobject、一個信號系統以及一個可擴展的參數/變量體系。
Why Bother to use Gobject?
—GObject告訴咱們,使用C語言編寫程序時,能夠運用面向對象這種編程思想。
—Gobject系統提供了一個靈活的、可擴展的、而且容易映射到其餘語言的面向對象的C語言框架。
—GObject的動態類型系統容許程序在運行時進行類型註冊,它的最主要目的有兩個:
1)使用面向對象的設計方法來編程。GObject僅依賴於
GLib和
libc,經過它可以使用純C語言設計一整套面向對象的軟件模塊。
2)多語言交互。在爲已經使用 GObject框架寫好的函數庫創建多語言連結時,能夠很容易對應到許多語言,包括C++、Java、Ruby、Python和.NET/Mono等。GObject被設計爲能夠直接使用在
C 程序中,也
封裝至其餘語言。
—Gobject如何解決靜態語言與動態語言的溝通問題?
—在python語言中調用一個C的API:C的API是經常是一些從二進制文件中導出的函數集和全局變量。C的函數能夠有任意數量的參數和一個返回值。每一個函數有惟一的由函數名肯定的標識符,而且由C類型來描述參數和返回值。相似的,由API導出的全局變量也是由它們的名字和類型所標識。一個C的API可能僅僅定義了一些類型集的關聯。例如:
- static void function_foo(int foo)
- {
- }
-
- int main(int argc, char *argv[])
- {
- function_foo(10)
- return 0;
- }
若是你瞭解函數調用和C類型至你所在平臺的機器類型的映射關係,你能夠在內存中解析到每一個函數的名字從而找到這些代碼所關聯的函數的位置,而且構造出一個用在這個函數上的參數列表。最後,你能夠用這個參數列表來調用這個目標C函數。第一個指令在堆棧上創建了十六進制的值0xa(十進制爲10)做爲一個32位的整型,並調用了function_foo函數。就如你看到的,C函數的調用由gcc實現成了本地機器碼的調用(這是實現起來最快的方法)。
- push $0xa
- call 0x80482f4 <function_foo>
有了gcc這個第三方,咱們的代碼與機器的溝通更順暢了!記住:GType/GObject庫不只僅是爲了設計向C開發者提供面向對象的特性,也是爲了
透明的
跨語言互通性。
作一個受歡迎的協調者
—爲了實現調用C函數,Python解釋器須要作:
(1)找到函數所處的位置:這個意味着在C編譯器編譯成的二進制文件中尋找這個函數。 php
(2)在可執行的內存中,載入有關這個函數的相關代碼。 python
(3)在調用這個函數前,將Python的參數轉換爲C兼容的參數。 程序員
(4)用正確的方式調用這個函數。 編程
(5)將C函數的返回值轉換成Python兼容的變量並將其返回至Python代碼中。 安全
—方案一:手動編寫一些「粘合代碼」,當每一個函數被導入或導出時,使用這些代碼將Python的參數轉換爲C兼容的參數,並將C的返回值轉換爲Python兼容的返回值。這個粘合代碼將被鏈接到解釋器上,從而解釋器在解釋Python程序時,能夠完成程序中的調用C函數的工做。方案二:自動產生粘合代碼,當每一個函數被導入或導出時,使用一個特殊的編譯器來讀取原始的函數簽名。
—GLib用的解決辦法是,使用GType庫來保存在當前運行環境中的全部由開發者描述的對象的描述。這些「動態類型」庫將被特殊的「通用粘合代碼」
來自動轉換函數參數和進行函數調用在不一樣的運行環境之間。 數據結構
GOBJECT模擬封裝
在 GObject世界裏,類是兩個結構體的組合,一個是實例結構體,另外一個是類結構體。有點繞。類、對象、實例有什麼區別?能夠這麼理解,類-對象-實例,無非就是類型,該類型所聲明的變量,變量所存儲的內容。後面能夠知道,類結構體初始化函數通常被調用一次,而實例結構體的初始化函數的調用次數等於對象實例化的次數。全部實例共享的數據,可保存在類結構體中,而全部對象私有的數據,則保存在實例結構體中。 閉包
下面咱們摘取一段示例代碼(包含示例結構體和類結構體)來幫助更好的理解上述概念: app
GOBJCT如何模擬私有屬性
—一種最簡單的辦法,是在類的定義時,只須要向結構體中添加一條註釋,用於標明哪些成員是私有的,哪些是能夠被直接訪問的。C語言認爲,程序員應當知道本身正在幹什麼,並且保證本身的所做所爲是正確的。
—第二種辦法,也就是最經常使用的辦法,是把須要設爲私有屬性的數據再次封裝,而且將該封裝實例的定義放到實現.c文件中。在上頁的例子中,GUPnPContextPrivate的定義就被定義爲私有,其定義放在gupnp-context.c文件中
C語言實現CLASS域GOBJECT支持
如何實現gobject面向對象支持呢? 框架
很簡單,咱們只須要創建本身的頭文件,並添加一些宏定義G_DEFINE_TYPE便可。 編程語言
這樣,GUPnPContext就成爲了Gobject庫承認的一類合法公民了,即成功的把GUpnPContextClass類所表明的type(類型)註冊到了glib類型系統中,而且將成功獲取到一個類型ID。
也就是說,當你設計新類時,GUPnPContext能夠被考慮加進你的繼承體系,同時GUPnPContext也能夠被用於組合成其餘的類。
進一步理解GType類型系統
—Gtype類型系統是Glib運行時類型認證和管理系統。
—Gtype API是Gobject系統的基礎,它提供註冊和管理全部基本數據、用戶定義對象和接口類型的技術實現。如:
G_DEFINE_TYPE
宏、
G_DEFINE_INTERFACE
宏、
g_type_register_static
函數等都在
GType
實現。
—前面提到的G_DEFINE_TYPE宏,展開後主要用於
實現用戶定義類型,包括:聲明類初始化函數、聲明實例初始化函數、聲明父類的一些信息、以及用於獲取分配類型ID的xx_xx_get_type()函數;以下圖所示:
GOBEJCT如何實現繼承
—前面咱們已經介紹,在 GObject世界裏,類是兩個結構體的組合,一個是實例結構體,另外一個是類結構體。
—很容易理解,GOBJECT的繼承須要實現實例結構體的繼承和類結構體的繼承。
—在前面的例子,咱們經過在gupnpcontext實例中顯示聲明GSSDPClient parent來告知gobject系統GSSDPClient是gupnpcontext實例的雙親;同時,經過GUPnPContextClass定義中聲明GSSDPClientClassparent_class。經過實例結構體和類結構體的共同聲明,
—GOBJECT知道gupnpcontext是gssdpclient的子類。
GOBJECT構造函數
—Gobject對象的初始化可分爲2部分:類結構體初始化和實例結構體初始化。
類結構體初始化函數只被調用一次,而實例結構體的初始化函數的調用次數等於對象實例化的次數。這意味着,全部對象共享的數據,可保存在類結構體中,而全部對象私有的數據,則保存在實例結構體中。
多態的概念
—多態指同一個實體同時具備多種形式。它是面向對象程序設計的一個重要特徵。
—把不一樣的子類對象都看成父類來看,能夠屏蔽不一樣子類對象之間的差別,寫出通用的代碼,作出通用的編程,以適應需求的不斷變化。
—賦值以後,父對象就能夠根據當前賦值給它的子對象的特性以不一樣的方式運做。也就是說,父親的行爲像兒子,而不是兒子的行爲像父親。
—咱們這裏討論的多態,主要指運行時多態,其具體引用的對象在運行時才能肯定。
爲何要在GOBJECT引入多態?
—用C的struct能夠實現對象。普通的結構體成員能夠實現爲成員數據,而對象的成員函數則能夠由函數指針成員來實現。不少開源的軟件也正是這麼作的。
—這樣的實現有一些嚴重的缺陷:彆扭的語法、類型安全問題、缺乏封裝,更實際的問題是
空間浪費嚴重。每個實例化的對象須要4字節的指針來指向其每個成員方法,而這些方法對於類的每一個實例(對象)應該都是相同的,因此是徹底冗餘的。假設一個類有
4個方法,
1000個實例,那麼咱們將浪費接近
16KB的空間。
—很明顯,咱們不須要爲每一個實例保存這些指針,咱們只須要保存一張包含這些指針的表。
(1)Gobject爲每一個子類在內存中保存了一份包含成員函數指針的表. 這個表,就是咱們在C++常常說到的虛方法表(vtable)。當你想調用一個虛方法時,你必須先向系統請求查找這個對象所對應的虛方法表。這張表包含了一個由函數指針組成的結構體。在調用這些函數時,須要在運行時查找合適的函數指針,這樣就能容許子類覆蓋這個方法,咱們稱之爲「虛函數」。
(2) Gobject系統要求咱們向它註冊新聲明的類型,系統同時要求咱們去向它註冊(對象的和類的)結構體構造和析構函數(以及其餘的重要信息),這樣系統就能正確的實例化咱們的對象。
(3)Gobject系統經過枚舉化全部的向它註冊的類型來記錄新的對象類型,而且要求全部實例化對象的第一個成員是一個指向它本身類的虛函數表的指針,每一個虛函數表的第一個成員是它在系統中保存的枚舉類型的數字表示。
由經常使用的g_object_new()想到的
—g_object_new可以爲咱們進行對象的實例化.因此它必然要知道對象對應的類的數據結構.
—如上圖示例,除第一個參數外,很容易猜測後面的參數都是「
屬性名
-
屬性值」的配對。
—第一個參數實際上是一個宏:具體細節能夠不去管它,能夠知道它是去獲取數據類型xx_xx_get_type函數的做用就是告訴它有關PMDList類的具體結構。在*.c文件實現中,G_DEFINE_TYPE宏能夠爲咱們生成xx_xx_get_type函數的實現代碼。 它能夠幫助咱們最終實現類類型的定義。 。 當g_object_new從xx_xx_get_type函數那裏獲取類類型標識碼以後,即可以進行對象實例的內存分配及屬性的初始化。初始化函數在前面已有介紹。
—要想實現前面講述的讓g_object_new函數中經過「屬性名-屬性值」結構爲Gobject子類對象的屬性進行初始化,咱們須要完成如下工做:
(1)實現xx_xx_set_property與xx_xx_get_property函數,完成g_object_new函數「屬性名-屬性值」結構向Gobject子類屬性的映射;
(2)在Gobject子類的類結構體初始化函數中,讓Gobject基類的兩個函數指針set_property與get_property分別指向xx_xx_set_property與xx_xx_get_property。
(3)在Gobject子類的類結構體初始化函數中,爲Gobject子類安裝具體對象的私有屬性。
能夠看出,set_property是Gobject的虛函數實現,是運行時的多態。
GOBJECT多態:將優雅展現於外界
—
set_property
是
2
個函數指針,位於
Gobject
基類的類結構體中。這說明,它們能夠被
Gobject
類及其子類的全部對象共享,而且各個對象均可以讓這
2
個函數指針指向它所指望的函數。
—相似的機制,在C++中被稱爲虛函數,主要用於實現多態。
—因爲有了這種機制,咱們可使用g_object_new函數在對象實例化時便進行對象的初始化。
—當咱們要獲取或設置類的實例屬性時,可直接使用統一的接口:g_object_get_propertyg_object_set_property
假設咱們須要一種數據類型,能夠實現一個能夠容納多類型元素的鏈表,我想爲這個鏈表編寫一些接口,能夠不依賴於任何特定的類型,而且不須要我爲每種數據類型聲明一個多餘的函數。這種接口必然能涵蓋多種類型,咱們稱它爲GValue(Generic Value,泛型)。
要編寫一個泛型的屬性設置機制,咱們須要一個將其參數化的方法,以及與實例結構體中的成員變量名查重的機制。從外部上看,咱們但願使用C字符串來區分屬性和公有API,可是內部上來講,這樣作會嚴重的影響效率。所以咱們枚舉化了屬性,使用索引來標識它們。
屬性規格,在Glib中被稱做!GParamSpec,它保存了對象的gtype,對象的屬性名稱,屬性枚舉ID,屬性默認值,邊界值等,類型系統用!GParamSpec來將屬性的字符串
名轉換爲枚舉的屬性ID,GParamSpec也是一個能把全部東西都粘在一塊兒的大膠水。
—當咱們須要設置或者獲取一個屬性的值時,傳入屬性的名字,而且帶上GValue用來保存咱們要設置的值,調用g_object_set/get_property。g_object_set_property函數將在GParamSpec中查找咱們要設置的屬性名稱,查找咱們對象的類,而且調用對象的set_property方法。這意味着若是咱們要增長一個新的屬性,就必需要覆蓋默認的set/get_property方法。並且基類包含的屬性將被它本身的方法所正常處理,由於GParamSpec就是從基類傳遞下來的。最後,應該記住,咱們必須事先經過對象的class_init方法來傳入GParamSpec參數,用於安裝上屬性!
一個Closure是一個抽象的、通用表示的回調(callback)。它是一個包含三個對象的簡單結構:
(1)一個函數指針(回調自己) ,原型相似於:
return_type function_callback (... , gpointeruser_data);
(2) user_data指針用來在調用Closure時傳遞到callback。
(3)一個函數指針,表明Closure的銷燬:當Closure的引用數達到0時,這個函數將被調用來釋放Closure的結構。
一個GClosure提供如下簡單的服務:
調用(g_closure_invoke):這就是Closure建立的目的: 它們隱藏了回調者的回調細節。
通知:相關事件的Closure通知監聽者如Closure調用,Closure無效和Clsoure終結。監聽者能夠用註冊g_closure_add_finalize_notifier(終結通知),g_closure_add_invalidate_notifier(無效通知)和g_closure_add_marshal_guards(調用通知)。
對於終結和無效事件來講,這是對等的函數(g_closure_remove_finalize_notifier和g_closure_remove_invalidate_notifier,但調用過程不是。
「一眼望穿」閉包
—GClosureMarshal是一個函數指針,可是要注意它是用來定義回調函數類型的而不是直接調用。
GObject中真正的回調是marshal_data,這個是一個void *指針。這個在可經過查看C語言Marshaller的實現來獲得證實。
Gobject爲何搞這麼複雜?用於其它語言間的綁定.
咱們從分析g_signal_new函數的使用來講明這個問題。第7個參數爲GSignalMarshaller類型,它與前面體面提到的GClosureMarshal是一個東西,都是一個函數指針。
GSignalCMarshaller c_marshaller:該參數是一個GSignalCMarshall類型的函數指針,其值反映了回調函數的返回值類型和額外參數類型(所謂「額外參數」,即指除回調函數中instance和user_data之外的參數)。
例如,g_closure_marshal_VOID_VOID說明該signal的回調函數爲如下的callback類型:
typedef void (*callback) (gpointer instance, gpointer user_data);
而g_closure_marshal_VOID_POINTER則說明該signal的回調函數爲如下的callback類型:
typedef void (*callback) (gpointer instance,gpointer arg1,gpointer user_data);
GType return_type:該參數的值應爲回調函數的返回值在GType類型系統中的ID。
guintn_params:該參數的值應爲回調函數的額外參數的個數。
...: 這一系列的參數的值應爲回調函數的額外參數在GType類型系統中的ID,且這一系列參數中第一個參數的值爲回調函數的第一個額外參數在GType類型系統中的ID,依次類推。
能夠認爲,信號就是包含對能夠鏈接到信號的閉包的描述和對鏈接到信號的閉包的調用順序的規定的集合體。
事實上,它是用來翻譯閉包的參數和返回值類型的,它將翻譯的結果傳遞給閉包。之因此不直接調用callback或閉包,而在外面加了一層marshal的封裝,主要是方便gobjec庫與其餘語言的綁定。例如,咱們能夠寫一個pyg_closure_marshal_void_string函數,其中能夠調用python語言編寫的「閉包」並將其計算結果傳遞給Gvalue容器,而後再從Gvalue容器中提取計算結果。
Gobject消息系統:Signal機制
—在gobject系統中,信號是一種定製對象行爲的手段,也是一種多種用途的通知機制。
—每個信號都是和能發出信號的類型一塊兒註冊到系統中的。
—該類型的使用者,須要實現信號與閉包的鏈接,在給定的信號和給定的closure間指定對應關係,這樣在信號被髮射時,閉包會被調用。信號是closure被調用的主要機制;
—使用 GObject信號機制,通常有三個步驟:
(1)信號註冊,主要解決信號與數據類型的關聯問題
(2)信號鏈接,主要處理信號與閉包的鏈接問題;
(3)信號發射, 調用callback進行處理。