L'Isola di Niente
L'Isola di Niente » C and GLib Tips » Vala (C Generator)

Vala (C Generator)

Projects/Vala - GNOME Wiki!

Vala は C# っぽい文法で C 言語ソースコードを生成し gcc でビルドする言語です。
つまりジェネレーターですが意識せずに使えるようになっています。

コードは GLib の関数群に変換されるので Linux と相性もよく GTK+ の利用も簡単です。
Windows で C# を少しかじった人にはとても馴染みやすい言語だと思います。

最終的に gcc を用いてビルドするので当然ネイティブアプリとして動作します。
mono のように特殊なランタイムを必要としませんし圧倒的に速いです。

文字列変数が利用できスライス等も可能、ただし C# の Unicode とは違い C と同じ ASCII です。
C 言語に変換するけど C++ の string みたいに使えるという感じになっています。

new で作成したオブジェクトは C 言語変換時に破棄コードを自動生成する。
ガベージコレクションではありませんが破棄コードを書くのは不要です。

文法が簡単で短く、かつアプリは高速という Linux 最強の言語かもしれません。

チュートリアル
Projects/Vala/Tutorial - GNOME Wiki!

C# との細かい違いは以下
Projects/Vala/ValaForCSharpProgrammers - GNOME Wiki!

この言語は公式ドキュメントもありサンプルコードも沢山ある。
valadoc.org

環境構築

gcc でビルドするので当然 gcc は必須。
valac がコンパイラ、後は GLib, GTK+ のヘッダが必要

gtk3-devel (Fedora) | libgtk-3-dev (Ubuntu)

注意、古いバージョンでビルドすると以下の警告が出る場合があります。
`g_type_init` は廃止されました
ビルドは可能なので無視してください、気になるならパッケージを削除後に最新ソースを自力ビルド。

Hello World

以下のコードを書いて hello.vala という名前で保存します。
public class Test {
 
    public static int main (string[] args) {
        var sb = new StringBuilder();
        sb.append("Hello");
        sb.append_c (' ');
        sb.append("World\n");
        print(sb.str);
        return 0;
    }
}
何か見覚えがある main 関数、なんと StringBuilder が使える。
C#, Java の経験があるならすぐ理解できますよね。
using GLib;
GLib.StringBuilder();
が必要な感じがしますが暗黙的に行ってくれるようです。
Gtk を使う場合でも完全名を全部書くなら Gtk というプリフィクスは不要だったりします。

C 言語に変換するので main 関数の戻り値は int にします。
print() だけで文字列を表示できる、これは GLib の g_print に変換される。
実はエントリポイントを class に入れなくても動く、入れるよう推奨していますが。

そして端末から
$ vala hello.vala
で標準出力に表示されます、この場合コンパイルはされずスクリプト言語のように使えます。
$ valac hello.vala
と valac コマンドなら hello という実行ファイルができあがります。
$ valac -C hello.vala
-C オプションを使えば生成された C 言語のソースコードを吐き出します。
どのように変換されたかを確認して C の勉強もできるので一石二鳥です。

サンプルコード

using Gtk;

/**
 * Singnal Connect Test
 * valac --pkg gtk+-3.0 hoge.vala
 */
public class TestWindow : Window {

    public TestWindow () {
        // valadoc Like
        var button1 = new Button.with_label("Button 1");
        button1.clicked.connect(() => {
            button1.label = "Clicked!";
        });
        // C Like
        var button2 = new Button.with_label("Button 2");
        button2.clicked.connect(on_clicked);
        var vbox = new Box(Orientation.VERTICAL, 0);
        vbox.pack_start(button1, false, false, 0);
        vbox.pack_start(button2, false, false, 0);
        // this
        set_title("Test");
        destroy.connect (Gtk.main_quit);
        add ( vbox );
        show_all ();
    }
    private void on_clicked(Button button) {
        button.set_label("Clicked!");
    }
    public static int main (string[] args) {
        Gtk.init (ref args);
        new TestWindow();
        Gtk.main();
        return 0;
    }
}
signal ハンドラは valadoc では上記のように無名関数で解説されています。
暗黙的に呼び出し元の Widget 名が引数で渡されるので括弧中は通常は空でいいです。
C 言語風にもコネクト可能ですが残念ながら user_data は使えないようです。
クラス変数を利用しろということみたい。

GtkDrawingArea の cair_t みたいなパラメータは下記のように、型名は不要。
using Gtk;

/**
 * DrawingArea Test
 * valac --pkg gtk+-3.0 hoge.vala
 */
public class DrawTest : Window {
	public DrawTest () {
		// The drawing area:
		var da = new DrawingArea ();
		da.draw.connect ((cr) => {
			int height = da.get_allocated_height ();
			int width = da.get_allocated_width ();
			// Draw an arc:
			double xc = width / 2.0;
			double yc = height / 2.0;
			double radius = int.min (width, height) / 2.0;
			cr.arc (xc, yc, radius, 0.0, 2*Math.PI);
			//Style
			weak StyleContext sc = da.get_style_context ();
			Gdk.RGBA color = sc.get_color (StateFlags.PRELIGHT);
			Gdk.cairo_set_source_rgba (cr, color);

			cr.fill ();
			return true;
		});
		add (da);
		destroy.connect (Gtk.main_quit);
		show_all();
	}

	public static int main (string[] args) {
		Gtk.init (ref args);
		new DrawTest ();
		Gtk.main ();
		return 0;
	}
}
weak という謎のキーワードについては下記で。
Projects/Vala/ReferenceHandling - GNOME Wiki!

using Gtk;

/**
 * Single Instance Window
 * valac --pkg gtk+-3.0 hoge.vala
 */
public class TextReader : Window {

    private Notebook _note;

    public TextReader () {
        // Property Set
        Object ( title: "Hoge", default_width: 500, default_height: 400 );
        _note = new Notebook ();
        this.add ( _note );
        this.show_all ();
    }
    public void create_tab ( File? file = null ) {
        if ( file == null ) {
            new_contents ( "New Document", "" );
        } else {
            try {
                var fstream = file.read();
                var dstream = new DataInputStream(fstream);
                var sb = new StringBuilder();
                string line;
                while ( (line = dstream.read_line_utf8(null)) != null ) {
                    sb.append( line );
                    sb.append_c( '\n' );
                }
                new_contents ( file.get_basename(), sb.str );
            } catch ( IOError e ) {
                 messagebox ( "IOError", e.message );
                 return;
            } catch ( Error e ) {
                messagebox ( "Error", e.message );
                return;
            } 
        }
    }
    private void new_contents ( string title, string text ) {
        var view = new TextView ();
        view.show ();
        var sw = new ScrolledWindow ( null, null );
        sw.add ( view );
        sw.show ();
        var label = new Label ( title );
        label.show ();
        if ( text != "" ) {
            var buf = view.get_buffer ();
            buf.set_text ( text );
        }
        var n = _note.append_page(sw, label);
        _note.set_current_page (n);
    }
    private void messagebox ( string title, string s ) {
        var dlg = new MessageDialog( this, DialogFlags.MODAL,
                    MessageType.WARNING, ButtonsType.OK, s );
        dlg.set_title ( title ) ;
        dlg.run ();
        dlg.destroy ();
    }
}

public class App : Gtk.Application {

    private TextReader _win;

    public App () {
        Object (application_id:"apps.test.textreader", flags:ApplicationFlags.HANDLES_OPEN );
    }
    protected override void startup () {
        base.startup ();
        _win = new TextReader ();
        add_window( _win );
    }
    protected override void activate () {
        _win.create_tab ();
        _win.present();
    }
    protected override void open ( File[] files, string hint ) {
        foreach ( var file in files ) {
            _win.create_tab ( file );
        }
        _win.present();
    }
    public static int main ( string[] args ) {
        Gtk.init ( ref args );
        var app = new App ();
        return app.run ( args );
    }
}
色々詰め込んでみた。

Object という奇妙な関数でプロパティをまとめてセットできる。
Python の __init__ と同様。

File? file = null という不思議なパラメータで File もしくは null という意味になる。
C# のような厳密な型チェックを行うようなので苦肉の回避策のように思えますけど。

GError をパラメータに含む関数を利用する場合はパラメータに入れるのではなく try 文にする。
面倒でも try 文にしないとビルド時に警告が出るので注意。

GtkApplication でシグナルを利用する場合は上記のようにオーバーライドする。
startup シグナルを利用する場合 base.startup が必要。

後は C# を少し勉強した御仁ならなんとなく理解できると思う。
Copyright(C) sasakima-nao All rights reserved 2002 --- 2017.