GObject對象系統


http://www.ibm.com/developerworks/cn/linux/l-gobject/

簡單的說,GObject對象系統是一個建立在GLIB基礎上的,用C語言完成的,具有跨平台特色的、靈活的、可擴展的、非常容易映射到其它語言的面向對象的框架。如果你是一個C語言的執着的追隨者,你沒有理由不研究一下它。

 

 

快速上手Gobject

 

http://blog.csdn.net/acs713/article/details/7778051

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 可能僅僅定義了一些類型集的關聯。如果你了解函數調用和 C 類型至你所在平台的機器類型的映射關系,你可 以在內存中解析到每個函數的名字從而找 到這些代碼所關聯的函數的位置,並且構 造出一個用在這個函數上的參數列表。 最后,你可以用這個參數列表來調用這 個目標 C 函數。第一個指令在堆棧上建立了 十六進制的值 0xa (十進制為 10 )作為一個

 

32位的整型,並調用了function_foo函數。就

如你看到的,C函數的調用由gcc實現成了本地機器碼的調用(這是實現起來最快的方法)。有了gcc這個第三方,我們的代碼與

機器的溝通更順暢了!記住:GType/GObject庫不僅僅是為了設計向C開發者提供面向對象的特性,也是為了透明的跨語言互通性。

做一個受歡迎的協調者

為了實現調用 C 函數, Python 解釋器需要做:

(1)找到函數所處的位置:這個意味着在C編譯器編譯成的二進制文件中尋找這個函數。

(2)在可執行的內存中,載入有關這個函數的相關代碼。

(3)在調用這個函數前,將Python的參數轉換為C兼容的參數。

(4)用正確的方式調用這個函數。

(5)C函數的返回值轉換成Python兼容的變量並將其返回至Python代碼中。

方案一:手動編寫一些“粘合代碼”,當每個函數被導入或導出時,使用這些代碼將 Python 的參數轉換為 C 兼容的參數,並將 C 的返回值轉換為 Python 兼容的返回值。這個粘合代碼將被連接到解釋器上,從而解釋器在解釋 Python 程序時,可以完成程序中的調用 C 函數的工作。方案二:自動產生粘合代碼,當每個函數被導入或導出時,使用一個特殊的編譯器來讀取原始的函數簽名。

GLib 用的解決辦法是, 使用 GType 庫來保存 在當前運行環境中的 所有由開發者描述的 對象的描述。這些“ 動態類型”庫將被特 殊的“通用粘合代碼”

來自動轉換函數參數和進行函數調用在不同的運行環境之間。

GOBJECT模擬封裝

         GObject世界里,是兩個結構體的組合,一個是實例結構體,另一個是類結構體。有點繞。類、對象、實例有什么區別?可以這么理解,類-對象-實例,無非就是類型,該類型所聲明的變量,變量所存儲的內容。后面可以知道,類結構體初始化函數一般被調用一次,而實例結構體的初始化函數的調用次數等於對象實例化的次數。所有實例共享的數據,可保存在類結構體中,而所有對象私有的數據,則保存在實例結構體中

GOBJCT如何模擬私有屬性

一種最簡單的辦法,是在類的定義時,只需要向結構體中添加一條注釋,用於標明哪些成員是私有的,哪些是可以被直接訪問的。 C 語言認為,程序員應當知道自己正在干什么,而且保證自己的所作所為是正確的
第二種辦法,也就是最常用的辦法,是把需要設為私有屬性的數據再次封裝,並且將該封裝實例的定義放到實現 .c 文件中。在上頁的例子中, GUPnPContextPrivate 的定義就被定義為私有,其定義放在 gupnp-context.c 文件中
 

C語言實現CLASSGOBJECT支持

    如何實現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 定義中聲明 GSSDPClientClass parent_class 。通過實例結構體和類結構體的共同聲明,
GOBJECT 知道 gupnpcontext gssdpclient 的子類。

GOBJECT構造函數

Gobject 對象的初始化可分為 2 部分:類結構體初始化和實例結構體初始化。
類結構體初始化函數只被調用一次,而實例結構體的初始化函數的調用次數等於對象實例化的次數。這意味着, 所有對象共享的數據,可保存在類結構體中,而所有對象私有的數據,則保存在實例結構體中

 多態的概念

多態指同一個實體同時具有多種形式。它是面向對象程序設計的一個重要特征。
把不同的子類對象都當作父類來看,可以屏蔽不同子類對象之間的差異,寫出通用的代碼,做出通用的編程,以適應需求的不斷變化。
賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。也就是說,父親的行為像兒子,而不是兒子的行為像父親。
我們這里討論的多態,主要指運行時多態,其具體引用的對象在運行時才能確定。

 為什么要在GOBJECT引入多態?

C struct 可以實現對象。普通的結構體成員可以實現為成員數據,而對象的成員函數則可以由函數指針成員來實現。很多開源的軟件也正是這么做的。
這樣的實現有一些嚴重的缺陷:別扭的語法、類型安全問題、缺少封裝,更實際的問題是 空間浪費嚴重 。每一個實例化的對象需要 4 字節的指針來指向其每一個成員方法,而這些方法對於類的每個實例(對象)應該都是相同的,所以是完全冗余的。假設一個類有 4 個方法, 1000 個實例,那么我們將浪費接近 16KB 的空間。
很明顯,我們不需要為每個實例保存這些指針,我們只需要保存一張包含這些指針的表。
Gobject 如何實現多態?

1Gobject為每個子類在內存中保存了一份包含成員函數指針的表這個表,就是我們在C++經常說到的虛方法表(vtable)。當你想調用一個虛方法時,你必須先向系統請求查找這個對象所對應的虛方法表。這張表包含了一個由函數指針組成的結構體。在調用這些函數時,需要在運行時查找合適的函數指針,這樣就能允許子類覆蓋這個方法,我們稱之為“虛函數”。

(2)  Gobject系統要求我們向它注冊新聲明的類型,系統同時要求我們去向它注冊(對象的和類的)結構體構造和析構函數(以及其他的重要信息),這樣系統就能正確的實例化我們的對象。

3Gobject系統通過枚舉化所有的向它注冊的類型來記錄新的對象類型,並且要求所有實例化對象的第一個成員是一個指向它自己類的虛函數表的指針,每個虛函數表的第一個成員是它在系統中保存的枚舉類型的數字表示。

由常用的 g_object_new () 想到的
g_object_new 能夠為我們進行對象的實例化 . 所以它必然要知道對象對應的 類的數據結構 .
如上圖示例,除第一個參數外, 很容易猜想后面的參數都是 屬性名 - 屬性值 ”的配對。
第一個參數其實是一個宏: 具體細節可以不去管它,可以知道它是去獲取數據類型xx_xx_get_type函數的作用就是告訴它有關PMDList類的具體結構。在*.c文件實現中,G_DEFINE_TYPE宏可以為我們生成xx_xx_get_type函數的實現代碼。 它可以幫助我們最終實現類型的定義。 。 當g_object_newxx_xx_get_type函數那里獲取類型標識碼之后,便可以進行對象實例的內存分配及屬性的初始化。初始化函數在前面已有介紹。
GOBJECT 多態: 將丑陋封鎖在內部
要想實現前面講述的讓 g_object_new 函數中通過“屬性名 - 屬性值”結構為 Gobject 子類對象的屬性進行初始化,我們需要完成以下工作:

1)實現xx_xx_set_propertyxx_xx_get_property函數,完成g_object_new函數“屬性名-屬性值”結構向Gobject子類屬性的映射;

  (2)Gobject子類的類結構體初始化函數中,讓Gobject基類的兩個函數指針set_propertyget_property分別指向xx_xx_set_propertyxx_xx_get_property

3)在Gobject子類的類結構體初始化函數中,為Gobject子類安裝具體對象的私有屬性。

可以看出,set_propertyGobject的虛函數實現,是運行時的多態。

GOBJECT 多態: 將優雅展示於外界
set_property 2 個函數指針,位於 Gobject 基類的類結構體中。這說明,它們可以被 Gobject 類及其子類的所有對象共享,並且各個對象都可以讓這 2 個函數指針指向它所期望的函數。
類似的機制,在 C++ 中被稱為虛函數,主要用於實現多態。
由於有了這種機制,我們可以使用 g_object_new 函數在對象實例化時便進行對象的初始化。
當我們要獲取或設置類的實例屬性時,可直接使用統一的接口: g_object_get_propertyg_object_set_property
GOBJECT 屬性實現:泛型與多態

假設我們需要一種數據類型,可以實現一個可以容納多類型元素的鏈表,我想為這個鏈表編寫一些接口,可以不依賴於任何特定的類型,並且不需要我為每種數據類型聲明一個多余的函數。這種接口必然能涵蓋多種類型,我們稱它為GValueGeneric Value,泛型)。

要編寫一個泛型的屬性設置機制,我們需要一個將其參數化的方法,以及與實例結構體中的成員變量名查重的機制。從外部上看,我們希望使用C字符串來區分屬性和公有API,但是內部上來說,這樣做會嚴重的影響效率。因此我們枚舉化了屬性,使用索引來標識它們。

屬性規格,在Glib中被稱作!GParamSpec,它保存了對象的gtype,對象的屬性名稱,屬性枚舉ID,屬性默認值,邊界值等,類型系統用!GParamSpec來將屬性的字符串

名轉換為枚舉的屬性IDGParamSpec也是一個能把所有東西都粘在一起的大膠水。

gobject 屬性設置
 
當我們需要設置或者獲取一個屬性的值時,傳入屬性的名字,並且帶上 GValue 用來保存我們要設置的值,調用 g_object_set / get_property g_object_set_property 函數將在 GParamSpec 中查找我們要設置的屬性名稱,查找我們對象的類,並且調用對象的 set_property 方法。這意味着如果我們要增加一個新的屬性,就必須要覆蓋默認的 set/ get_property 方法。而且基類包含的屬性將被它自己的方法所正常處理,因為 GParamSpec 就是從基類傳遞下來的。最后,應該記住,我們必須事先通過對象的 class_init 方法來傳入 GParamSpec 參數,用於安裝上屬性!
 
Gobject 息系統:閉包

      一個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_notifierg_closure_remove_invalidate_notifier但調用過程不是。

 

一眼望穿閉包

GClosureMarshal 是一個函數指針 , 但是要注意它是 用來定義回調函數類 型的而不是直接調用。
 
GObject 中真正的回調 marshal_data , 這個是一個 void * 指針。 這個在可通過查看 C 語言 Marshaller 的實現來得到 證明。
Gobject 為什么搞這么復 雜?用於其它語言 間的綁定 .
 
閉包給多語言綁定帶來了方便

我們從分析g_signal_new函數的使用來說明這個問題。第7個參數為GSignalMarshaller類型,它與前面體面提到的GClosureMarshal是一個東西,都是一個函數指針。

  

     GSignalCMarshallerc_marshaller:該參數是一個GSignalCMarshall類型的函數指針,其值反映了回調函數的返回值類型和額外參數類型(所謂“額外參數”,即指除回調函數中instanceuser_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, gpointeruser_data);

GTypereturn_type:該參數的值應為回調函數的返回值在GType類型系統中的ID。

guintn_params:該參數的值應為回調函數的額外參數的個數。

...:這一系列的參數的值應為回調函數的額外參數在GType類型系統中的ID,且這一系列參數中第一個參數的值為回調函數的第一個額外參數在GType類型系統中的ID,依次類推。可以認為,信號就是包含對可以連接到信號的閉包的描述和對連接到信號的閉包的調用順序的規定的集合體。

 

   事實上,它是用來翻譯閉包的參數和返回值類型的,它將翻譯的結果傳遞給閉包。之所以不直接調用callback或閉包,而在外面加了一層msrshal的封裝,主要是方便gobjec庫與其他語言的綁定。例如,我們可以寫一個pyg_closure_marshal_void_string函數,其中可以調用python語言編寫的“閉包”並將其計算結果傳遞給Gvalue容器,然后再從Gvalue容器中提取計算結果。

Gobject消息系統:Signal機制

gobject 系統中,信號是一種定制對象行為的手段,也是一種多種用途的通知機制。
每一個信號都是和能發出信號的類型一起注冊到系統中的。
該類型的使用者,需要實現信號與閉包的連接,在給定的信號和給定的 closure 間指定對應關系,這樣在信號被發射時,閉包會被調用 。信號是 closure 被調用的主要機制 ;
使用 GObject 信號機制,一般有三個步驟:

1)信號注冊,主要解決信號與數據類型的關聯問題

2)信號連接,主要處理信號與閉包的連接問題;

3)信號發射,   調用callback進行處理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM