【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

gtkmmからC#に移植


g:auautech:id:daiki41ti:20091026で公開されているid:daiki41tiさんのgtkmmアプリケーションをFreeBSDで動かして、C#に移植してみました。Monoのバグらしきものに遭遇しました。右のスクリーンショット(クリックで拡大します)内の右下に3つ並べて比較しています。左からオリジナル(gtkmm)、C#移植版(Mono)、C#移植版(Wine)です。真ん中は線の太さが表現されていません。

FreeBSDgtkmm

FreeBSDportsではGTK+の1.2系(gtk12)と2.x系(gtk20)が別れているため、gtkmmもそうだろうと考えてgtkmm20をインストールしたところ、バージョンが古くて今回のアプリがうまくコンパイルできませんでした。よく見るとgtkmmでは更に細かくバージョンが別れていて、gtkmm24をインストールすればうまくいきました。

OS依存コードがあるわけでもないため、特に何の修正も必要ありません。

C#に移植

コードを見ると、ほとんどそのままC#に移植できそうだったので、実際にやってみました。

// name:       curve.cs
// compile:    $ gmcs curve.cs /r:System.Drawing /r:System.Windows.Forms

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

class MyArea : Form
{
    private float x0=0.1f, y0=0.5f;  // start point
    private float x1=0.4f, y1=0.9f;  // control point #1
    private float x2=0.6f, y2=0.1f;  // control point #2
    private float x3=0.9f, y3=0.5f;  // end point
    private int state = -1;
    private float variation;
    
    public MyArea()
    {
        BackColor = Color.Black;
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.DoubleBuffer, true);
        SetStyle(ControlStyles.UserPaint, true);
        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if(-1 < state) {
            x1 += variation; y1 += variation; x2 -= variation; y2 -= variation;

            if(7 < state) {
                state = -1;
            } else {
                state++;
                variation += 0.001f;
            }

            Invalidate();
        }

        draw(e.Graphics);
    }
    
    protected override void OnMouseDown(MouseEventArgs e)
    {
        state = 0;
        variation = 0.005f;
        Invalidate();
    }
    
    private void draw(Graphics g)
    {
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.ScaleTransform(ClientSize.Width, ClientSize.Height);
        
        draw_curve(g);
        draw_shaft(g);
    }
    
    private void draw_curve(Graphics g)
    {
        using (var pen = new Pen(Color.FromArgb(230, 255, 255, 255), 0.05f))
        {
            g.DrawBezier(pen, x0, y0, x1, y1, x2, y2, x3, y3);
        }
    }
    
    private void draw_shaft(Graphics g)
    {
        using (var pen = new Pen(Color.FromArgb(154, 255, 51, 51), 0.05f))
        {
            g.DrawLine(pen, x0, y0, x1, y1);
            g.DrawLine(pen, x2, y2, x3, y3);
        }
    }
    
    static void Main()
    {
        var win = new MyArea();
        win.ClientSize = new Size(200, 200);
        win.Text = "DrawingArea";
        Application.Run(win);
    }
}

線の太さ

C#移植版は、Windows上ではMicrosoft.NET Framework 2.0でもMonoでも正常に動作しますが、FreeBSD上のMonoでは線の太さが表現されません。System.DrawingはGDI+(注:GTK+とは無関係)で処理されていて、Windows上ではMonoでもGDI+を利用しているため、結果に差が出ないようです。UNIX系OSにはGDI+が存在しないため、libgdiplusというライブラリを作成して処理しています。そのlibgdiplusで未実装もしくはサポートされていない機能にたまたま当ってしまったようです。

これ以上の調査はお手上げなので、id:atsushienoさんにお願いしてバグレポートを上げていただきました。

Wineで.NET Framework

最近のWineではMicrosoft製の.NET Frameworkが動きます。今回のC#移植版も問題なく動きました。これがスクリーンショットに並べた3つのうちの右側の「C#移植版(Wine)」です。

WineでWindows版Monoを動かすことも可能です。WineでもMonoへの配慮がされていて、.NET FrameworkがインストールされていなくてもMonoがインストールされている場合は、自動的にMonoを呼び出してexeを実行するようになっています。前述のようにGDI+は共通のものを使っているため、今回のC#移植版もWine上のWindows版Monoでは正常に動作します。

ちなみに先ほどのバグレポートの最後に添付されている画像では、以下の3つを並べています。すべてFreeBSD上で同時に動かしています。

  1. Mono (FreeBSDネイティブ)
  2. Mono (Wine)
  3. .NET Framework 2.0 (Wine)

かなりカオスな状態ですが、こうなって来ると個人的にはFreeBSD上でもMonoより.NETを使う方が多くなるかもしれません。