打造 GTK 的 Forth 程式語言介面

KsanaGTK: a Forth binding for GTK

剎那搜尋工坊 葉健欣 2008.8.8

2008.8.12 修訂

2008.8.13 支援Linux , svn revision 22

2008.10.28 多媒體影音教學(需安裝 wink) 源碼解說 追縱

目錄


一、所需的基礎知識
二、安裝
三、KsanaGTK 簡介
四、源碼詳解
1. ficl_gtk_symbols.c
2. ficl_gtk_signal.c
3. ficl_gtk_builder.c

五、追縱指引
六、全局觀

一、所需的基礎知識

在閱讀本教材之前,如果你從來沒聽過Forth 這個程式語言,請先閱讀Wikipedia 上的解說。本教材使用到的Forth 指令很少,大概只有 VALUE, DROP, ' (tick) , ." , CR 等幾個。對Forth 稍有涉獵者應該都很熟悉。

由於筆者於本週開始正式接觸 GTK,在此之前,沒有看過其技術文件或源碼,當然更沒有開發過任何基於 GTK 的應用,所以本教材絕不是為 GTK 高手而選寫的。

當然,你也最好具備一些 GUI的基本觀念,如視覺元件、事件的觸發、訊息的迴路等。如果你還不太清楚也沒關係,只要能將本教材認真讀完,並追縱原始碼,將會對 GUI的工作原理有一個比較全面的認識。

二、安裝


源碼:http://svn.ksana.tw/svn-repos/ksanagtk GPL 3.0
安裝詳細步驟

三、KsanaGTK 簡介

本教學是講解 KsanaGTK 的工作原理。KsanaGTK 是一個 GTK 和 Forth 的Binding。

GTK 的計劃主要就是衝著 QT 而來,QT 鼓勵直接用C++開發,其訴求是write once , compile everywhere。而GTK一開始就不假設大家會用C來撰寫GTK應用,而是可以搭配其他的腳本語言,得到快速原型開發、高度彈性以及免除跨平台編譯的麻煩。

GTK 官方網站公佈了15 種程式語言的介面,有了這麼多的其他語言抬轎,對GTK來說,如虎添翼,快速拓展使用群;對非 C 的程式員來說,可以繼續使用自己熟悉的腳本語言,來使用GTK這樣一個高效能、高品質的GUI元件庫,是魚與熊掌兼得的好事。

但讓其他腳本語言調用GTK的功能之前,需要一個所謂的Language Binding的機制,來橋接兩個語言。不同的語言就像兩個世界,有各自的「習慣」,如函式呼叫的規範、堆疊、暫存器使用方式、記憶體的管理等等。Language Binding 橋接了兩個彼此不往來的世界,也就是兩個不同的「執行情境」(Execution Context),一般認為是比較複雜高深的技術。

PHP, Perl, Python 這幾個主流腳本語言的 GTK Binding 動輒要數MB ,即使是號稱最精簡的Lua-GTK 也要273KB,如果你沒有過人的意志,是不容易一窺全貌的。腳本語言的語法看起來都很簡單,但其實上內部做了大量的苦工,從軟體工程的觀點來看,只是將複雜度往內推。因此,即使高階程式有豐富的編程經驗,但對其內部結構所知還是有限,我覺得十個python/perl/php programmers 之中很難找到一個有能力讀python 的C source 之人,不知這樣說有沒有過份?

KsanaGTK 則展現了一個新的可能,由於 Forth 的執行機制本身就很精巧(請參考另一個教學:剎那極簡虛擬機。Forth 沒有任何語法上的糖衣,沒有任何被隱藏的複雜度。事實上,任何一個稱職的Forth 程序員,都應該有能力自己打造一套自己的Forth。KsanaGTK 目前不用 KsanaVM ,而採用 FICL (Forth inspired control language) ,主要是因為 Ficl 比較完整,文件也齊全。而且教學的重心在於橋接的介面,所以用那套Forth其實關係不大。

主流的腳本語言和 Forth,背後有著完全不一樣的哲學。主流的腳本語言就像西醫治病:醫生需要長期的訓練、醫療的設備、藥物的配方等等,背後都由極為龐大的產業和機構所支撐。醫生、病名、藥名越來越多,大家越來越搞不懂,搞不懂就會害怕,而這份恐懼讓大家願意繼續受醫療體系所宰制。而Forth 就像中醫,你只需對身體的系統觀的理解 (氣血、經脈、臟腑、時節) 和掌握之間的互動關係,中醫所謂的治療,只是幫助你的身體回到平衡的狀態,以啟動自我修復機制,所以沒有醫生、沒有藥、可以用針炙、用推掌刮痧,一樣有療效。到了最高境界,就不必借助任何外部工具,只要打坐、吐納、練氣即可怯百病、得高壽。這不是老子說的「為學日益、為道日損」的最佳實證嗎?

所以我們可以看到 KsanaGTK 只有3個檔 12KB,約300行C程式,就溝通了 GTK和 Forth 兩個世界。這個規模的程式碼,和大一新生的課堂作業相差無幾,相信每個人都有能力遍讀。當然KsanaGTK 的完整性也許無法和其他語言相提並論,不過,對於理解語言之間的Binding原理,這樣就足夠了。當你完全搞懂了 KsanaGTK 的工作原理,再去看其他的語言Binding ,就會覺得問題的本身很簡單,只是有人將它搞得很複雜。所以,不要再沉迷於各種新奇語言、語法的花拳繡腿,請回到計算機的本質,澈底理解之、熟練之、再內化為你思路的一部份,內力就是這樣練出來的。而 Forth 就是捷徑,金城老師稱之為「人機合一的無上心法」。

四、源碼詳解

本教學的製作環境是在 Windows Vista 和 Visual C++ studio 2008, 先聲明一下個人對Windows 沒有偏愛、也不特別討厭,Visual C++ 是公司付的正版專業版。KsanaGTK 是用很平舖直叙的C所寫就,目前可以在Windows 和 Linux 底下運行。以下就來逐行講解實現 KsanaGTK 的三個模組。

1. ficl_gtk_symbols.c

這個模組的功能是連結GTK的函式,在Windows 下,需要先載入ligtk-win32-2.0-0.dll等 6個 DLL,放到一個 dl_handle的陣列之中,見 gtk_dll_init()。lookup_symbol的功能依名稱,到每個DLL尋找函式,在Windows 下,是由GetProcAddress實現,在Linux 下則為 dlsym。

本模組只實現了一個Forth 的字,稱為 gtk: ,用法是

1 gtk: gtk_window_new   \ 連結名為 gtk_window_new 的函式, 此函式需要一個參數

然後,就可以這樣用
0 gtk_window_new        \ 創建一個 window 視覺元件

所以,連結GTK 的方式就是先查手冊,確認函式名稱以及要傳入參數的個數,再用 gtk: 來建造。更多的例子請看gtk.f。可以將 gtk: 想像成一個「橋樑產生器」,每次使用,就會建造出一個溝通 forth 和 gtk 兩個世界的橋樑。

接下來我們看看這個橋樑產生器如何實現:

void	ficlCompileGtkMakeSymbol(ficlVm *vm)
{
	ficlDictionary  *dict=ficlSystemGetDictionary(vm->callback.system);
// 創造 gtk: 這個新字,由 ficl_make_gtk_func 實現
	ficlDictionarySetPrimitive(dict,"gtk:",ficl_make_gtk_func,FICL_WORD_DEFAULT);
}


void  ficl_make_gtk_func(ficlVm *vm)
{
    // 取得函式的參數個數 (gtk: 之前的數字)
	int narg=ficlStackPopInteger(vm->dataStack); 
	
	 // 取得函式的名稱  (gtk:之後的字串)
	ficlString name = ficlVmGetWord(vm);
	
	// forth 字典,建造橋樑的所在
	ficlDictionary *dic=vm->dictionary;
	
	// 指向 gtk api 的進入點
	void *gtk_api;
	
	// 函式名
	char funcname[0x100];
	// 將gtk: 後面的字串複製到 funcname
	strncpy_s(funcname,sizeof(funcname),name.text,name.length);

    // 尋找進入點
	gtk_api=lookup_symbol(funcname);
	
	if (gtk_api) // 非零,找到了
	{
	    // 建造一個新的 Forth 字,這個字由 ficlCEntryPoint
		ficlDictionaryAppendWord(dic, name, (ficlPrimitive)ficlCEntryPoint, FICL_WORD_DEFAULT );

// 要存兩項資料,此API 的進入點
ficlDictionaryAppendUnsigned(dic,narg);
// 以及參數個數
ficlDictionaryAppendUnsigned(dic,(int)gtk_api);
} else { // 找不到的話,就丟出錯誤訊息 ficlVmThrowError(vm, "gtk symbol %.*s not found", FICL_STRING_GET_LENGTH(name), FICL_STRING_GET_POINTER(name)); } }

因此,每搭一座橋的成本是一個Forth 字,加上8bytes 的空間,只比建造一個Forth 變數多 4 個 bytes 而已。

在上面的程式碼我們可以看到,gtk: 所建造的字,都將調用ficlCEntryPoint這個函式,也就是說,在 ficl 中,任何呼叫 GTK API 的字,都會先進入這個函式。ficlCEntryPoint 的主要任務就是將Forth 的堆疊搬到 C 的堆疊,然後呼GTK 函式。

static void ficlCEntryPoint(ficlVm *vm)
{
// 取得Forth 的param field指標,緊接在這個指標後面的 8個bytes ,就是使用 gtk: 編入字典的兩項數據
	int *param=(int*)(vm->runningWord->param);   
	
// 注意這和上面的粉紅色區是對應的

// 取得 GTK 函式進入點
void *gtk_api=(void*)(*param);
// 接著取得此函式的參數個數
int narg = *(param+1);
int p,i,r; // 將 Forth 的堆疊資料搬到 C , 這裡要用到x86組合語言的push 指令, // 因為 C 不支持在編譯時期未知參數個數的函式呼叫 for (i=0;i<narg;i++) { p=ficlStackPopInteger(vm->dataStack); __asm push p } __asm { // 呼叫 GTK 函式,取得返回值,放入 r call gtk_api mov r, eax // 回復 ESP 的數值 mov eax, narg shl eax,2 add esp, eax } // 將 r 放到 Forth 堆疊,在這裡我們稍微偷懶一下,有些 GTK 的函式返回值宣告為 void ,但我們不管,一律放上堆疊,如果不需要,只要加一個DROP 指令即可 ficlStackPushInteger(vm->dataStack,r); }

上面這段程式並不長,但背後涉及了一些比較少見的觀念:一般我們所謂的編寫程式,都是在編寫執行階段的行為,所謂的 runtime behavior programming,但ficl_make_gtk_func是在定義一種compile time behavior,或者說是 metaprogramming ,因為當執行 gtk: 時,會即時產生了新的「函式」出來。如果不用 Metaprogramming 的技術,那對每個GTK API ,我們都需要一個對應的C 函式,來處理參數的轉換,而由於GTK包含了成千上萬個函式,不用這樣的技巧,將大幅增加程式碼的大小,也讓維護變得極為麻煩。

2. ficl_gtk_signal.c

到目前為止,我們只打造了單向的橋樑,gtk: 可以讓 forth 呼叫任何 gtk 的函式,但是在GUI programming 中,有一個非常關鍵,稱為 signal (有時也叫 event) 和 callback ( 有時也叫 handler ) 的機制。比方說,畫面上有一個按鈕,當使用者按下時,GUI 會觸發一個 clicked 的事件,而應該有一個對應的函式來負責執行一個動作。由於這些當特定事件發生時才會被執行的函式,是由 GUI 觸發執行的,所以稱為 callback 回呼函式。除了讓Forth 來調用 GTK ,我們也必須可以使用Forth 來編寫 callback ,讓 GTK 來調用,這樣才算完成雙向的橋樑。

在 GTK 之中,聯繫元件、事件和回呼函式的API 叫作 gtk_signal_connect,其宣告如下:

gint gtk_signal_connect (GtkObject *object, gchar *name, GtkSignalFunc func, gpointer func_data)

這裡object 是元件,name 是signal 的名稱,func 是回呼函式的指標,func_data 是用戶額外要的資料,它會事件發生生,由GTK一並傳入 callback 。 GtkSignalFunc 並沒有明確的宣告,它將視訊息的類型而有所不同,這是GTK 一個非常有意思的設計,相較於 wxWidget ,提供了更大的彈性。

舉例來說,處理"clicked"事件的回呼,具有以下的形式:

on_button_click (GtkWidget *widget, gpointer data);
而 "delete_event" 事件的回呼,則是
on_delete_event ( GtkWidget *widget, GdkEvent  *event,  gpointer   data );

很顯然地,我們無法使用gtk: 來建造 gtk_signal_connect 介面,因為在 forth 的執行環境中,我們無法提供符合 C 呼叫規範的函式指標。因此,我們需要對gtk_signal_connect 做特別的處理,然後,在forth 中我們可以用以下的方式來聯繫事件和forth 回呼函式:

: on_click ( widget userdata -- )  2drop ." hello!!" ;  \ 一個簡單的forth word,只是印出 hello 

button1 z" clicked" ' on_click 101 gtk_signal_connect   \ 語意:元件button1發生事件 clicked 時執行 on_click 
                                                        \ 101 使用者額外提供的資訊

讓我們來看看 gtk_signal_connect 的實現方式。

void	ficlCompileGtkSignal(ficlVm *vm)
{
//注意,這個 vm->callback 和 GTK 無關,不要搞混。這行的目的只是要取得 ficl 的字典而已。
	ficlDictionary  *dict=ficlSystemGetDictionary(vm->callback.system);
//首先我們要改寫 gtk_signal_connect 的處理方式
	ficlDictionarySetPrimitive(dict,"gtk_signal_connect",ficl_gtk_signal_connect,FICL_WORD_DEFAULT);
}

因為在runtime 時事件和回呼函式的綁定可能頻繁地發生,我們不希望每次呼叫 gtk_signal_connect 都在forth dictionary 配置空間。因此,需要動態配置一個結構,來記錄調用回呼函式所需的資料,這個結構所佔據的記憶體會在signal 和 callback 的聯繫解除時被釋放

typedef struct callback_context {
	ficlVm *vm;          // 虛擬機的指標
	ficlWord *forthword; // 指向forth callback
	void *widget;        // 發出這個signal 的元件
	void *userdata;      // 綁定signal/callback 時,用戶的額外參數
	int n_param;         // 參數個數
	int return_type ;    // 返回的型別  (只有兩種,有返回值或無返回值 )
};

這是gtk_signal_conenct 的本體,主要的工作填寫callback_context 結構,將之當作 user_data 傳入 GTK,統一的入口是signal_callback,每當GTK執行回呼,都會先跳進這裡。

// gtk_signal_connect ( widget signal_name forth_word user-data -- result )
void ficl_gtk_signal_connect(ficlVm *vm)
{
	int r,signal_id;       
	GSignalQuery signal_query;    // 這裡會放 signal 的細節
	struct callback_context *cb;
	// 取出 forth 參數
 
	gpointer func_data=ficlStackPopPointer(vm->dataStack);
	void *forthword=ficlStackPopPointer(vm->dataStack);
	char *name=ficlStackPopPointer(vm->dataStack);
	GtkObject *object=(GtkObject *)ficlStackPopPointer(vm->dataStack);

	// 為結構 callback_context 配置記憶體
	cb=g_slice_new(struct callback_context);
	
	//填寫 callback_context
	cb->vm=vm; 
	cb->forthword=forthword; 
	cb->userdata=func_data;   // 這是從 forth 提供的,因為我們呼叫 g_signal_connect_data 時要佔用,所以要在這裡保存
	cb->widget=object;

	// 取得 signal id
	signal_id = g_signal_lookup(name, G_OBJECT_TYPE(object));
	// 查詢 signal 的細節
	g_signal_query(signal_id,&signal_query);
	// 此signal  callback 的參數個數
	cb->n_param=signal_query.n_params ;
	// 此signal 的返回型別
	cb->return_type=signal_query.return_type ;

	//這裡要用 G_CONNECT_SWAPPED,這樣GTK調用callback 時會用相反的順序傳入,讓signal_callback比較好處理
	r=g_signal_connect_data(object,name,(GCallback)signal_callback,cb,_free_callback_context,G_CONNECT_SWAPPED);

	ficlStackPushInteger(vm->dataStack,r);
}

GTK 會在聯繫解除時自動呼叫這個函式
void _free_callback_context(gpointer data, GClosure *closure)
{
    g_slice_free(struct callback_context, (struct callback_context*) data);
}

所有的callback 都會先進入這裡,再跳進Forth 函式。注意這是一個未知參數個數的函式,就像 printf 一樣。我們使用C 的 va_arg函式來取得實際傳入的參數。

int signal_callback (gpointer context,...)
{
	va_list ap;
	struct callback_context *cb=context;   
	//從callback_context 中 取回執行所需的資料
	int i;
	void * param;

	//每個callback 的第一個參數總是 widget
	ficlStackPushPointer(cb->>vm-->>dataStack,cb->widget);

	//將參數依序移到 Forth 
	va_start(ap, context);
	for (i=0; i<cb->n_param; i++) {
		param=va_arg(ap,void*);                        //取得本函式傳入的參數
		ficlStackPushPointer(vm->dataStack,param);  //推入  forth 堆疊
	}
	va_end(ap);

	//最後一個參數就是用戶額外資料
	ficlStackPushPointer(cb->>vm->dataStack,cb->userdata);
	
	//呼叫 Forth 函式
	ficlVmExecuteXT(vm, cb->forthword);

	//看看callback 有沒有返回值。
	if (cb->return_type!=G_TYPE_NONE) {
		return ficlStackPopInteger(cb->>vm->dataStack); 
	} else return 0;
}


至此,從Forth 到 GTK ,以及從 GTK 到 Forth 的雙向橋樑已建造完成。我們就可以用 Forth 來開發基於 GTK 的應用,享受GTK這個開源社群傑出的成果。

3. ficl_gtk_builder.c

雖然本質上所有的視窗元件都是使用API 來建生,但從軟體開發的角度而言,將使用者介面從程式碼分離出來,是主流的做法。 在 GTK 中有個 GtkBuilder的機制,其前身是 libglade ,其原理是將視覺元件的擺放位置、顯示訊息、功能表以及事件的綁定,存成XML 檔案,在執行期間再載入。這樣做的好處很多,首先,程式功能的開發和畫面的設計可以獨立進行而不相互干擾。其次,由於 XML 和語言無關,因此在系統的開發之初,可以使用腳本語言來做快速原型開發,等規格和介面穩定下來,再用C或C++來撰寫。第三,XML 本身是跨平台的文字檔,所以畫面自然可以跨平台,更新畫面也非常容易。通常系統的維護,畫面的調整佔了70%以上,用使用XML描述 UI,修改畫面不必動到程式,也就不必重新除錯和測試,長期下來,可以節省可觀的維護成本。

這裡需要用到三個Forth 的字,分別是 gtk_builder_new , g_object_unref 和gtk_builder_get_object,以下是使用範例。後二者直接用 gtk: 製造。

s" calc.ui"            gtk_builder_new         VALUE  Builder     \ 載入calc.ui 檔案
Builder s" CalcWindow" gtk_builder_get_object  TO     CalcWindow  \ 取得 名為 CalcWindow 的元件
Builder s" CalcEntry"  gtk_builder_get_object  TO     CalcEntry   \ 取得 名為 CalcEntry 的元件
Builder                g_object_unref                             \ 釋放 builder, 不過CalcWindow 和 CalcEntry 還是有效的

calc.ui 是一個 XML 檔,它是由 Glade-3 視覺化介面製作工具所產生(類似於VB, Delphi),再經由 gtk-builder-convert 而得,不建議直接用手動編修。

gtk_builder_new 載入介面 UI XML檔,並且將 XML所指定的 callbacks 和 forth 的字聯繫起來。也就是說,這個函式會自動地替每個在 XML 定義的元件,執行 gtk_signal_connect 的動作,程式碼可以大幅簡短,同時又增加了彈性。舉例來說,只要先寫好某個動作 FileSave ,UI 中可以使用功能表來呼叫 FileSave,或是用一個接鈕,都不必動到程式。

 void ficl_gtk_builder_new(ficlVm *vm)
 {
	 GtkBuilder *builder;
	 char *str=ficlStackPopPointer(vm->dataStack);
	 int r;
	 
	 // 創建 GtkBuilder 元件
	 builder=gtk_builder_new();
	 
	 //載入 XML UI 定義檔
	 r=gtk_builder_add_from_file(builder,str,NULL);
	 if (!r) { // 無法載入則顯示錯誤訊息
		 g_warning ("error loading GtkBuilder ui file %s", str);
		 g_object_unref(G_OBJECT(builder));
		 ficlStackPushPointer(vm->dataStack,0);
		 return;
	 }
	 
	 // 將 UI 中定義的callback 和 forth 聯繫起來
	 // 對每個XML定義的callback,會調用 gtk_builder_connect_signal_forth 一次。
	 // 同時傳入 vm ,以便 gtk_builder_connect_signal_forth 要用到
     gtk_builder_connect_signals_full (builder,gtk_builder_connect_signal_forth,vm);

	 //留下 builder 的指標
	 ficlStackPushPointer(vm->dataStack,builder);
 }

先到forth 字典查詢是否存在於指定的callback,如果找到,就呼叫 gtk_signal_connect。

static void gtk_builder_connect_signal_forth (GtkBuilder    *builder,
   GObject       *object,    const gchar   *signal_name,
   const gchar   *callback_name,   GObject       *connect_object,
   GConnectFlags  flags,    gpointer       user_data)
{
	ficlVm *vm= (ficlVm*)user_data;
	ficlWord *forthword = NULL;
	ficlString str;
  
	//準備 forth 的字串
	str.text=(gchar*)callback_name;
	str.length=strlen(callback_name);

	// 到字典查查看
	forthword = ficlDictionaryLookup(ficlVmGetPrivateDictionary(vm), str);
	if (!forthword) forthword = ficlDictionaryLookup(ficlVmGetDictionary(vm), str);

	// 查無此字
	if (!forthword)   {
      g_warning ("Could not find signal callback '%s'", callback_name);
      return;
    }

	//準備呼叫 forth 字來達成聯繫的動作
	ficlStackPushPointer(vm->dataStack,object);
	ficlStackPushPointer(vm->dataStack,signal_name);
	ficlStackPushPointer(vm->dataStack,forthword);
	ficlStackPushPointer(vm->dataStack,user_data);
	// 重用我們之前定義好的 gtk_signal_connect 
	ficl_gtk_signal_connect(vm);
	//返回值就不要了
	ficlStackPopPointer(vm->dataStack);	
}

五、追縱指引

對於別人寫的程式,看十遍不如追縱一遍,因為看只是靜態,程式對你而言還是死的,而追縱才有辦法觀測到活生生的程式。打個比方說,想要了解生物體,最好的方式並不是解剖,生物一死,氣脈停止,還能觀察出什麼子丑寅卯來?當然是要在活著的情況下,才有辦法觀察到情志、臟腑、飲食之間的影響。同理,程式也是一樣,觀察程式運行的最佳工具,就是單步除錯器(Step Debugger),不要小看這個,一個人的編寫程式的能力,很大程度取決於對除錯器的掌握程度。

因此,大家如果對以上講解的實作,那怕還有一絲一毫的疑惑,都應該祭出除錯器,一步步執行,觀察變數、記憶體和暫存器的變化,再難的程式,只要多追幾步,必定會豁然開朗。只要將這個技能練熟,就算是數十萬行的 Linux Kernel (用user mode linux),也可以用同樣的技巧來庖丁解牛,所有的奧秘將無所遁形。

建議大家先在 ficl_make_gtk_func 設中斷點,觀察當執行 gtk: 時,新的介面如何被建置。然後在ficlCEntryPoint設中斷點,觀察Forth 的參數如何轉換為C 的參數,並入gtk 函式的過程。接下來觀看 gtk_signal_connect ,掌握signal 和callback聯繫的過程。最後在 sigal_callback 設中斷點,回到GUI程式按下按鈕,就會停在 sigal_callback ,仔細觀察從 GTK 進入到 Forth 的流程。

只要掌握了這四處重點,就大致理解了整個系統的運作。為驗證自己的理解是否澈底,可以試著將 FICL 換為其他的 Forth ,或是將 GTK 換為 wxWidget, MicroWindow, FLTK等 tool kits 。只要內力具足,學習各種花招都輕而易舉,而且再平淡無奇的招式都可以發揮威力。

六、全局觀

1)GTK 和 Forth 雖然都是用 C 寫的,但有不同參數傳遞方式、記憶體管理方式,需使用language binding的機制將兩者嫁接起來。

2)Binding 分為兩部份,首先是讓 Forth 可以呼叫 GTK function ,這是透過 gtk: 來實現的。

3)gtk: 可以想像成一個只有兩個field和一個 method 的class,每次執行 gtk: 都會產生一個instance,以記錄某個gtk函式的進入點和參數個數。執行這個forth word ,就是執行這個instance 的method,也就是調用 GTK 的服務。

4)GUI 發生了任何動作稱為 signal ,對signal做出的反應由 callback 來完成。視覺元件(按鈕、功能表之類)、signal 和 callback 之間的聯繫,可以用程式碼,或是使用 glade 用圖形化的方式來指定。

5)當某個事件發生時,比方說某個按鈕被按下,此時,GTK 會產生一個 clicked 事件,並搜尋看看有沒有負責對這個事件回應的callback。如果沒有,就當什麼事都沒發生過。如果有,就呼叫這個 callback 。

6)為了可以用forth 來撰寫callback ,首先在聯繫signal 和callback 之際,配置一小塊記憶體callback_context來放虛擬機的handle等其他執行callback 所需的資料,同時我們規定所有的callback都先進入signal_callback。

7)當事件發生時,首先會進入 signal_callback ,在這裡我們依據callback_context,來處理參數的搬動,並進入 Forth word 。

8)從Forth 可以呼叫 GTK,也讓Forth 可以準備callback 供 GTK 呼叫,如此就完成了 Forth 和 GTK 兩個世界的溝通,使用者只要有能力查閱GTK 文件,就可以像其他語言如python/perl/lua 一樣,快速地開發GTK應用程式。

謝謝大家耐心看完本文,歡迎來信切磋和賜教。我的信箱是yapcheahshen@gmail.com