Vala 程式語言入門
介紹
如果你知道 "C with Classes" 甚至曾經用過,那麼對於 Vala 的運作方式,想必也會感到熟悉。我認為「 Vala 是 "C with GObject" 的編譯器」 (Vala is a C with GObject compiler) 是非常貼切的介紹描述。
Vala is a new programming language that allows modern programming techniques to be used to write applications that run on the GNOME runtime libraries, particularly GLib and GObject.
Vala Tutorial
介紹 Vala 之前,我先說個關於 C++ 的故事。 Bjarne Stroustrup 最初設計了一套型別機制,具有類別與繼承能力,同時設計了一個搭配的程式語法。一開始,Bjarne Stroustrup 先寫了一個前置編譯器,稱為 C with Classes 。 C with Classes 先將新語法程式碼編譯成 C 程式碼,再交給 C 編譯器編譯為機器碼。隨著這套型別機制與程式語法的普及,人們賦予它一個更易記憶的名字,那就是 C++。
今天的主角 Vala ,也跟 C++ 具有類似的背景。當初 GNOME 專案使用 C 語言發展了一套新的型別機制,稱為 GObject。它具有許多現代程式語言型別系統的特徵,如類別、繼承、泛型、執行時期型別識別等。但是它並沒有同時出現一個配合的新程式語法,而是讓程序員用 C 語言一磚一瓦地開始堆砌。曾經用 C 語言實作自己的型別系統的程序員,應該不難想像這個基礎工作有多麻煩。建立型別系統結構、定義新的型別與類別、註冊自定類別與方法等等,都要使用者在程式碼中寫出。這使得 GObject 非常難以入門,轉換門檻甚至高於純 C 語言程序員學習 C++ 。
所幸 Vala 的出現改變了這一切。 Vala 就是搭配 GObject 系統的新程式語言,它大量地借用了 C# 的語法來表示 GObject 的內容,它還實作了記憶體管理機制。 Vala 編譯器 (valac) 的運作方式則非常類似 C with Classes: 先將 Vala 程式碼編譯成 C 語言碼,再交給 C 編譯器編譯為機器碼。在 Vala 編譯器眼中,Vala 程式碼文件與 C 程式碼文件的地位相同。透過適當地宣告,我們可以直接在 Vala 程式碼中調用 C 程式碼定義的函數,也可以在 C 程式碼中調用 Vala 定義的方法。有些案例顯示,程序員會用 Vala 語言撰寫程式,由 valac 產生 C 程式碼文件後,將產生的 C 程式碼文件與其他人撰寫的 C 程式碼文件一併提交到版本控制庫(VCS repository)與建置系統(nightly builder server),讓建置系統用 gcc 編譯出產品。
Hello vala
Debain/Ubuntu 系統提供了 valac 套件。安裝之後即可使用 Vala 編譯器。接著完成我們的 Vala 版 hello world 吧。
基本上,你只需要使用 valac 就可以直接產生執行檔。如果你想知道 valac 產生的 C 程式碼內容,請加上 -C
參數,它就只做 Vala 程式碼編譯成 C 程式碼的動作。
$ valac hello.vala
$ ./hello
Hello world
Key: b; Value: 2
Key: a; Value: 1
Key: c; Value: 10
10
1
2
$ valac -C hello.vala # 輸出結果為 hello.c
$ cat hello.c
類別與方法
我們來看看 Vala 如何借用 C# 語法表示 GObject 的型別系統。
下列程式碼定義了兩個類別,其中 BClass 繼承 AClass。內容使用了 property 語法以及特殊方法的語法支援,例如定義了 T2 get(T1 k)
方法,就允許我們以 [ k ]
的索引語法取值。相當於 C# 的 indexer method 或是 C++ 的 operator[]
。
上述內容看來平凡無奇。接著我們用 valac 產生 C 語言程式碼文件,讓我們看看如果直接用 C 語言配合 GObject 要堆砌多少程式碼才能達成上述內容完成的工作。答案是 348 行 C 程式碼,其中約 300 行都是用於建立 GObject 型別結構與註冊類別表、方法表。
當我們產生 C 程式碼後,可以直接使用 gcc 編譯出執行檔。因為我們使用了 GObject ,所以還要告知 gcc 去哪找尋 GObject 所需的 header files 與 lib 。在大多數的主流 Linux 散佈版本中,我們都可以執行 pkg-config --cflags --libs gobject-2.0
得到 gcc 所需的額外參數 (pkg-config 是 GNOME 開發套件的一部份)。
$ valac -C class.vala
$ cat class.c
$ gcc `pkg-config --cflags --libs gobject-2.0` -o class class.c
$ ./class
100, 200
100, 200
{a: 100; b: 200}
如果你的 shell 不支援指令輸出替換語法,你需要先以 pkg-config 取得參數內容,再手動添加到 gcc 的參數中。
$ pkg-config --cflags --libs gobject-2.0
-pthread -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include \
-pthread -lgobject-2.0 -lgthread-2.0 -lrt -lglib-2.0
# 複製那一段參數到 gcc 參數...
$ gcc -pthread -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include \
-pthread -lgobject-2.0 -lgthread-2.0 -lrt -lglib-2.0 \
-o class class.c
與 C 程式碼合體
在介紹中提到 Vala 程式碼文件與 C 程式碼文件的地位相同。透過適當地宣告,我們可以直接在 Vala 程式碼中調用 C 程式碼定義的函數
。此節將舉一例說明如何實作。
我先寫一份 C 程式碼文件,它實作了一個 md5sum() 的函數,此函數將呼叫 openssl 函數庫(在Debain/Ubuntu中,這是預裝項目,連接器所需參數為 -lcrypto
) 提供的 MD5() 產生字串的 Digest 內容,並轉為以16進位代碼顯示的字串。
再寫一份調用 md5sum() 函數的 Vala 程式碼。
CCode 是 Vala 專屬的 attribute (C#/Vala 的 attribute 作用與 Java 的 annotation 類似,都可以調整某些編譯行為),主要用於調整 Vala 產生的 C 程式碼內容。在此,我用 extern 宣告 md5sum() 是使用外部程式語言定義的函數,並用 CCode 的 cname 參數,告知 valac 此函數在外部語言中的符號名稱叫 md5sum 。
最後,我將 test-md5.vala 與 md5sum.c 一併交給 valac 編譯。並用 -X -lcrypto
傳遞額外要給 gcc 的連接器參數。
$ valac -X -lcrypt test-md5.vala md5sum.c
$ ./test-md5
d = [hello 石頭成], len = [15]
1[19d63df7a195f5a3a847bd3a54831b97]
2[19d63df7a195f5a3a847bd3a54831b97]
參考文件
由於 Vala 語言主要借用了 C# 的語法,且 Vala 官方文件中也沒有系統化地整理出整套語言規格,因此大家可以從 C# 的語法書中,學習 Vala 的語言內容。本文不教授這些基本內容。
C# 語法書中,我推薦的是 Standard ECMA-334 - C# Language Specification。此文件是 C# 的開放規格,可以免費下載。而且是純語言書籍,不像市面上的C# 書籍總是夾雜一堆 .Net Framework 的內容。那些書籍反而像是在說明 .Net Framework API,而不是 C# 語言。
至於 Vala 專屬的規格,請參考下列文件。
- Vala - GNOME Live! - Vala 官網
- Vala Tutorial - 教學
- Valadoc Online API Documentation
- Vala Reference Manual [Draft] - Vala 參考手冊。仍在草稿階段,內容不齊。
- Hackers' Guide to Vala
- Vala/Bindings! - 擴充 C/Vala library 的指南。
在 Linux 平台,gedit 和 MonoDeveloper IDE 都支援 Vala 。Windows 平台,也有 Vala 使用者提供 binary 安裝套件: Vala on Windows。