Paepoi

Paepoi » C and GLib Tips » Vala (C Generator)

Vala (C Generator)

# 最終更新日 2021.02.27

※ Fedora 33 時点での環境に合わせて書き換え、Ubuntu 関連の削除をしました。

Projects/Vala - GNOME Wiki!

Vala は C# っぽい文法で C 言語ソースコードを生成し gcc でビルドする言語です。
つまりジェネレーターですが意識せずに使えるようになっています。
mono のように特殊なランタイムを必要としませんし圧倒的に速いです。

コードは GLib の関数群に変換されるので Linux と相性もよく GTK+ の利用も簡単です。
最終的に gcc を用いてビルドするので当然ネイティブアプリとして動作します。

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

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

interface が定義できる等 mono とは違いガンガン追加機能が行われています。
文法が簡単で短く、かつアプリは高速という Linux 最強の言語かもしれません。
Cheese や Geary 等のメジャーなアプリも Vala で作られています。

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

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

この言語は公式ドキュメントもありサンプルコードも沢山ある。
valadoc.org
環境構築
gcc でビルドするので当然 gcc は必須ですが現行 Fedora には最初から入っています。
それと GLib や GTK+ のヘッダが必要、C 言語でビルド可能な環境を作る必要がある。
vala パッケージに valac コンパイラ等が揃っています。
# 具体的には以下で最小限の環境が作れる
sudo dnf install vala gtk3-devel
注意、古いバージョンでビルドすると以下の警告が出る場合があります。
`g_type_init` は廃止されました
ビルドは可能なので無視してください、現行 Fedora なら出ません。

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

C 言語に変換するので main 関数の戻り値は int にします。
実はエントリポイントを class に入れなくても動く、入れるよう推奨していますが。

そして端末から
vala hello.vala
で標準出力に表示されます、この場合コンパイルはされずスクリプト言語のように使えます。
valac hello.vala
valac コマンドなら hello という実行ファイルができあがります。
gcc コマンドとは違いデフォルト名は .vala という拡張子を外したファイル名になるので注意。
もちろん最終的に gcc なので -o オプションで好きなファイル名に変更することも可能。
valac -o suzuki.katana hello.vala
-C オプションを使えば生成された C 言語のソースコードを吐き出します。
valac -C hello.vala
どのように変換されたかを確認して C の勉強もできるので一石二鳥です。

サンプルコード
using Gtk;

/**
 * Singnal Connect Test
 * valac --pkg gtk+-3.0 hoge.vala
 */
public class TestWindow : Window {
 
    public TestWindow () {
        // valadoc では無名関数でシグナル処理をやっている
        // C# と同等なスコープ範囲の変数しか使わないならパラメータは不要
        var button1 = new Button.with_label("Button 1");
        button1.clicked.connect(() => {
            button1.label = "スズキの";
        });
        // パラメータを使う場合は変数名だけ書けばいい
        // てか (Button katana) と書くとコンパイルエラー
        var button2 = new Button.with_label("Button 2");
        button2.clicked.connect((katana) => {
            katana.label = "バイクは";
        });
        // C 風の処理も当然可能、
        // 残念なことに user_data は使えない
        var button3 = new Button.with_label("Button 3");
        button3.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);
        vbox.pack_start(button3, false, false, 0);
        // 以下 this を付けるかどうかはお好みで
        // プロパティならイコールで、関数なら引数で
        // this.title = "プロパティ";
        set_title("関数");
        destroy.connect (Gtk.main_quit);
        add ( vbox );
        show_all ();
    }
    private void on_clicked(Button button) {
        button.set_label("カッコイイ");
    }
    public static int main (string[] args) {
        Gtk.init (ref args);
        new TestWindow();
        Gtk.main();
        return 0;
    }
}
valadoc が少し判り辛いので捕捉。
signal ハンドラは C と同じようにも書くこともできます。
匿名関数の場合は C# と基本同じですが C# のように型を指定できません。
クラスメンバに this を付けるかどうかは自由。

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);
			// StyleContext
			unowned StyleContext sc = da.get_style_context ();
			//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;
	}
}
unowned weak という謎のキーワードについては下記で。
Projects/Vala/ReferenceHandling - GNOME Wiki!
翻訳している人もいる。
Valaのメモリー管理について - M12i.
ようするに破棄されてはいけないオブジェクト、参照のみの変数に指定する。

下記は引数で渡されたファイルを読み込むテキストリーダーのサンプル。
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 という奇妙な関数でプロパティをまとめてセットできる。
PyGObject の __init__ や Gjs の super._init と同様。
ただし new 時にプロパティのセットはできない、そりゃ C 言語に変換するから無理だよね。

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

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

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

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