《Makefile 光學(xué)教程》之面向 Makefile 編程·C/C++ 項(xiàng)目模板 [Glib-2.0 & ADT]

此教程將計(jì)劃以兩部分內(nèi)容呈現(xiàn),目標(biāo)是從零基礎(chǔ)到 GNU make 最本原的原理的掌握,這是第二部分內(nèi)容,按不同的工程類型分成多個(gè)示范項(xiàng)目來(lái)展示。零基礎(chǔ)可以先看第一部分:Basic Concepts:
1.? ?? Basic Concepts
2.? ?? Demo Projects
????2.1.? ?? Scheme R6RS 語(yǔ)言規(guī)范文檔處理 [LaTeX]
????2.2.? ?? Multi threaded Download [Msys2 Packages]
????2.3.? ?? C/C++ Project Templates [GLib Gobject & ADT]
????2.4.? ?? Erlang Project Templates?
????2.5.? ?? Unit Test [CPL]?
此部分涉及內(nèi)容較多,主要是 OOP 思想與 GLib (GObject)框架內(nèi)容,將細(xì)分為三小節(jié),內(nèi)容較多請(qǐng)忍耐一下:

?? GLib–2.0 前置教程:Msys + Meson 構(gòu)建工具
?? GLib–2.0 GObject ADT 類型系統(tǒng)庫(kù)
?? GObject–2.0 OOP 框架入門教程
完整《Makefile 光學(xué)教程》以及 GNU M4 教程參考開(kāi)源文檔:https://github.com/Jeangowhy/opendocs/blob/main/Makefile.md
???GLib–2.0?前置教程:Msys + Meson 構(gòu)建工具
研究開(kāi)源庫(kù)過(guò)程中養(yǎng)成了一個(gè)不知是好是壞的習(xí)慣(自覺(jué)更能進(jìn)入心流狀態(tài)),那就是首先分析源代碼中的開(kāi)源文檔結(jié)構(gòu)。通常,官方文檔是研究開(kāi)源庫(kù)的第一手資料,其次是搜索引擎能找到的優(yōu)質(zhì)資料,之所以強(qiáng)制優(yōu)質(zhì),是因?yàn)楝F(xiàn)代社會(huì)制造垃圾信息的成本太低了。換個(gè)“不恰當(dāng)”的說(shuō)法就是:造謠一張嘴,辟謠跑斷腿!
可以從 gitlab.gnome.org 上下載到 glib2 框架源代碼,感謝 GTK+ 開(kāi)發(fā)團(tuán)隊(duì)提供高質(zhì)量的開(kāi)源文檔。源代碼使用基于 Python 3 +?Ninja 組合實(shí)現(xiàn)的 meson 作為構(gòu)建工具,默認(rèn)以 meson.build 為配置腳本,Meson Build system 工作模式類似 CMake:
理解 Makefile 基本原理后(依賴關(guān)系網(wǎng)絡(luò)),學(xué)習(xí) CMake 或者 Meson 這些高級(jí)自動(dòng)化構(gòu)建工具就易如反掌。Meson sample 提供的 meson.build 腳本示范參考,一眼就可以看到它們隱含的依賴關(guān)系處理,子目錄下的 `meson.build` 腳本只需要調(diào)用 `subdir('gio')` 這樣的函數(shù)就可以嵌套處理 :
以下?meson.build 示例來(lái)自 Meson?IndepthTutorial.md 文檔,演示如何使用 pkg-config 查找依賴庫(kù):
CMake 和 Meson 都是非?,F(xiàn)代的自動(dòng)構(gòu)建工具,都是值得學(xué)習(xí)的自動(dòng)化構(gòu)建工具,前者使用 C++ 實(shí)現(xiàn),代碼量較后者多幾倍。由于 Meson 基于 Python 之上構(gòu)建,所以節(jié)省了一定的代碼量。雙方都有非常完善的文檔,CMake 文檔使用 reStructured Text 格式,內(nèi)容非常精細(xì),甚至可以用繁多來(lái)形容,具體到每個(gè)變量、每個(gè)函數(shù)都有一個(gè)文檔對(duì)應(yīng),當(dāng)然也有目錄。Meson 使用 Markdown 用戶指南加 YAML 參考手冊(cè)格式,它們都是非常好用的文檔格式,和 markdown,或者專業(yè)排版的 TeX 或者 LaTeX 都是非常優(yōu)秀的開(kāi)源文檔格式。
它們兩者本身就是一個(gè) DSL 領(lǐng)域特定語(yǔ)言,專用于處理構(gòu)建過(guò)程中的依賴關(guān)系、依賴庫(kù)處理等問(wèn)題。甚至可以將二者的源代碼作為研究編譯實(shí)現(xiàn)的范本項(xiàng)目:
Meson 為了降低自身出現(xiàn)依賴問(wèn)題,約定一條規(guī)則:不使用 Python 基礎(chǔ)標(biāo)準(zhǔn)庫(kù)以外的模塊,只需要 Pyton 3 和 Ninja。Ninja 使用 C++ 實(shí)現(xiàn)極輕量的構(gòu)建工具,其設(shè)計(jì)目標(biāo)之一是“必須易于嵌入大型構(gòu)建系統(tǒng)”。Ninja 的規(guī)則文件 ninja.build 并沒(méi)有條件語(yǔ)句或是基于文件后綴的規(guī)則,相反,使用列表記錄確切的輸入文件路徑,以及所產(chǎn)生的確切結(jié)果。因?yàn)檫@種簡(jiǎn)單的表達(dá)并不需要額外的解釋,所以,在運(yùn)行時(shí),這些規(guī)則文件能夠被快速載入。由于 Ninja 追求目標(biāo)簡(jiǎn)潔,就像是一個(gè)新式的 GNU Make,它沒(méi)有隱式規(guī)則、沒(méi)有函數(shù)、也沒(méi)有第三方依賴,源文件不到 1MB,使用 CMake 就可以執(zhí)行編譯。注意,Msys2 編譯環(huán)境有可能出現(xiàn) ‘Subprocess’ 類型字段沒(méi)有定義的錯(cuò)誤,不能通過(guò)編譯:
Meson 為非原生構(gòu)建的項(xiàng)目提供 Wrap database 服務(wù),項(xiàng)目中可以使用 .warp 文件提供模塊信息,其功能類似 pkg-config 中使用的 .pc 文件。可以使用 `meson wrap` 命令進(jìn)行查詢、安裝等等操作。
Meson 支持多種依賴庫(kù)配置工具,可以在其依賴對(duì)象中 method 設(shè)置中指定:默認(rèn)值是 `auto`,可選擇使用 `pkg-config`, `config-tool`, `cmake`, `builtin`, `system`, `sysconfig`, `qmake`, `extraframework` 還有 `dub`。默認(rèn)的依賴庫(kù)查找控以下順序處理:
? 1. `pkg-config`
? 2. `cmake`
? 3. `extraframework` (OSX only)
Meson 官方文檔自信滿滿,各項(xiàng)指標(biāo)都暴打 GNU Autotools 這套臃腫的自動(dòng)化構(gòu)建工具。作為后來(lái)都,Meson 還支持將 CMake 項(xiàng)目作為子項(xiàng)目導(dǎo)入。作為 GNU Autotools 的反面,GNU Make 真正做到小而美,它在實(shí)現(xiàn)上的克制(絕對(duì)不亂加代碼實(shí)現(xiàn)混亂的功能)使用得 GNU Make 始終是自動(dòng)化構(gòu)建工具的典范!當(dāng)然,現(xiàn)代的自動(dòng)化構(gòu)建工具,已經(jīng)不需要開(kāi)發(fā)者手寫 Makefile 腳本了,很多規(guī)則定義工作只需要 CMake 或者 Meson 的一個(gè)函數(shù)就可以替代,包括代碼文件的生成,但是 GNU Make 傳承下來(lái)的依賴處理的理念始終是根本。
Meson Build system Features
*? ?multiplatform support for Linux, macOS, Windows, GCC, Clang, Visual Studio and others
*? ?supported languages include C, C++, D, Fortran, Java, Rust
*? ?build definitions in a very readable and user friendly non-Turing complete DSL
*? ?cross compilation for many operating systems as well as bare metal
*? ?optimized for extremely fast full and incremental builds without sacrificing correctness
*? ?built-in multiplatform dependency provider that works together with distro packages
*? ?fun!
Meson 文檔目錄記錄在 docs\sitemap.txt 文件。
https://github.com/mesonbuild/meson/blob/master/docs/sitemap.txt


C/C++ 代碼閱讀推薦使用 Sublime Text + LSP 插件 + Clangd LSP 服務(wù)器:

LSP: Settings 中設(shè)置 Clangd 編譯器提供的 C/C++ LSP 語(yǔ)言服務(wù):


Meson 文檔 Vala.md 展示了 Gnome 為了簡(jiǎn)化基于 GLib 的圖形應(yīng)用程序而開(kāi)發(fā)的 Vala 和 Genie 編程語(yǔ)言項(xiàng)目。Vala 支持現(xiàn)代語(yǔ)言特性,借鑒了大量的 C# 語(yǔ)法。而發(fā)行在兩年后的 Genie 則參考了 Python 和 Delphi 語(yǔ)言,但是它們都使用相同的 `valac` 編譯器(轉(zhuǎn)譯器),.vala .gs .vapi 等代碼文件會(huì)轉(zhuǎn)換成 C 語(yǔ)言代碼,再編譯成二進(jìn)制程序執(zhí)行。

Vala 是一門新興的編程語(yǔ)言,由 GNOME 主導(dǎo)開(kāi)發(fā),支持很多現(xiàn)代語(yǔ)言特性,借鑒了大量的 C# 語(yǔ)法,Python 的手感,C 的執(zhí)行速度,Vala 最終會(huì)轉(zhuǎn)換為 C 語(yǔ)言,然后把 C 代碼編譯為二進(jìn)制文件,使用 Vala 編寫應(yīng)用程序和直接使用 C 語(yǔ)言編寫應(yīng)用程序,運(yùn)行效率是一樣的,但是 Vala 相比 C 語(yǔ)言更加容易,可以快速編寫和維護(hù)
GLib 官方網(wǎng)站上的文檔都可以在源代碼中找到對(duì)應(yīng)的 xml 源文件,分別可以在以下三個(gè) meson.build 腳本中找到對(duì)應(yīng)的目錄:
1. glib-2.78.0\docs\reference\gio\meson.build
2. glib-2.78.0\docs\reference\glib\meson.build
3. glib-2.78.0\docs\reference\gobject\meson.build
GLib 框架文檔列表中包含:?glib-docs.xml 即 GLib API 文檔目錄文件;gobject-docs.xml? 即 GObject?模塊 API 文檔目錄文件;?gio-docs.xml 即 GIO 模塊 API 文檔目錄文件。
入門應(yīng)該先讀 GObject 教程部分,即 tut_intro 入門教程。以下 GnomeVFS Overview 架構(gòu)圖可以幫助理解 GLib 的大體結(jié)構(gòu)。Virtual File System (VFS) 即構(gòu)建于內(nèi)存空間的文件系統(tǒng),相對(duì)于傳統(tǒng)磁盤中的文件系統(tǒng)。

Msys2 平臺(tái)中使用 pacman 安裝依賴庫(kù),包括安裝 pkg-config 依賴庫(kù)信息管理工具(使用 pkgconf 作為其兼容實(shí)現(xiàn)):
1. https://packages.msys2.org/base/pkgconf
2. https://packages.msys2.org/base/mingw-w64-pkg-config
?? GLib–2.0 GObject ADT 類型系統(tǒng)庫(kù)
Gobject 即 GTK 為 C 語(yǔ)言提供類型系統(tǒng)實(shí)現(xiàn)而開(kāi)發(fā)的 Glib 基礎(chǔ)庫(kù)的擴(kuò)展,用于輔助 C 語(yǔ)言編寫面向?qū)ο蟪绦?,提供以下?nèi)容:
1. 一個(gè)通用的動(dòng)態(tài)類型系統(tǒng)(GType)
2. 一個(gè)基本類型的實(shí)現(xiàn)集(如整型、枚舉等)
3. 一個(gè)基本對(duì)象類型 Gobject
4. 一個(gè)信號(hào)系統(tǒng)以及一個(gè)可擴(kuò)展的參數(shù)/變量體系。
GObject 基于 Glib 實(shí)現(xiàn)動(dòng)態(tài)類型系統(tǒng) GType,原來(lái)是 GTK+ 的一部分,GTK+ 2.0 中將與 GUI 不相關(guān)的部份都移到 GObject 而創(chuàng)建了此類庫(kù),源碼包含在 Glib。gobject-query 命令可以用來(lái)查詢類型樹(shù)。
GObject 世界里,一個(gè)類類型定義是*實(shí)例結(jié)構(gòu)體* GObject 和*類結(jié)構(gòu)體* GObjectClass 兩個(gè)者的組合。GObject 的繼承機(jī)制需要實(shí)現(xiàn)實(shí)例結(jié)構(gòu)體的繼承和類結(jié)構(gòu)體的繼承,Gobject 對(duì)象的初始化可分為兩個(gè)部分:類結(jié)構(gòu)體初始化、實(shí)例結(jié)構(gòu)體初始化。類結(jié)構(gòu)體初始化函數(shù)只被調(diào)用一次,而實(shí)例結(jié)構(gòu)體的初始化函數(shù)的調(diào)用次數(shù)等于對(duì)象實(shí)例化的次數(shù)。這意味著,所有對(duì)象共享的數(shù)據(jù),可保存在類結(jié)構(gòu)體中,而所有對(duì)象私有的數(shù)據(jù),則保存在實(shí)例結(jié)構(gòu)體中。為每一個(gè)對(duì)象分配一個(gè) ID,即 GType 這個(gè)用于標(biāo)識(shí)類型的值,使用引用計(jì)數(shù)方式進(jìn)行內(nèi)存管理。
GLib 可謂 C 語(yǔ)言中的“STL”,在此之前,動(dòng)態(tài)數(shù)組、鏈表、哈希表等通用容器,可能每個(gè) C 開(kāi)發(fā)者實(shí)現(xiàn)過(guò) N 次以上。甚至在同一個(gè)項(xiàng)目里,出現(xiàn)幾份鏈表的實(shí)現(xiàn),也并非罕見(jiàn)。GLib 的開(kāi)放終結(jié)了重復(fù)造輪子的惡夢(mèng)。GLib 提供動(dòng)態(tài)數(shù)組、單/雙向鏈表、哈希表、多叉樹(shù)、平衡二叉樹(shù)、字符串等常用容器。完全面向?qū)ο笤O(shè)計(jì),完全跨平臺(tái),通用的 set/get 屬性訪問(wèn),內(nèi)部實(shí)現(xiàn)信號(hào)機(jī)制(本質(zhì)是?Observer?Design?Pattern)。
GStreamer 就是一個(gè)基于 GLib 構(gòu)建的通用流媒體應(yīng)用程序開(kāi)發(fā)框架,GStreamer 最顯著的用途是在構(gòu)建一個(gè)播放器上,支持多種格式,包括: MP3、Ogg/Vorbis. MPEG-12、AVI、Quickime、mod 等等。
Geany 是基于 GTK+ GLib 實(shí)現(xiàn)的一個(gè)輕量快速的 IDE,集成了語(yǔ)法高亮、命令自定義、項(xiàng)目構(gòu)建功能以及插件擴(kuò)展,可以實(shí)現(xiàn) Make 等外部功能集成,基本上達(dá)到輕量與快速的目標(biāo)。但是遠(yuǎn)達(dá)不好好用的級(jí)別,界面設(shè)計(jì)還是停留在傳統(tǒng)的區(qū)域分割設(shè)計(jì),強(qiáng)制需要鼠標(biāo)點(diǎn)點(diǎn)點(diǎn)(鼠標(biāo)手警告)。和 Sublime Text 不在同一級(jí)別,只能和 Notepad 或 Editplus 相比較,但也打不過(guò)人家小巧可愛(ài)。?
https://zenlayer.dl.sourceforge.net/project/notepadplusplus.mirror/v8.5.7/npp.8.5.7.Installer.x64.exe
基于 GLib OOP 程序開(kāi)發(fā)涉及以下方面的內(nèi)容:
1. GObject instantiation
2. GObject properties (set/get)
3. GObject casting
4. GObject referencing/dereferencing
5. glib memory management
6. glib signals and callbacks
7. glib main loop
本文篇幅受限,不能涉及所有的內(nèi)容,只限于類型系統(tǒng)基本原理、和 GObject 基本使用。
面向?qū)ο缶幊瘫举|(zhì)上是人類抽象能力集中體現(xiàn),計(jì)算機(jī)編程中一切數(shù)據(jù)類型都是抽象概念。比如說(shuō),整數(shù)、浮點(diǎn)數(shù)它們真實(shí)存在計(jì)算機(jī)系統(tǒng)內(nèi)嗎?其實(shí)沒(méi)有。它們基于人類構(gòu)建出來(lái)用于表達(dá)抽象概念的機(jī)械之上才得以呈現(xiàn)。同樣的,高級(jí)語(yǔ)言中的函數(shù)、類方法等等,都是抽象而來(lái)的概念,本質(zhì)上它們都是 CPU 控制數(shù)據(jù)總線從磁盤加載到內(nèi)存中的一段具有典型特征的代碼,這些特征包括:使用 push 以及 call 指令,在返回的位置調(diào)用 pop 指令。
“抽象”這一概念的最佳說(shuō)明就是畢加索的《公牛》,全畫(huà)幾乎就是用了少量簡(jiǎn)單的線條完全概括出牛的生物結(jié)構(gòu)。這副畫(huà)并不是一次畫(huà)成的,而是從具象的牛慢慢地,經(jīng)過(guò)多次演繹才演變?yōu)樽罱K版本的極簡(jiǎn)牛!《公?!樊?huà)作創(chuàng)作時(shí)間從 1945年12月5日到1946年1月17日完稿,長(zhǎng)達(dá) 6 周有余。

說(shuō)明抽象這一概念的另一個(gè)例子是數(shù)學(xué),一個(gè)蘋果和另一蘋果,一個(gè)繩結(jié)和另一個(gè)繩結(jié),這些都是具象,這些都在人數(shù)數(shù)學(xué)誕生前計(jì)數(shù)的概念,當(dāng)一個(gè)蘋果成為 1,一個(gè)繩結(jié)也成為 1 之后,兩個(gè)蘋果或者兩個(gè)繩結(jié)就是 1+1=2,數(shù)學(xué)就這樣誕生了!而 1、2 和 + 都是數(shù)學(xué)符號(hào),= 號(hào)是約定規(guī)則符號(hào)。
抽象就是要教會(huì)我們抓住研究對(duì)象最本質(zhì)的東西,通過(guò)概括完成對(duì)復(fù)雜的事物進(jìn)行系統(tǒng)的梳理,這就是少即是多的哲理。抽象是共通于生活、編程、藝術(shù)等等領(lǐng)域共通的基本能力。
從抽象出發(fā),OOP 中的類形這一概念就是對(duì)一切可能的數(shù)據(jù)結(jié)構(gòu)的高度概括,類定義就可以看到是這一概念具象化的第一步,*類型實(shí)例化*則是這一概念具象化的下一步,最后*類實(shí)例化*完成了抽像概念的最終具象。這個(gè)過(guò)程就像是從抽象的牛到各種品種的牛,再到某人家的牛,從概念到具象的過(guò)程。
實(shí)現(xiàn)“類型實(shí)例”就是在創(chuàng)建更多的 "Class",而“類實(shí)例化”就創(chuàng)建更多某種類型的具象 "Instance of Class"。這個(gè)描述可能有點(diǎn)拗口或混亂,換個(gè)說(shuō)法就是“type instances”和“class instances”的區(qū)別。在編程中,`Type` 和 `Class` 是兩個(gè)經(jīng)常用到的術(shù)語(yǔ),當(dāng)使用 Type 時(shí)通常是指高度抽象的類型,使用 Class 則是指經(jīng)過(guò)一輪具象處理的類型,就像從“?!钡健澳膛!边@一過(guò)程。具象化即實(shí)例化,對(duì)抽象類型進(jìn)行具象化就是具體的類型,對(duì)具體類型的具象化就是類實(shí)例。在實(shí)際的編程工作中,主要關(guān)心的是使用 `class` 關(guān)鍵字定義類型,使用 `new` 關(guān)鍵字實(shí)例化這個(gè)類型得到一個(gè)具體的“對(duì)象”。OOP 中最令人迷惑的術(shù)語(yǔ)大概就是 Object 一詞,這個(gè)問(wèn)題在 JavaScript 的實(shí)現(xiàn)中尤甚。
動(dòng)態(tài)類型語(yǔ)言中,典型代表有 JavaScript、TypeScript、Python、PHP 等等,這此語(yǔ)言更多的是使用 duck typed,即叫起來(lái)像鴨子、走起路來(lái)也像鴨子、長(zhǎng)得也像鴨子,那么就可以認(rèn)它是鴨子。這是一種生物學(xué)人類思想,是動(dòng)態(tài)類型語(yǔ)言的基本類型實(shí)現(xiàn)邏輯:dynamically typed。
TypeScript 示范代碼如下,注意花括號(hào)是 JavaScript 中的對(duì)象字面類的表達(dá)形式。可以進(jìn)行 duck = darkDuck 這樣的賦值,因?yàn)?darkDuck 擁有 duck 的所有特性(這里指 gaga),相當(dāng)于 C++ 繼承類型系統(tǒng)中的子類型。返過(guò)來(lái),并不能將 duck 賦值給 darkDuck,因?yàn)樗笔Р糠旨嫒莸奶匦裕?/p>
原生類型可以認(rèn)為是只有數(shù)據(jù)的對(duì)像的抽象結(jié)構(gòu)(char, int, long, float, double),而復(fù)雜類型可以認(rèn)為是除了數(shù)據(jù),還封裝了相應(yīng)接口方法的抽象結(jié)構(gòu)。C++ 入門課程一般都會(huì)學(xué)習(xí) Abstract Data Types (ADT) 概念,通常指的是復(fù)雜的類型 (Lists, Sets, and Maps),但是在我看來(lái),編程中涉及的所有數(shù)據(jù)類型都是抽象數(shù)據(jù)類型,只是復(fù)雜程度不一樣。
另外,在中文編程教材中經(jīng)常會(huì)出現(xiàn)一個(gè)詞“堆棧”,比如說(shuō)堆棧溢出導(dǎo)致程序崩潰。其中棧和堆對(duì)應(yīng) Stack & Heap,是程序裝入內(nèi)存后運(yùn)行中需要使用的兩塊內(nèi)在區(qū)域。Heap 單詞本來(lái)指一些東西凌亂地堆在一起的狀態(tài),Stack 單詞同樣也指一些東西堆在一起,但是整齊堆疊在一起。堆內(nèi)存相對(duì)較大,可以是操作系統(tǒng)中所有未使用的內(nèi)存空間,而棧內(nèi)存相對(duì)較小,通常在程序運(yùn)行時(shí)配置其大小,比如說(shuō) 10MB。
Stack 是一種 FIrst-in Last-Out (FILO) 或者 Last-in First-Out (LIFO) 數(shù)據(jù)結(jié)構(gòu),它的特別之處在于:CPU 硬件內(nèi)置了一個(gè) Stack Pointer (SP) 棧內(nèi)存指針寄存器。另一個(gè)重要的寄存器是 Program Counter (PC) 通用寄存器,用來(lái)指向程序要運(yùn)行的下一條指令的地址。程序執(zhí)行時(shí),每當(dāng)調(diào)用函數(shù)就會(huì)使用 push 指令在 Stack 內(nèi)存中規(guī)則地存入?yún)?shù)和返回上層函數(shù)的地址,函數(shù)返回時(shí)則使用 pop 指令將相應(yīng)傳入的數(shù)據(jù)從 Stack 中移除(回收 Stack 內(nèi)存)。所以這些有限的 Stack?memory 總會(huì)有可能出現(xiàn)耗盡的時(shí)候,遞歸函數(shù)調(diào)用經(jīng)常會(huì)導(dǎo)致堆棧溢出問(wèn)題。
回到 GLib OOP 框架,GObject 則是意圖呈現(xiàn)上面所述的抽象過(guò)程,開(kāi)發(fā)者從這個(gè)抽象(GTypeInstance 和 GTypeClass)演化出更多其它類型的實(shí)現(xiàn),最終用戶對(duì)這些構(gòu)建出來(lái)的類型進(jìn)行實(shí)例化并使用它。
C 語(yǔ)言本身發(fā)布比較早,1971 年的時(shí)候還沒(méi)有 OOP 編程的語(yǔ)法規(guī)范,所以在 C 上使用 OOP 編程思想,就是直接定義函數(shù)作用類型對(duì)象的 API 方法。本質(zhì)上,函數(shù)就是內(nèi)存上的一段代碼,根據(jù) C 語(yǔ)言調(diào)用函數(shù)規(guī)則以及函數(shù)地址,就可以在外部(其它語(yǔ)言)調(diào)用 C 語(yǔ)言實(shí)現(xiàn)程序中導(dǎo)出的函數(shù)。比如,Python、Erlang、JavaScript (WebAssembly) 或者 PHP 等等,這種語(yǔ)言間的互調(diào)用 (interoperability Interprogramming) 最能體現(xiàn) C 語(yǔ)言作為底層語(yǔ)言的強(qiáng)大。
以下是一段 C 語(yǔ)言程序代碼,以及調(diào)用靜態(tài)函數(shù)時(shí)對(duì)應(yīng)的匯編指令。靜態(tài)函數(shù)就是 C/C++ 中一個(gè)處理單元(一個(gè)源代碼文件)可以訪問(wèn)的函數(shù)。
函數(shù)的調(diào)用約定 calling conventions 包含參數(shù)的入棧順序,對(duì)寄存器也有影響,以 x86 cdecl,即 C 語(yǔ)言函數(shù)的調(diào)用約定為例:
1 - 函數(shù)參數(shù)通過(guò)棧傳遞,參數(shù)列表按從右到左順序壓入棧內(nèi)存,并且由調(diào)用者負(fù)責(zé)清理?xiàng)V械膮?shù)。
2 - 整型值和內(nèi)存地址通過(guò) EAX 返回。
3 - EAX, ECX, EDX 由調(diào)用者負(fù)責(zé)保存,其余的由被調(diào)函數(shù)負(fù)責(zé)保存。
C/C++ 默認(rèn)使用 `__cdecl` 調(diào)用約定,由于此方式由主調(diào)函數(shù)負(fù)責(zé)參數(shù)入棧、清棧,所以可以實(shí)現(xiàn) `vararg` 即變參函數(shù),參數(shù)列表使用 `va_list` 引用。
微軟官方聲明 `__pascal`, `__fortran`, `__syscall` 等為過(guò)時(shí)約定不再支持,參考下表https://learn.microsoft.com/en-us/cpp/cpp/calling-conventions
假設(shè),要通過(guò) Python 調(diào)用以上定義了靜態(tài)函數(shù),那么就需要按以下步驟操作:
1. 在 C 語(yǔ)言編譯器生成的程序中找到導(dǎo)出函數(shù)的地址;
2. 加載可執(zhí)行內(nèi)存(操作系統(tǒng)根據(jù)程序代碼需求分類內(nèi)存)中的函數(shù)代碼;
3. Python 將參數(shù)轉(zhuǎn)換為兼容 C 語(yǔ)言的類型;
4. 按照 C 語(yǔ)言調(diào)用約定 `__cdecl` 處理參數(shù)入棧并調(diào)用函數(shù);
5. 將返回值轉(zhuǎn)換回 Pythong 的數(shù)據(jù)類型;
Foreign function interface (FFI) 就是用來(lái)表示這種互調(diào)用的術(shù)語(yǔ),這兩種語(yǔ)言中間進(jìn)行數(shù)據(jù)轉(zhuǎn)換的代碼稱為膠水代碼 glue code。
參考 Python-3.11.0 源代碼中的文檔:
1. Doc\c-api\abstract.rst? ? ? ? ? ? ? ?Abstract Objects Layer
2. Doc\library\ctypes.rst?? ? ? ? ? ? ?? A foreign function library for Python
3. Doc\extending\embedding.rst? ?Extending and Embedding the Python Interpreter
膠水代碼可以手寫,為每一個(gè)導(dǎo)出函數(shù)手寫脫水代碼是勞動(dòng)生產(chǎn)效率最低的方式。另一種更高效的方式是使用專用編譯器,根據(jù)導(dǎo)出函數(shù)的簽名自動(dòng)生成相應(yīng)的脫水代碼。
GType 的解決方案是使用通用膠水代碼,其最大優(yōu)點(diǎn)是:位于運(yùn)行時(shí)域邊界的膠水代碼只寫一次,下圖更清楚地說(shuō)明了這一點(diǎn)。

目前,存在多個(gè)通用的粘合代碼,這使得可以在各種語(yǔ)言中直接使用 GType 編寫的 C 對(duì)象,只需最少的工作量:不需要自動(dòng)或手動(dòng)生成大量的粘合代碼。GType/GObject 的設(shè)計(jì)不僅是為了向 C 程序員提供類似 OOP 功能,而且是透明的跨語(yǔ)言互操作性。
?? GObject–2.0 OOP 框架入門教程
設(shè)計(jì)類型時(shí)就需要考慮類名選取、繼承鏈信息、類型初始化順序、類接口方法設(shè)計(jì)等信息,這些基本概念涉及到的主要源代碼文件如下:
0. 使用 GLib 框架需要引用 glib.h 頭文件;
0. 使用 GObject 框架需要引用 glib-object.h 頭文件;
1. `GObject` 各個(gè)結(jié)構(gòu)聲明在 gtype.h 文件;
2. `GObject` 各個(gè)函數(shù)聲明在 gobject.h 文件;
源代碼包含了豐富的注解內(nèi)容,一般函數(shù)命名規(guī)律是:以相關(guān)對(duì)象名稱為前綴。比如 GObject 對(duì)象的相關(guān)函數(shù)就有 getv 或 setv,完整名稱就是 `g_object_getv` 和 `g_object_setv`。文檔包含了一個(gè)符號(hào)列表文件,所有 GObject 模塊中定義的宏符號(hào)、函數(shù)都分類羅列在: glib-2.78.0\docs\reference\gobject\gobject-sections.txt
`GObject` 各個(gè)結(jié)構(gòu)聲明在 gtype.h 文件,注意對(duì)位關(guān)系,邏輯說(shuō)明如下:
?* `GObject` 結(jié)構(gòu)定義的所有字段都為私有,類型實(shí)現(xiàn)者不該直接訪問(wèn);
?* `GObjectClass` 為類型實(shí)例提供構(gòu)建、析構(gòu)、屬性訪問(wèn)、消息機(jī)制等接口機(jī)制;
?* `GTypeInstance` 內(nèi)部結(jié)構(gòu),表示類型實(shí)例的基礎(chǔ)結(jié)構(gòu);
?* `GTypeClass` 內(nèi)部結(jié)構(gòu),表示類型基礎(chǔ)結(jié)構(gòu),Basic Type Structures;
?* `GType` 是一個(gè)用于標(biāo)識(shí)各種類型實(shí)例數(shù)值,即 Type ID;
?* `GTypeInterface` 是所有接口類型的基礎(chǔ)結(jié)構(gòu);
?* `TypeNode` 是記錄類型關(guān)系數(shù)據(jù)的節(jié)點(diǎn)樹(shù)中節(jié)點(diǎn)結(jié)構(gòu);
?* `GTypeQuery` 是用于記錄類型信息的結(jié)構(gòu);
?* `GTypeInfo` 和 `GTypeFundamentalInfo`,以及 `GInterfaceInfo` 是記錄類型信息的結(jié)構(gòu);
?* `GTypeFundamentalFlags` 和 `GTypeFlags` 枚舉值用于控制不同類型的功能特性;
一般地,64-bit 環(huán)境下,`GObject` 類型占據(jù) 24 字節(jié):兩個(gè)指針加一個(gè) unsigned int 用于記錄引用計(jì)數(shù)。
假定要派生一個(gè) GObject 子類型,即定義一個(gè)類型通常需要將父類型基礎(chǔ)結(jié)構(gòu)重疊(boilerplate)到子類型的結(jié)構(gòu)中,并且需要作為前綴字段定義,這樣在進(jìn)行向上轉(zhuǎn)型(轉(zhuǎn)換為父類型)就可以通過(guò)原指針獲取到父類型的結(jié)構(gòu):
1. 一個(gè)包含 `GObject` 的結(jié)構(gòu)體作為類型實(shí)例的本體,頂級(jí)類使用 `GTypeInstance`;
2. 一個(gè)包含 `GObjectClass` 的結(jié)構(gòu)體作為類型本體;
參考 `g_type_module_get_type` 方法的實(shí)現(xiàn),內(nèi)部的 `GTypeModuel` 就是這樣的一個(gè)子類型。簡(jiǎn)單概括為一個(gè)嵌套結(jié)構(gòu),類型信息結(jié)構(gòu)則會(huì)傳遞給注冊(cè)函數(shù)以登記到類型樹(shù)數(shù)據(jù)系統(tǒng)中:
從三個(gè)最基本的信息記錄用途的結(jié)構(gòu): `GTypeFundamentalInfo` 和 `GTypeInfo`,以及 `GInterfaceInfo` 就可以發(fā)現(xiàn),GLib 類型系統(tǒng)中的基礎(chǔ)類型、一般類型以及接口類型所有具有的基本功能。結(jié)合 Flags 控制位,開(kāi)啟或關(guān)閉某種特性,比如是否可被繼承、是否可以實(shí)例化、是否是 Final 類型等等。
Glib 并沒(méi)有提供 Flags 枚舉值來(lái)指定是不否是靜態(tài)類型,而是提供了多個(gè)注冊(cè)函數(shù),來(lái)區(qū)別靜態(tài)類型、基礎(chǔ)類型、一般類型和接口類型。
創(chuàng)建一般用戶類型時(shí),`GTypeInfo` 必不可少,注冊(cè)基礎(chǔ)類型時(shí)還額外增了 `GTypeFundamentalInfo`?;绢愋托畔⒂涗洶?:
1. `class_size`: class size.
2. `base_init` and `class_init`: class initialization functions (C++ constructor).
3. `base_finalize` and `class_finalize`: class destruction functions (C++ destructor).
4. `instance_size`: instance size (C++ parameter to new).
5. `n_preallocs`: instantiation policy (C++ type of new operator).
6. `value_table`: copy functions (C++ copy operators).
7. type characteristic flags: `GTypeFlags`.?
可以從 `GTypeInfo` 結(jié)構(gòu)中看到,設(shè)置有 base 和 class 兩對(duì)初始化、終止方法,而對(duì)于一個(gè)類的實(shí)例則只設(shè)置一個(gè)初始化方法。base 相關(guān)的兩個(gè)方法一般用不上,它可以在類型初始化之前,或類型析構(gòu)之后做一些工作。
一個(gè)幾乎沒(méi)有任何用途(除了測(cè)試編譯流程)的示范程序如下:
GLib 2.36 版本后,已經(jīng)不需要主動(dòng)調(diào)用 `g_type_init` 方法,GLib 系統(tǒng)自動(dòng)初始化類型系統(tǒng),而這個(gè)函數(shù)什么也不做。
Windows 系統(tǒng)上使用 Msys2 環(huán)境開(kāi)發(fā) GLib 應(yīng)用,以下命令用于檢測(cè)環(huán)境要求,以及編譯源代碼,以供參考:
可能遇到的問(wèn)題:
最后,還有一個(gè)潛在問(wèn)題,Msys2 使用的編譯器平臺(tái)不兼容,導(dǎo)致編譯出來(lái)的程序出現(xiàn)非法指針,內(nèi)存違規(guī)訪問(wèn)。這種情況會(huì)導(dǎo)致 bash 不能檢測(cè)到返回碼,-1073741819 0xC0000005 STATUS_ACCESS_VIOLATION。錯(cuò)誤碼參考 2.3.1 NTSTATUS Values。
Msys2 bash 環(huán)境中出現(xiàn)引用未定義符號(hào);
PowerShell 環(huán)境下找不到頭文件或者庫(kù)文件,是因?yàn)槊钚袥](méi)有正確傳遞頭文件目錄,以及依賴庫(kù)名稱中包含句點(diǎn),這會(huì)導(dǎo)致編譯器誤判文件名。應(yīng)該使用引號(hào)包括依賴庫(kù) `-l"gobject-2.0" -l"glib-2.0"`。
Msys2 中的依賴包默認(rèn)使用 /usr 此類絕路徑前綴,pkg-config 可以根據(jù) PKG_CONFIG_SYSROOT_DIR 環(huán)境變量指定的路徑替換依賴包 .pc 文件中的 $(prefix) 路徑。
另外,就是 PowerShell 中使用變量、或者同命令行中傳遞 pkg-config 返回的編譯器參數(shù)時(shí),會(huì)因?yàn)樽詣?dòng)給變量加引號(hào)面導(dǎo)致參數(shù)失效(所有配置項(xiàng)變成一個(gè)字符串參數(shù))。此情形不能直接使用 -split 進(jìn)行字符串分割,PowerShell 使用反引號(hào)作為轉(zhuǎn)義符號(hào),也不能像 bash 一樣使用反引號(hào)插入其它命令。解決方法有多種:
1. 使用新 pwsh 進(jìn)程的 -c 選項(xiàng)解釋命令行(惡心)。
2. Invoke-Expression 執(zhí)行字符串包含的命令及參數(shù)(依然有 glib-2.0 名稱問(wèn)題)。
3. 最佳方法是 Make 或者 CMake、Meson 等構(gòu)建工具,可以很好處理這樣的問(wèn)題。
4. 終極的解決方法是:手動(dòng)給 GCC 指定參數(shù)或者修改 GLib 依賴庫(kù)名稱和 .pc 配置。
PowerShell 調(diào)用運(yùn)算符 (&) 用于執(zhí)行腳本、腳本塊或命令,但它不會(huì)像 Invoke-Expression 那樣解釋 command 參數(shù),它會(huì)將字符串當(dāng)作一個(gè)命令(不含要傳遞的參數(shù))。
比較棘手的是引用符號(hào)未定義,這是高發(fā)編譯錯(cuò)誤,解決難度羅列:
1. 沒(méi)有給鏈接程序指定鏈接庫(kù),比如缺失 `"-lglib-2.0"` 或者路徑錯(cuò)誤;
2. GCC 鏈接庫(kù)參數(shù) -l 不要寫在源代碼文件之前,會(huì)鏈接不到庫(kù);
3. 指定依賴庫(kù)依然報(bào)錯(cuò),此情況就可能是使用了不兼容編譯器版本;
4. 最后,是真得找不到相應(yīng)的符號(hào),可能是代碼寫錯(cuò)或者庫(kù)中沒(méi)有定義;
GCC 鏈接器鏈接文件時(shí)的流程:
1. 從左往右鏈接源文件;
2. 源文件調(diào)用了沒(méi)有定義的函數(shù)、變量等,則記錄下來(lái);
3. 如果遇到 -l 指定的庫(kù),則在庫(kù)中盡可能檢索所有記錄下來(lái)的沒(méi)有定義的函數(shù)、變量,只從庫(kù)中提取用到的部分,然后拋棄此庫(kù);
4. 在全部鏈接完后,如果依然存在未定義的函數(shù)、變量,則報(bào)錯(cuò)
正因?yàn)?GCC 鏈接器的這樣的鏈接流程,并不會(huì)回過(guò)頭來(lái)檢索之前鏈接過(guò)的庫(kù),因此要求按正確順序傳遞依賴庫(kù)。由此可知,即使兩個(gè)庫(kù)有相同的函數(shù)、變量的定義,最終只有先找到的庫(kù)中定義的符號(hào)才生效。
以下是 meson.build 腳本用 Meson 構(gòu)建命令參考(glib-2.0 依賴可以省略,因?yàn)?gobject-2.0 已經(jīng)包含):
很難下結(jié)論說(shuō),到底 CMake、Meson 這類功能豐富的自動(dòng)化構(gòu)建功能好(甚至支持 CI/CD 功能),還是說(shuō) GNU Make 這種經(jīng)典的小可愛(ài)好。雙方都有優(yōu)點(diǎn)和缺點(diǎn)。功能豐富意味著,學(xué)習(xí)它需要涉及更多的概念(心智負(fù)擔(dān)、學(xué)習(xí)曲線),但是經(jīng)過(guò)優(yōu)化,有些功能可以做到開(kāi)箱即用。比如說(shuō) Unit tests,可以在 meson.build 腳本中調(diào)用 `test` 函數(shù)設(shè)置需要進(jìn)行單元測(cè)試的程序,它可以接收 `excutable` 函數(shù)返回的 exe 對(duì)象。如果事先不知道這一點(diǎn),那么就會(huì)對(duì)編腳本中返回的 exe 對(duì)象形成抽象概念理解的困難。
Meson 單元測(cè)試使用 `meson test` or `meson test "*hello"` 等命令形式調(diào)用,生成的報(bào)告會(huì)保存在 `meson-logs/testlog.txt`。
使用傳統(tǒng)的 Make 腳本,由于它功能特性保持穩(wěn)定,一旦掌握就不需要消耗更多的精力去學(xué)習(xí)各種細(xì)枝末節(jié)的功能。并且,Make 也可以通過(guò)和第三方編程工具結(jié)合,比如 Node、Deno 或者 Python 等腳本編程工具實(shí)現(xiàn)各種功能,根本不需要用到 GNU Make 本身提供的 C/C++ 插件機(jī)制。
以下為項(xiàng)目目錄結(jié)構(gòu),build-examples 是構(gòu)建輸出目錄,stackdump 文件是 GLib 程序運(yùn)行時(shí)錯(cuò)誤產(chǎn)生的內(nèi)存轉(zhuǎn)儲(chǔ)文件:
經(jīng)過(guò)以上目錄結(jié)構(gòu)設(shè)計(jì),就可以很方便地使用 watch 工具進(jìn)行監(jiān)測(cè)并自動(dòng)捃重新構(gòu)建,幾乎就是完全自動(dòng)化的開(kāi)發(fā)構(gòu)建體驗(yàn)。另外,配合 Sublime Text + Origami 布局插件 + Terminus 控制臺(tái)插件,使用體驗(yàn)略勝 VS Code(除程序調(diào)試功能外)。
項(xiàng)目嘗試使用雙 Makefile 設(shè)計(jì),根目錄中的 Makefile 只包含一條 `include src/Makefile` 指令,用于引用 src 目錄下的主腳本。主腳本內(nèi)容及實(shí)現(xiàn)的功能如下:
1. 設(shè)置默認(rèn)的構(gòu)建輸出目錄 OUTPUT = build-examples;
2. 設(shè)置默認(rèn)的源代碼目錄 SRC = src;
3. 設(shè)置默認(rèn)的構(gòu)建輸出的程序文件 BINS,包含 hello.c 和 static.c 兩個(gè)演示程序;
4. 強(qiáng)制用戶在根據(jù)目錄中執(zhí)行 make 命令,并將構(gòu)建生成的文件保存在 OUTPUT 目錄;
5. 檢測(cè)系統(tǒng)是否 Windows NT,如果是就使用 .exe 作為默認(rèn)程序擴(kuò)展名;
6. 果系統(tǒng)還沒(méi)有設(shè)置 PKG_CONFIG_SYSROOT_DIR 環(huán)境變量,就以 c:/msys64 為默認(rèn)值;
7. 設(shè)置了測(cè)試功能,并且為每個(gè)程序 touch 一個(gè) .test 目標(biāo)文件用作更新時(shí)間檢測(cè);
8. 使用命令前綴 - 忽略錯(cuò)誤,可以根據(jù)命令返回碼來(lái)決定是否執(zhí)行 touch .test;
依賴關(guān)系是 all -> prepare BINS test,在構(gòu)建程序之前先準(zhǔn)備創(chuàng)建好輸出目錄,構(gòu)建完程序后,再執(zhí)行測(cè)試。測(cè)試目標(biāo)規(guī)則中,定義依賴對(duì)應(yīng)的程序目標(biāo)。每個(gè)程序目標(biāo)規(guī)則定義了依賴的源代碼文件,由于一個(gè)源代碼文件生成一個(gè)程序,所以這種依賴處理起來(lái)顯得更容易。另一方面,測(cè)試目標(biāo)依賴關(guān)系是:test -> BINS -> sources,所以源代碼更新會(huì)導(dǎo)致程序重新構(gòu)建,進(jìn)行導(dǎo)致測(cè)試需要重新執(zhí)行。如果程序沒(méi)有更新,就不會(huì)觸發(fā)重新測(cè)試。主腳本中主要是使用了 Static Pattern Rules 來(lái)重新映射 Targets 或者依賴關(guān)系,同時(shí)結(jié)合高級(jí)變量引用表達(dá) `$(VAR:%=%)`,實(shí)現(xiàn)目標(biāo)命名的批量處理、得命名。參考 Make 手冊(cè) 4.12 Static Pattern Rules 以及 6.3.1 Substitution References。
因?yàn)橐獙?duì)待測(cè)試程序的返回碼乾驗(yàn)證,就需要將測(cè)試命令塊定義為 `.ONESHELL`,這樣才會(huì)在一個(gè) shell 進(jìn)程中執(zhí)行命令,否則命令按行為單位執(zhí)行,這就導(dǎo)致無(wú)法獲取到前面程序的返回碼。
最后,使用 watch 工具進(jìn)行開(kāi)發(fā)測(cè)試依然是更方便的操作:

創(chuàng)建 GObject 自定義類型,首先要明確 GLib 各種結(jié)構(gòu)的基本功能與關(guān)系,至少要清楚創(chuàng)建一個(gè)子類型的基本要素。假定要派生一個(gè) GObject 子類型,即定義一個(gè)類型通常需要:
1. 一個(gè)包含 `GObject` 的結(jié)構(gòu)體作為類型實(shí)例的本體;
2. 一個(gè)包含 `GObjectClass` 的結(jié)構(gòu)體作為類型本體;
參考 `g_type_module_get_type` 方法的實(shí)現(xiàn),內(nèi)部的 `GTypeModuel` 就是這樣的一個(gè)子類型。GLib 內(nèi)部使用 `g_type_register_static` 注冊(cè)的類型,還可以稱之為 Module Type。 glib-2.78.0\gobject\gtypemodule.c
可以使用 `GObject` 作為父類型進(jìn)行擴(kuò)展,它提供了以下功能:
1. 引用計(jì)數(shù)器 reference counting,當(dāng)實(shí)例引用計(jì)數(shù)重置為 0 時(shí)就會(huì)回收其所占內(nèi)存;
2. 子類型可以添加任意屬性進(jìn)行擴(kuò)展;
3. 提供異步信號(hào)事件處理服務(wù);
為了簡(jiǎn)化起見(jiàn),現(xiàn)在來(lái)實(shí)現(xiàn)一個(gè)不能乾實(shí)例化操作的單例靜態(tài)類型,所謂單例 Singleton 即是指只有一個(gè)實(shí)例存在的類,這是一種常用編程模式。
最簡(jiǎn)單的類型定義就是各種內(nèi)置的值類型,參考宏符號(hào) `G_REAL_CLOSURE`,以及注冊(cè)各種值類型的 `_g_value_types_init` 方法。
`GTypeInfo` 中的 n_preallocs 字段適用于 GLib 2.10 之前版本,GLib 2.10 開(kāi)始忽略此字段。用于指定預(yù)分配內(nèi)存空間(預(yù)先保留內(nèi)存空間),0 表示不使用緩存。
如果要定義派生類型,那么派生類型必需使用以上所說(shuō)的父類型基礎(chǔ)結(jié)構(gòu),在子類型工中稱之為重疊結(jié)構(gòu) boilerplate,因此子類型占據(jù)內(nèi)存空間必然比父類型多。否則,在注冊(cè)類型時(shí)就會(huì)給出異常提示信息。如果,后續(xù)對(duì)錯(cuò)誤的類型進(jìn)行引用、實(shí)例化,則會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤終止程序:
與 `GObject` 相對(duì)應(yīng)的是 `GObjectClass`,這個(gè)結(jié)構(gòu)與 `GObectInfo` 結(jié)構(gòu)同樣重要。后者提供類型信息,前者為各種類型提供類型構(gòu)建、析構(gòu)、屬性訪問(wèn)、消息機(jī)制等接口機(jī)制。全局類型的定義與注冊(cè)在 `_g_object_type_init` 方法中完成。
如果是注冊(cè)頂級(jí)基礎(chǔ)類型,就像 `GObject` 不需要父類型的信息,使用 `g_type_register_fundamental` 方法進(jìn)行注冊(cè)??梢允褂?`g_type_fundamental_next()` 為用戶定義的頂級(jí)類型獲取一個(gè)自動(dòng)分配的 ID,
對(duì)于可以實(shí)例化的類型,那么就可以使用 `g_type_create_instance` 方法進(jìn)行實(shí)例化。如果有成員方法,那么它應(yīng)該接收一個(gè) instance 作為第一個(gè)參數(shù),成員方法的操作應(yīng)該相對(duì)于這個(gè)指定的實(shí)例對(duì)象。就像 Python 中定義類成員方法時(shí),第一個(gè)參數(shù)總是為 self。
因?yàn)轭愋?ID 已經(jīng)登記在 GLib 類型節(jié)點(diǎn)樹(shù)中,調(diào)用實(shí)例化方法時(shí),就會(huì)根據(jù)指定類型 ID 找到相應(yīng)的類型定義,并且判斷此類型是否是 instantiatable 類型,如果確定是才可以繼續(xù)進(jìn)行實(shí)例化。
使用 `G_TYPE_CHECK_INSTANCE_TYPE` 或者 `G_TYPE_CHECK_INSTANCE_FUNDAMENTAL_TYPE` 兩個(gè)宏就可以判斷指定的實(shí)例與指定類型 ID 是否有關(guān)系,從而判斷其是否為指定類型的實(shí)例。它們根據(jù)類型信息結(jié)構(gòu)中提供的 `GTypeInstance` -> `GTypeClass` 關(guān)系鏈來(lái)確定其類型:instance->g_class->g_type。如果類型不能通過(guò)這個(gè)此方法判斷,內(nèi)部還有兩個(gè)備選的方法,它們會(huì)調(diào)用 `lookup_type_node_I` 方法去 `TypeNode` 查找節(jié)點(diǎn)數(shù)據(jù)。
以下測(cè)試程序應(yīng)該輸出類似以下內(nèi)容,如果是這樣,那么恭喜你已經(jīng)可以開(kāi)發(fā) GObject 應(yīng)用了!
static.c 程序源代碼參考如下,注意,以下 KickFundament 和 KickStatic 兩個(gè)類型共享的同一個(gè) GTypeInfo 信息結(jié)構(gòu),實(shí)際中應(yīng)該你使用獨(dú)立的信息結(jié)構(gòu):
《Makefile 光學(xué)教程》之面向 Makefile 編程·C/C++ 項(xiàng)目模板 [Glib-2.0 & ADT]的評(píng)論 (共 條)
