Delphi Prismを用いたMono開発

ホワイト ペーパー
Delphi Prism を用いた Mono 開発
Brian Long Consultancy & Training Services Ltd
2009 年 12 月
本社
EMEA 本部
日本
100 California Street, 12th Floor
San Francisco, California 94111
York House
18 York Road
Maidenhead, Berkshire
SL6 1SF, United Kingdom
東京都千代田区飯田橋 4-7-1
ロックビレイビル 8F
Delphi Prism を用いた Mono 開発
Brian Long
目次
はじめに ...................................................................................................................................................................................................- 3 MICROSOFT .NET と ECMA と MONO .................................................................................................................................................- 3 クロスプラットフォーム開発 .................................................................................................................................................................- 5 ライセンスに関する検討事項 .................................................................................................................................................................- 6 ファースト ステップ ...............................................................................................................................................................................- 7 コンパイラ ...............................................................................................................................................................................................- 7 Linux と OS X ...........................................................................................................................................................................................- 7 Mono ........................................................................................................................................................................................................- 8 Mono ソース ............................................................................................................................................................................................- 8 Xcode と Interface Builder ......................................................................................................................................................................- 9 共有フォルダ ...........................................................................................................................................................................................- 9 最初の開発:コンソール アプリケーション ..........................................................................................................................................- 9 プラットフォームとランタイムの特定................................................................................................................................................. - 13 アプリケーションの配置 .......................................................................................................................................................................- 15 現状のままにする ..................................................................................................................................................................................- 15 .exe ファイルの動作を変更する ........................................................................................................................................................... - 16 スクリプト .............................................................................................................................................................................................- 16 バンドル形式実行可能ファイル ............................................................................................................................................................ - 17 Mac OS X のアプリケーション バンドル ............................................................................................................................................. - 18 データ アクセス .....................................................................................................................................................................................- 19 GUI アプリケーションの問題 ............................................................................................................................................................... - 23 GUI ツールキット ..................................................................................................................................................................................- 24 WinForms ...............................................................................................................................................................................................- 24 Mac のアプリケーション アイコン ...................................................................................................................................................... - 27 GTK# ......................................................................................................................................................................................................- 28 GTK# プロジェクト コードの微調整 .................................................................................................................................................... - 30 GTK# の例 ..............................................................................................................................................................................................- 31 GTK# の単純な例 ...................................................................................................................................................................................- 31 ダイアログの例 ......................................................................................................................................................................................- 33 TreeView の例 ........................................................................................................................................................................................- 34 GTK# バンドル形式実行可能ファイル.................................................................................................................................................. - 39 GTK# Mac OS X のアプリケーション バンドル ................................................................................................................................... - 41 -
-1-
Delphi Prism を用いた Mono 開発
Brian Long
Cocoa# ...................................................................................................................................................................................................- 42 Monobjc .................................................................................................................................................................................................- 43 .nib ファイル..........................................................................................................................................................................................- 44 Interface Builder ....................................................................................................................................................................................- 44 単純なテキスト エディタ ......................................................................................................................................................................- 46 Monobjc と Snow Leopard....................................................................................................................................................................- 48 正しい閉じ方 .........................................................................................................................................................................................- 49 コントロール間の相互作用の例 ............................................................................................................................................................ - 52 色選択の例 .............................................................................................................................................................................................- 56 Cocoa の UI 手法 - ウィンドウを振動させてエラーを示す ................................................................................................................. - 59 Cocoa の UI 手法 - スライドイン シートを使った確認 ........................................................................................................................ - 61 まとめ ....................................................................................................................................................................................................- 63 謝辞 ........................................................................................................................................................................................................- 63 -
-2-
Delphi Prism を用いた Mono 開発
Brian Long
はじめに
Delphi Prism は、よく知られた .NET Object Pascal コンパイラで、RemObjects Software が作
成し、Embarcadero Technologies が販売しています。この製品の中核となるコンパイラは
RemObjects Oxygene で、元は RemObjects Chrome と呼ばれていたものです。通常、Delphi
開発者は Delphi Prism を使って、Windows アプリケーション(.NET WinForms ライブラリや、
新しい Windows Presentation Foundation(WPF)ライブラリを使ったもの)、コンソール ア
プリケーション、Windows サービス アプリケーション、ASP.NET Web サーバー アプリケー
ション、Windows Communication Foundation(WCF)サービス ライブラリといった、さまざまな種類の標準的
な .NET アプリケーションを構築します。
このホワイトペーパーでは、従来の .NET 開発は脇に置き、Delphi Prism で Mono プロジェクトを使うことで、これま
での .NET や Delphi のスキルを活かしながら Windows の世界から抜け出す方法を考えてみます。それによりクロスプ
ラットフォーム アプリケーションを構築できるようになるため、守備範囲が広がり、Linux や Mac OS X のユーザーを
獲得することも可能になります。クロスプラットフォーム開発の世界に進出すると、必ずいくらかの落とし穴にはまる
ことにはなるでしょうが、ここではできるだけ多くの一般的な問題を予測し、その問題を回避したり乗り越える方法を
検討します。
このホワイトペーパーに含まれるサンプル コードは、Code Central でも提供する予定です。
MICROSOFT .NET と ECMA と MONO
Microsoft が 2002 年にリリースした .NET 1.0 は、重要な開発対象プラットフォームとなりました。このプラットフォ
ームには、セキュリティや、一貫した例外処理体系、ガベージ コレクション、さまざまな言語のコードを 1 つのマネ
ージ実行環境で組み合わせる機能などが組み込まれていますが、その他に、これまでになく具体的なクロスプラットフ
ォ ー ム 開 発 の 可 能 性 が 期 待 で き ま す 。 実 際 に 、 .NET ア プ リ ケ ー シ ョ ン は 共 通 中 間
言語(Intermediate Language:IL)にコンパイルされ、それをプラットフォームが JIT(Just-In-Time compilation:ジ
ャストインタイム コンパイル)処理によってネイティブの命令に翻訳するため、結果として、潜在的なさまざまなハ
ードウェア アーキテクチャへの扉を開くことになります。
Microsoft の .Net に関する限り、最初に実現されたクロスプラットフォーム サポートでは Windows 98、Me、NT 4.0、
2000、XP で動くことを保証しており、その後、Windows のそれ以降のバージョンでも動くことが保証されています。
このクロスプラットフォームは「さまざまな Windows 実装が同じハードウェア上で動くこと」と定義されていました
-3-
Delphi Prism を用いた Mono 開発
Brian Long
が、実際のところそれではあまり利点はありませんでした。Windows 開発者には当たり前のことだと思われていたか
らです。しかし、すべてが無駄になったわけではありません。
.NET 開発サイクルの初期に、Microsoft は自分たちが目指しているものの可能性を感じ、.NET インフラストラクチャ
の主要部分の仕様を ECMA(European Computer Manufacturer’s Association)に標準として公開しました。共通言語
基盤(Common Language Infrastructure:CLI)と呼ばれるものの最初の標準は、2001 年 12 月にリリースされました。
これが 2003 年 4 月に ISO 標準となります。標準文書の最新版は ECMA の Web サイト(http://www.ecmainternational.org/publications/standards/Ecma-335.htm)で提供されていますので、是非ダウンロードされることをお
勧めします。.NET 開発者が開発を行うプラットフォームの正式な定義が定められているためです。この標準はさまざ
まなセクション(Partition)に分かれていて、仮想マシン(実行エンジン)のアーキテクチャや、メタデータ、基本ク
ラス ライブラリ(base class libraries:BCL)、中間言語を扱っています。
Microsoft の .NET プラットフォームは CLI を実装したものです。Microsoft は .NET の仮想マシン コンポーネントを共
通言語ランタイム(Common Language Runtime:CLR)と呼んでいて、.NET クラス ライブラリ(Framework Class
Library:FCL)は BCL の重要なスーパーセットであるため、.NET は CLI(CLR + BCL)の実装に多数のライブラリが
追加されたものだと見なすことができます。CLI の仕様は、プラットフォーム部分と BCL 部分に分かれた 2 冊の注釈
付きの本として、Addison-Wesley から出版されてもいます。James S. Miller および Susann Ragsdale の『The
Common Language Infrastructure Annotated』(Addison-Wesley, 2004)と、Brad Abrams の『.NET Framework
Standard Library Annotated Reference, Volume 1: Base Class Librray and Extended Numerics Library』(AddisonWesley, 2004)です。
Microsoft は、さらに CLI の実装を進めていて、.NET を実質上のクロスプラットフォーム製品にしようとしています。
モバイル装置および組み込み装置向けの .NET Compact Framework は、2002 年の終わりごろに発表されました。同じ
年のもっと早い時期には、CLI のシェアード ソース実装である SSCLI(コードネームは Rotor)がアカデミック ライセ
ンスでリリースされています。この巨大なソースには商用の .NET プラットフォームと共通する部分が多く含まれてい
ますが、プラットフォーム抽象化レイヤ(Platform Abstraction Layer:PAL)を導入することで Windows や FreeBSD
や Mac OS X に対応しています。
さらに興味深いのは、元々は Ximian 社が、現在は Novell 社が行っている Mono プロジェクト
(http://www.mono-project.com)です。このプロジェクトは .NET 1.0 がリリースされる前か
ら始まっていて、Mono 1.0 は 2004 年にリリースされています。Mono は、CLI を完全に実装
するだけでなく、.NET の標準化されていない部分をできるだけ多くのプラットフォームででき
るだけたくさん実装しようとしています。JIT コンパイラは x86、x86-64、SPARC、ARM、
PowerPC に対応していて、Windows、OS X、およびさまざまな Linux ディストリビューション
用のインストール キットがダウンロード可能になっています。
-4-
Delphi Prism を用いた Mono 開発
Brian Long
.NET と同様の機能が提供されているだけでなく、Mono ではさらに進んで、Linux や OS X に固有の多くの技術を利用
することができます。特定の Unix 関連の呼び出しをサポートするライブラリがあったり、さまざまなグラフィック ツ
ールキットがサポートされています。
Delphi Prism では Mono プラットフォームをサポートしています。ここでは、このサポートを利用して、現在の知識を
あまり無駄にすることなく Linux や OS X の世界に進出するにはどうすればよいかを検討します。
CLI の実装としては他にも DotGNU プロジェクトの Portable.NET などがありますが、このホワイトペーパーでは扱い
ません。
クロスプラットフォーム開発
コードに飛び込む前に、クロスプラットフォーム開発とは何かを考えてみましょう。これが大切なのは、さまざまな解
釈が可能なためです。
標準的な .NET アプリケーションは、32 ビットと 64 ビットの両方の Microsoft Windows マシン上で動くため、元々ク
ロスプラットフォームです。これをクロスアーキテクチャと言うことができます。多くの .NET アプリケーションは、
特に変更しなくても Mono プラットフォーム上で実際に動きます。これをクロス CLI と言うことができます。あるマ
ネージ アプリケーションが複数のオペレーティング システムで動くかもしれません。これをクロス OS と言うことが
できます。そしてもちろん、その組み合わせもあります。その時々の要件を決定し、利用できるツールを使って解決に
向けて努力しなければなりません。このホワイトペーパーの目的は、コードの再利用(クロス CLI)を促進し、クロス
OS コードの実現可能性を考察することです。これは自然にある程度クロスアーキテクチャになります。
もちろん、Windows 以外の任意の OS(ここでは Linux や Mac OS X を想定しています。いずれも Unix ベースの OS
です)で動くコードにしようと考え始めたときから、既存の Windows コードのどの部分が動かなくなるかを意識しな
ければならなくなります。わかりやすい例は、ファイル システムを扱う場合です。パスにおけるディレクトリの間の
区切り文字(Windows ではバックスラッシュ、Unix ではスラッシュ)や、パス リストにおけるパスの間の区切り文字
(Windows ではセミコロン、Unix ではコロン)などが異なります。これらは、プラットフォーム固有のオプションを
隠 し て く れ る
BCL
の 機 能 、 具 体 的 に は
System.IO.Path.DirectorySeparatorChar お よ び
System.IO.Path.PathSeparator を律儀に使用すれば、簡単に避けられる落とし穴です。もう少し興味深いのは、
Unix ベースのファイル システムでは、通常、大文字と小文字を区別するという点です。ファイル操作を頻繁に行う場
合には、これについて少し考える必要があります。
アプリケーションの GUI のことや、対象とする OS のルック アンド フィールの一貫性を維持するかどうかを考えると、
さらにややこしい問題が持ち上がります。UI を 1 つだけ作成してあらゆる場所でそれを動かすことが許されるでしょ
うか。それとも OS ごとに固有の UI を構築することを検討するべきでしょうか。この選択肢については後で検討しま
す(- 24 - ページの「GUI ツールキット」を参照してください)。
-5-
Delphi Prism を用いた Mono 開発
Brian Long
また、プラットフォーム呼び出し(P/Invoke)や COM 相互運用メカニズムを通して呼び出しているネイティブ コード
はどうなるでしょうか。プラットフォーム呼び出しは完全にサポートされているため、適切な関数がエクスポートされ
ているネイティブ ライブラリの適切な実装があれば、おそらく、コードで条件付けをして呼び出す関数を分けること
ができるでしょう。実行時に相互運用層によってプラットフォーム呼び出し関数がロードされるのは、その関数を呼び
出すメソッドが呼び出されてからです。そのため、プラットフォーム固有のプラットフォーム呼び出しをそれぞれ別の
関数に含め、別々の関数が別々のプラットフォーム上のネイティブ関数を呼び出すようにしておけば、この問題を克服
することができます。Mono クラス ライブラリでは、多くのネイティブ関数を呼び出して処理を行っています。Mono
では、Unix 固有のプラットフォーム呼び出しを Mono.Posix.dll アセンブリの Mono.Unix.Native.Syscall クラスにまとめ
ています。ついでに言うと、Mono ツールの monodis にコマンドライン スイッチ --implmap を指定すると、アセンブ
リに含まれるすべてのプラットフォーム呼び出しの一覧を取得することができます。
しかし、COM 相互運用機能は別問題です。というのも、COM はほとんど Windows 固有の技術だからです。そうは言
っ て も 、 Mono で は Windows プ ラ ッ ト フ ォ ー ム上で あ る 程 度 COM 相 互 運 用 機能 を サ ポ ー ト して いま す
(http://mono-project.com/COM_Interop を参照してください)。
Windows と Linux と OS X のどれで動いているかをプログラムで判別できるのか、Mono と .NET のどちらで動いてい
るかがわかるのかと、疑問に思われるかもしれません。アプリケーションの構築を始めればわかりますが、いずれも実
際に可能です。ただし、CLI を基にコードを条件分けするのは良い方法と見なされていません。しかし、それが必要な
場合もあることは認められています。Mono のバグや制限事項をたまたま見つけた場合などはもちろんその例に当たり
ます。Mono は常に開発中であり、いつも .NET に対して遅れを取り戻そうとしていることを忘れないでください。非
常に良くできていますが、それでもあちこちに落とし穴があります。
コードのライブラリを Mono に対応させる作業がどの程度うまく進むかを検討するために、どれだけの .NET ライブラ
リ 呼 び 出 し が Mono で 実 装 さ れ て い る か を 知 り た け れ ば 、 MoMA ( Mono Migration Analyzer ) ツ ー ル
(http://www.mono-project.com/MoMA)を使用してください。このツールは、アセンブリをスキャンして、Mono の
個別のリリースごとに生成された定義ファイルを基にレポートを作成します。
ライセンスに関する検討事項
Mono や、Mono ツール、GTK#、Monobjc といったオープン ソース ライブラリを使ってアプリケーションを構築する
際に検討しなければならない 1 つの重要な要因に、使用されているライセンス モデルと、その結果として生じるプロ
ジェクト全体のライセンスの制約があります。
Mono ツールは、"ウィルス性" の GPL v2(GNU General Public License:http://www.opensource.org/licenses/gpllicense.html)ライセンスで動作します。つまり、自分のプロジェクトに Mono ツールを組み込むと、プロジェクトで
も GPL を採用しなければならない(つまりオープン ソフトウェアにしなければならない)ことになります。幸い、こ
-6-
Delphi Prism を用いた Mono 開発
Brian Long
れは最終的に大きな意味を持ちません。この奇妙な Mono ツールを使用する必要があるのは開発中だけで、できあが
ったソフトウェアに含める必要は通常はないからです。
Mono ラ ン タ イ ム と Monobjc ラ イ ブ ラ リ の ラ イ セ ン ス 許 諾 は LGPL v2 ( GNU Library General Public
License:http://www.gnu.org/copyleft/library.html)に基づいて行われます。つまり、これらを使用したとしても、自
分のプロジェクトで GPL を採用する必要はありません。
Mono ク ラ ス ラ イ ブ ラ リ の ラ イ セ ン ス 許 諾 は MIT X11 ライセン ス(http://www.opensource.org/licenses/mitlicense.html)に基づいて行われます。これは LGPL と似た設計ですが、フレームワークを使用してその中のクラスを
継承したという理由だけでアプリケーションを LGPL にする必要があるかもしれないという技術的な抜け穴が回避され
ています。
まとめると、Mono 関係のライブラリをプロジェクトで使用したとしても、配布時にアプリケーションで LGPL を使用
する必要はなく、使用するライセンスを自分で選択することができます。ただし、著者は法律家ではないため、後々困
った羽目に陥らないよう、この部分についてはご自分で明確に調査してください。
ファースト ステップ
前置きはもう十分です。Windows の向こうへ旅をするために必要なツールがすべて揃っているかを確認しましょう。
コンパイラ
まずコンパイラですが、Visual Studio 内で(または好みによって .NET または Mono のコマンドラインから)動いてい
る Delphi Prism を使用します。2010 年には Mono 開発ツールの MonoDevelop でも動くようになる予定ですが、現在
のところは Visual Studio を使用します。
Linux と OS X
クロスプラットフォームの要件が単に別の CLI 実装で稼働することに限定されるのでなければ、OS X で Apple Mac を
動かすか、どこかで Linux を動かすことになります。ここで取り上げる例では、(執筆時点で)最新版である OS X バ
ージョン 10.6(Snow Leopard)を使用しています。また、Debian ベースの Linux ディストリビューションである
Ubuntu も動かしています。作業のほとんどは、2009 年 4 月リリースの Ubuntu(バージョン 9.04、別名 Jaunty
Jackalope)で行いました。
-7-
Delphi Prism を用いた Mono 開発
Brian Long
Mono
Delphi Prism をインストールすると、Windows 版の Mono(現在はバージョン 2.4)もインストールされます(インス
トール ディレクトリは C:\Program Files\Mono-2.4 です)。
Ubuntu には Mono の中核部分があらかじめインストールされています。Ubuntu 9.04 には Mono のバージョン 2.0.1
が /usr/lib/mono および /usr/bin にインストールされています。Ubuntu 9.10 には Mono 2.4.2.3 が含まれています。
Linux ディストリビューションによって、http://www.go-mono.com/mono-downloads からパッケージをダウンロード
できる場合と、Ubuntu のようにディストリビューションに既にインストールされている場合があります。Ubuntu に
は Mono ライブラリの中核部分しかインストールされていないことから後で問題が起きるのですが、解決方法はその
時に説明します。他のディストリビューションでも同様の問題が起きるかもしれませんが、調べれば同様の解決策があ
るでしょう。
OS X には Mono がインストールされていないので、上記の Mono ダウンロード リンクから適切なバージョンを取得す
る必要があります。Intel アーキテクチャ用のバージョンと PowerPC アーキテクチャ用のバージョンが置かれています。
Mac では、/Library/Frameworks/Mono.framework と /usr/bin にインストールされます。Mac インストーラは次のよう
なものです。
Mono ソース
もちろん Mono を使うために必要なわけではありませんが、Mono の各部分のソース コードを好きなだけダウンロー
ド で き る こ と を 知 っ て お い て く だ さ い 。 こ れ は 非 常 に た め に な り ま す 。 私 は 、 http://www.monoproject.com/Compiling_Mono_From_SVN に書かれているように、Subversion を使って SVN ツリーから直接にソース
-8-
Delphi Prism を用いた Mono 開発
Brian Long
をチェックアウトしています。Mono のメイン クラス ライブラリ ソースの場合、110,000 ファイルほど(合計で 0.5
ギガバイトより少し多いくらい)が mcs という新しいディレクトリにダウンロードされます。
svn co http://anonsvn.mono-project.com/source/trunk/mcs
Xcode と Interface Builder
Mac 用に開発をする予定で、最終的に Mac 風の外観を持つアプリケーションにしたいのであれば、Apple が無料で提
供している Xcode 開発ツールをインストールする必要があります。このツールには、開発環境(Xcode)と、さらに
重要なことに UI 設計ツール(Interface Builder)が含まれています。この 2 つは、OS X インストール DVD のオプショ
ン インストールにも含まれていますし、無料の ADC(Apple Developer Connection)メンバシップに登録する
と http://developer.apple.com/tools/xcode からインストーラ パッケージをダウンロードすることもできます。
一見して Apple だとわかるユーザー インターフェイスは Cocoa ライブラリで作成します。Cocoa を使用したアプリケ
ーションの構築に取り掛かるには、Xcode ツールに含まれる Interface Builder を使用する必要があります。このホワイ
トペーパーの準備に最新版の OS X(10.6)を使用している理由の 1 つは、Interface Builder の最近の数バージョンでは
それぞれに大きな変更がなされていて、さまざまなものが異なる場所にあったり、そもそも含まれていなかったりする
ことです。最新版を使うことで、読者の皆さん全員が最新の変更の恩恵を受けることができます。
共有フォルダ
次に考慮しなければならないのは、Windows でのプロジェクトのコンパイルと Linux や OS X での実行をどう管理する
かです。何らかの共有フォルダが必要です。これは、Linux や OS X のマシン上のフォルダをネットワーク経由で共有
するのでもかまいませんし、非 Windows マシン上の VM ソフトウェアで Windows を動かしている場合には共有フォ
ルダのメカニズムを使ってもかまいません。あるいは、Windows の共有フォルダを Linux や Mac のファイル システム
にマウントすることもできます。
私の場合は、MacPro を実マシンとして使用し、Windows XP と Ubuntu Linux を VMWare を使ってそれぞれの仮想マ
シン上で同時に実行しています。OS X のホーム ディレクトリのサブディレクトリの 1 つを両方の VM と共有している
ため、Windows VM で Mac のドライブ上にアプリケーションを生成できますし、Linux VM でもそれを見ることができ
ます。
最初の開発:コンソール アプリケーション
さて、コードを書くときが来ました。まずは主張が正しいことを証明しなければなりません。.NET アプリケーション
を構築して、それが .NET と Mono で実行できることを確認します。その後、同じプロジェクトを Mono プロジェクト
-9-
Delphi Prism を用いた Mono 開発
Brian Long
として構築し、それも両方のプラットフォームで動くことを証明します。最初は、環境についてのさまざまな情報を出
力する、簡単なコンソール アプリケーションを作成します。[ファイル|新規作成|プロジェクト...](Ctrl + Shift +
N)を選択して利用可能なプロジェクトを探すと、Delphi Prism のセクションに [コンソール アプリケーション] が、
Mono のセクションに [Mono コンソール アプリケーション] があります。どちらを選んでもかまいません(実際に両方
を試してみることもできます)。
どちらのプロジェクトでもメイン ソースは次のようになります。
class method ConsoleApp.Main;
begin
Console.WriteLine(String.Format('Command line: {0}', Environment.CommandLine));
Console.WriteLine(String.Format('Current directory: {0}',
Environment.CurrentDirectory));
Console.WriteLine(String.Format('#Processors: {0}', Environment.ProcessorCount));
Console.WriteLine(String.Format('User name: {0}', Environment.UserName));
Console.WriteLine(String.Format('Machine name: {0}', Environment.MachineName));
Console.WriteLine(String.Format('User domain: {0}', Environment.UserDomainName));
Console.WriteLine(String.Format('Reported OS Version summary: {0}',
Environment.OSVersion.VersionString));
Console.WriteLine(String.Format('Reported OS platform: {0}',
Environment.OSVersion.Platform));
Console.WriteLine(String.Format('Reported OS Version: {0}',
Environment.OSVersion.Version));
Console.WriteLine(String.Format('Reported OS Service Pack: {0}',
Environment.OSVersion.ServicePack));
Console.Write('Actual platform: ');
if IsWindows then
Console.WriteLine(String.Format('A Windows platform ({0})',
Environment.OSVersion.Platform))
else if IsLinux then
Console.WriteLine('Linux')
else
Console.WriteLine('Mac OS X');
Console.WriteLine(String.Format('Execution engine version: {0}',
Environment.Version));
Console.Write('Running .NET or Mono: ');
if IsMono then
Console.WriteLine('Mono')
else
Console.WriteLine('.NET');
Console.WriteLine(String.Format('System dir: {0}', Environment.SystemDirectory));
Console.WriteLine(String.Format('Application Data folder: {0}',
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)));
Console.WriteLine(String.Format('Desktop folder: {0}',
Environment.GetFolderPath(Environment.SpecialFolder.Desktop)));
// More of the same sorts of calls...
Console.ReadLine;
end;
このコードではいくつかのヘルパ メソッドを使用しています。後で説明しますが、どうしても必要な時にランタイム
や OS を確認するためのものです。現在のところは、.NET 版または Mono 版のプロジェクトをコンパイルすると、基
本的には同じ結果(Mono または .NET が見つかる場所で実行できるマネージ PE(Portable Executable)ファイル)が
得られるという点に着目してください。たとえば、Mono 版のプロジェクトを .NET で実行すると次のようになります。
- 10 -
Delphi Prism を用いた Mono 開発
Brian Long
このアプリケーションでは .NET で動いているとわざわざ報告してくれていますが、それだけでなく、.NET で動いて
いるのは、アプリケーションに次のような mono コマンドを渡さなければ Mono で実行できないからだということを
理解しておいてください。
メモ: Windows では、Mono の bin ディレクトリが必ずシステム パスに含められているとは限りません。これを修正
してもかまいませんし、[スタート] メニューの Mono フォルダにある Mono コマンド プロンプトを使用してもかまい
ません。
ランタイム(CLI)に関係なく、アプリケーションでは律儀に Windows ビルドの詳細を出力します。興味深いことに、
Mono では、実行エンジン(仮想マシン)のバージョンが .NET CLR のものとまったく同じであると出力します。これ
はおそらく互換性のためです。
- 11 -
Delphi Prism を用いた Mono 開発
Brian Long
次に示すのは、アプリケーションを Linux 上で実行したものです。ここで表示されているプラットフォーム バージョ
ンは、Linux カーネルのバージョン番号です。Personal や My Documents のディレクトリに注目してください。Unix
ベースの OS でコマンド プロンプトを使用したりスクリプトを書いたりするときには、このホーム ディレクトリは通
常、省略して ~ と表記されます。
そしてこちらが OS X で実行したものです。ディレクトリ構造の違いがわかります。
- 12 -
Delphi Prism を用いた Mono 開発
Brian Long
プラットフォームとランタイムの特定
先に進む前に、コードで使われているヘルパ関数について説明しなければなりません。これはランタイムと OS を特定
するためのものです。ランタイムを確認する呼び出しは簡単です。Mono の mscorlib.dll には必ず Mono.Runtime 型
が含まれているため、次のようにします。
class method ConsoleApp.IsMono: Boolean;
begin
exit &Type.GetType('Mono.Runtime') <> nil
end;
class method ConsoleApp.IsDotNet: Boolean;
begin
exit not IsMono()
end;
メモ: このコードでは、Delphi Prism の Exit に対する拡張機能を使っています。この拡張では、1 つのステートメン
トで、戻り値を指定して終了することができます。これは C や C# の return 文と似ています。この例で Result に代入
する代わりにこの拡張を使うのは無意味ですが、後の例のように他のロジックの途中で使用するのは有益です。値を
Result に代入してから Exit を呼び出す(または他の複雑な条件ロジックを使って後のコードを実行しないようにす
る)必要がなくなるからです。
プラットフォーム特定用のルーチンに関しては、System.Environment.OSVersion.Platform をちょっと覗けば解決でき
るのにと考えても無理はありません。この値は System.PlatformID 列挙型であり、この列挙型には対象とするプラット
フォームがすべて含まれているからです。しかし、ここでは歴史的な知識が役立ちます。.NET 1.0 が出てきたとき、
PlatformID に定義されていたのは Windows の値(Win32NT、Win32S、Win32Windows、WinCE)だけでした。.NET 2
で Unix という値が追加され、.NET 3.5 で MacOSX と Xbox が追加されました。その結果、Mac 上の Mono で問題が発
生します。上の例を見るとわかりますが、OS X では Unix が返されています。(既に Mono に追加されていた)正し
い MacOSX という値を返すことも検討されたのですが、その時点ではあまりに多くのコードで問題が起きました。
汎用の Unix プラットフォームの検出も問題になります。当初の Mono は PlatformID の値をそのままに表現していた
ため、Unix では Platform は 128 という整数値を返していました。.NET 2 で Unix という値(整数値 4)が追加されると、
その値が返されるようになり、さらに最近になって MacOSX(値 6)が追加されました。
そのため、プラットフォームが Windows か Unix かを判断するには、System.IO.Path.DirectorySeparatorChar が '\' か
'/' かを確認する方法が一般に推奨されています。Unix の種類(Linux か OS X か)を判断するには、Unix の uname コ
マンドを利用する方法が良いとされています。ターミナル ウィンドウで uname を呼び出すと、'Darwin'(OS X の基と
なっている FreeBSD 系 Unix)または 'linux' が返されます。このようなコマンドをプログラムから呼び出すにはどうす
ればよいでしょうか。外部プロセスを呼び出すのは行き過ぎです。通常は Mono.Posix.dll の参照を追加して、
Mono.Unix.Native.Syscall.uname() を呼び出します。Windows ではこの呼び出しを行わないよう注意してください。
- 13 -
Delphi Prism を用いた Mono 開発
Brian Long
class method ConsoleApp.IsWindows: Boolean;
begin
Result := System.IO.Path.DirectorySeparatorChar = '\'
end;
class method ConsoleApp.IsLinux: Boolean;
begin
if IsWindows then
exit False;
var buf: Mono.Unix.Native.Utsname;
if Mono.Unix.Native.Syscall.uname(out buf) = 0 then
exit string.Compare(buf.sysname, 'linux', True) = 0;
Result := false;
end;
class method ConsoleApp.IsOSX: Boolean;
begin
if IsWindows then
exit False;
var buf: Mono.Unix.Native.Utsname;
if Mono.Unix.Native.Syscall.uname(out buf) = 0 then
exit string.Compare(buf.sysname, 'darwin', True) = 0;
Result := false;
end;
メモ: 上記のコードでは、Delphi Prism の構文拡張を利用して、メソッドのコード ブロックの上にある var セクションを使
うのではなく、必要になった場所で変数を宣言できるようにしています。
Mono ライブラリのいずれかを使用していて、Windows 上に(特に Mono を使っていない .NET ユーザーに)配置する予定
があるのなら、依存ファイルがすべて bin ディレクトリにコピーされるよう、Mono アセンブリ参照の Copy Local オプショ
ンを必ず True に設定してください。そうすることで、ユーザーに何を配置する必要があるかを簡単に知ることができます。
完全を期すため、そしてクロスプラットフォーム コードでもプラットフォーム呼び出しを利用できることを示すために、次
のように Unix のライブラリ libc を直接使用する方法もあります。
ConsoleApp = class
private
[DllImport('libc')]
class method uname(buf: IntPtr): Integer; external;
...
end;
...
class method ConsoleApp.RunningOnUnix: Boolean;
begin
//.NET 1.x didn't have a Unix value in System.PlatformID enum, so Mono
//just used value 128.
//.NET 2 added Unix to PlatformID, but with value 4
//.NET 3.5 added MacOSX with a value of 6
exit Integer(Environment.OSVersion.Platform) in [4, 6, 128];
end;
class method ConsoleApp.RunningOnLinux: Boolean;
begin
exit RunningOnUnix and InternalRunningLinuxInsteadOfOSX
end;
class method ConsoleApp.RunningOnOSX: Boolean;
begin
exit RunningOnUnix and not InternalRunningLinuxInsteadOfOSX
end;
- 14 -
Delphi Prism を用いた Mono 開発
Brian Long
class method ConsoleApp.InternalRunningLinuxInsteadOfOSX: Boolean;
begin
//based on Mono cross-platform checking code in:
// mcs\class\Managed.Windows.Forms\System.Windows.Forms\XplatUI.cs
if not RunningOnUnix then
raise new Exception('This is not a Unix platform!');
var Buf: IntPtr := Marshal.AllocHGlobal(8192);
try
if uname(buf) <> 0 then
//assume Linux of some sort
exit True
else
//Darwin is the Unix variant that OS X is based on
exit Marshal.PtrToStringAnsi(Buf) <> 'Darwin'
finally
Marshal.FreeHGlobal(Buf);
end;
end;
メモ: Delphi Prism コンパイラに関して言うと、Mono プロジェクトと .NET プロジェクトの違いはリンクする先のア
センブリだけです。Mono プロジェクトの場合、プロジェクト ファイルには Mono フレームワーク アセンブリを指す
指令が含まれています。
アプリケーションの配置
アプリケーションを Mono ランタイムで実行したときに、実行可能ファイル名を mono コマンドの引数として渡さな
ければならないという要件がありました。コマンドラインで通常行うよりも多くのことをユーザーがしなければならな
いため、これは少し問題になるかもしれません。もちろん、このアプリケーションがどこかのアプリケーション メニ
ューに含められて、コマンドラインが隠されるなら、大した問題にならないかもしれませんが、Mac にはそのような
メニューがありません。ですから、アプリケーションを配置してユーザーに実行してもらうための選択肢を検討してみ
ましょう。
現状のままにする
1 つ目の選択肢は、何もせず、ユーザーが mono を使ってアプリケーションを実行しなければならないままにするこ
とです。コマンドライン ツールの場合、特にユーザーが Mono アプリケーションや Mono ツールを使い慣れている場
合にはこれで問題ないかもしれませんが、あまり満足のいく方法ではありません。
- 15 -
Delphi Prism を用いた Mono 開発
Brian Long
.exe ファイルの動作を変更する
呼び出すファイルがポータブル実行可能ファイル形式に準拠したファイル(.exe ファイル)である場合には、Linux で
特有の応答をセットアップし、binfmt カーネル モジュール経由で mono を使って実行することが可能です。しかし、
他のアプリケーションを妨害する可能性があるため、この方法は推奨できません。たとえば、VMWare Fusion では、
ゲストの Windows システムのコンテキストで .exe ファイルを実行するために、同様のメカニズムをセットアップして
います。
スクリプト
次善の策は、mono を実行してアプリケーションを渡すことだけを目的にしたシェル スクリプトをアプリケーション
と一緒に提供することです。Windows ではバッチ ファイル(.bat)またはコマンド スクリプト(.cmd)になりますが、
Windows に関してはこの問題に現実的な意味はないと言っていいでしょう。ユーザーは通常、[スタート] メニューの
フォルダからアプリケーションを起動するからです。ただし、コマンドライン ツール(Mono 本体と一緒に提供され
ているものなど)の場合には、バッチ ファイルを使用するのが妥当です。
Unix システムでは、シェル スクリプトにはファイル拡張子が付きません。使い慣れた Visual Studio のエディタでスク
リプトを書こうと安易に考えてしまうかもしれませんが、それではあまりうまくいきません。まず行末の問題がありま
す。Windows ではデフォルトで復帰文字と改行文字を使用しますが、Unix では改行しか使用しません。しかしさらに
重要なのは、スクリプトをテストする必要があることです。ですから思い切って Unix のテキスト エディタを使用する
べきです。
Mac では、弱気な人はグラフィカルな TextEdit アプリケーションに移行しつつありますが、ed、pico(実際は
nano)、vi(実際は vim)、悪名高い emacs など、さまざまなターミナル エディタが存在します。筋金入りの
Unix マニアなら vi か emacs を選ぶ利点を主張するでしょうが、以前からある vim エディタを GUI ベースに使いやす
くした MacVim を http://code.google.com/p/macvim から入手できます。また、使い方を理解するために vimtutor を
実行するとよいでしょう。
Ubuntu では、ed、pico(実際は nano)、vi が最初からインストールされています。vim、vimtutor、emacs も利用可
能です。これらについては、コマンドを実行するとインストール方法が表示されます。
Mono アプリケーションの名前が Blah.exe だとすると、適切なスクリプト(最低限のことしか含んでいませんが)は
次のようになります。スクリプトの名前は、大文字/小文字を区別する Unix システムで一般的に見られる小文字表記を
使って、おそらくは blah になります。
#!/bin/sh
mono Blah.exe $@
- 16 -
Delphi Prism を用いた Mono 開発
Brian Long
スクリプトの 1 行目はシェル実行指令であり、スクリプトを sh(OS X の bash(Bourne-Again shell)に相当しま
す)で実行することを指定しています。2 行目では、Mono アプリケーションを引数に渡して mono を実行していま
す。$@ は、スクリプトに指定されたすべてのコマンドライン パラメータをプログラムに引き渡すことを表します。
メモ: Mac の UK キーボードには # キーがありません。# を入力するには Alt + 3 を使用してください。
メモ: シェル スクリプトを直接実行するには、アクセス権を実行可能に設定しなければなりません。実行可能でなけ
れば Permission denied(権限がない)というエラーが発生します。実行可能にするには chmod +x blah というコマン
ドを使います。あるいは、sh blah のように明示的にシェルを使ってスクリプトを実行することもできます。
メモ: プロジェクトをコンパイルして出力する先の共有フォルダが Linux または OS X のファイル システムにマウン
トされた Windows フォルダであれば、上記の問題は発生しません。Windows のファイル システムでは Unix の実行ビ
ットを理解できないので、すべてのファイルがデフォルトで実行可能になるからです。これは便利で時間の節約にもな
りますが、スクリプトを実行可能にしなければならないという根本的な意味は覚えておいてください。
メモ: スクリプトと同じディレクトリで実行する場合でも、スクリプト名だけを入力してシェルを直接に実行するこ
とはできません。実行するスクリプトはパス上になければなりませんが、Unix ベースのシステムでは、たいていの場
合、カレント ディレクトリはパスに含まれません(セキュリティ上の理由で)。そのため、./blah のようにして明示的
にパスを示す必要があります。
メモ: Mono ツールはマネージ アプリケーションであり、1 つの単語だけで呼び出せるよう、パス上のディレクトリ
にあるスクリプトが使われています。
バンドル形式実行可能ファイル
もう 1 つの選択肢は、アプリケーションに必要なものをすべて 1 つの実行可能ファイルに同梱する方法です。Mono ツ
ールの mkbundle はまさにそのためのものです。このツールは、実行可能ファイルを調べて依存するアセンブリ(と構
成ファイル)を特定し(--deps コマンドライン スイッチを指定した場合)、生のアセンブリ言語ファイルとしてエン
コードします。このファイルは次に、GNU アセンブラによってアセンブルされ、オブジェクト ファイルとして出力さ
れます。mkbundle は、次に C ファイルを作成し、コンパイルして、1 つの自己完結したアプリケーションにすべてを
まとめます。このアプリケーションを起動すると、やはりさまざまなネイティブ ライブラリが使われますが、マネー
ジ アセンブリはすべて 1 つのイメージにコンパイルされています。この方法を使った例は後で取り上げます(- 39 - ペ
ージを参照してください)。
mkbundle には --static オプションも用意されていて、これを指定すると、Mono ライブラリが出力先の実行可能ファ
イルに動的にリンクされるのではなく、静的にリンクされます。そうするとアプリケーションで LGPL ライセンスを使
用しなければならなくなるため、一般的にこのオプションは避けた方が賢明です。mkbundle は実際に、--static
- 17 -
Delphi Prism を用いた Mono 開発
Brian Long
スイッチが使われると、この問題について次のような警告メッセージを出力します。"Note that statically linking the
LGPL
Mono
runtime
has
more
licensing
restrictions
than
dynamically
linking.
See
http://www.mono-
project.com/Licensing for details on licensing."(LGPL Mono ランタイムを静的にリンクすると、動的にリンクする場
合よりもライセンスの制限が厳しくなります。ライセンスの詳細については http://www.mono-project.com/Licensing
を参照してください。)
Mac OS X のアプリケーション バンドル
OS X では、アプリケーションで必要な厄介な付加的リソースをまとめるために、もう 1 つ、アプリケーション バンド
ル(パッケージ)という方法を用意しています。Mac を使ったことがあり、Finder でアプリケーションを起動したこ
とがあれば、アプリケーション バンドルが 1 つにまとまった項目として表示され、アプリケーションのインストール
やアンインストールがとても簡単に行えるのを目にしているでしょう。しかし、そのように見えるのは幻想です。各ア
プリケーションは、そのアプリケーションで必要なさまざまなファイルやリソースを含むディレクトリ構造になってい
ます。
Finder でアプリケーションを右クリックすると、コンテキスト メニューに [パッケージの内容を表示] というメニュー
項目が表示されます。それを選択すると、別の Finder ウィンドウが開き、アプリケーション バンドル(パッケージ)
内のディレクトリ構造が表示されます。
1 つにまとまったアプリケーションに見えるけれども実際にはディレクトリであるアプリケーション バンドルに
は、.app という拡張子が付いていますが、それは Finder では隠されています。その下には Contents ディレクトリが
あります。その中に、実行可能プログラムが含まれる MacOS ディレクトリと、Resources ディレクトリ、それから
Info.plist というファイルがあります。最後のファイルはプロパティ リストであり、アプリケーション バンドルの情報
が決まった形式で記述されています。バンドルのディレクトリ構造には必要に応じて他のファイルも含まれますが、こ
こで述べたものが主な内容です。Dock やタスク スイッチャで表示されるアプリケーション アイコンを設定するには、
アプリケーション バンドルを使うのが最も適した方法です。アイコン ファイルを Resources ディレクトリに置き、プ
ロパティ リスト ファイルのエントリから参照します。
バンドルとしてセットアップされたアプリケーションは、Finder では明らかに問題なく開けますが、コマンドライン
からはどうでしょうか。アプリケーションがバンドルとしてセットアップされている場合には、open コマンドを使用
することができます。これはそもそも Finder で使われているものです。
アプリケーション バンドルのディレクトリ名(/Applications/iChat.app など)がわかっている場合には、次のように
パラメータとして open に渡します。
open /Applications/iChat.app
MacVim(インストールされている場合)などのように、アプリケーションが /Applications メイン ディレクトリにイ
ンストールされていて名前がわかっているなら、-a スイッチを使って、たとえば次のようにパラメータを渡すことが
- 18 -
Delphi Prism を用いた Mono 開発
Brian Long
できます。
open -a MacVim ~/.bash_profile
メモ: open には、渡されたファイルを TextEdit で自動的に開くための -e スイッチもあります。
さまざまなサポート ファイルを使用する OS X アプリケーションを構築するときには、アプリケーション バンドルを
選択するのが自然です。実際のところ、Cocoa アプリケーションを構築するときには、ほとんどバンドルを使用せざ
るを得ません。その作業の助けとして、Mono には macpack というツールが用意されています。このツールは、アセ
ンブリとさまざまなリソースとアイコン ファイルを受け取って、アプリケーション バンドルを生成します。-m スイ
ッチを使うと、そのアプリケーション バンドルが WinForms アプリケーション、Cocoa アプリケーション、X11 アプ
リケーション、コンソール アプリケーションのいずれであるかを指定することができます。生成しなければならない
ものが一部異なるためです。
アプリケーション バンドルについては、また後でも説明します(- 41 - ページを参照してください)。
データ アクセス
ECMA CLI 仕様の範囲には含まれませんが、Mono では ADO.NET 機能をサポートしています。標準の ADO.NET 名前
空 間 だ け で な く 、 さ ま ざ ま な 他 の デ ー タ ベ ー ス 用 の Mono 固 有 の 名 前 空 間 も 数 多 く 存 在 し ま す 。 詳 細
は、http://www.mono-project.com/Database_Access を参照してください。興味深いのは、Microsoft SQL Server をサ
ポートするための System.Data.SqlClient 名前空間などは完全なマネージ コードで書かれているため、ネイティブ クラ
イアントが必要ないことです。
メモ: Mono には SQL#(sqlsharp で起動できます)という便利なデータベース問い合わせツールが含まれていて、プ
ロバイダとコネクション文字列を設定すると SQL プロンプトからクエリ文字列を試すことができます。デフォルトで
は Ubuntu に 含 ま れ ま せ ん が 、 sudo apt-get install mono-devel を 実 行 す る と 簡 単 に 追 加 で き ま す 。
sudo は、スーパーユーザー(root)としてコマンドを実行できるようにするためのコマンドです。
よく使われるデータベースはオープン ソースの MySQL(http://www.mysql.com)で、このデータベースにはさまざま
な方法でアクセスすることができます。通常の .NET では、MySQL ODBC ドライバを使って、次のようなコードで
SELECT クエリを実行できます。
uses System.Data.Odbc;
...
class method ConsoleApp.GetSomeData(): string;
const
rootPassword = '';
//ODBC connection string
connectionString = 'Driver={MySQL ODBC 3.51 Driver};Server=localhost;' +
'User=root;Password=' + rootPassword + ';Option=3;';
selectSQL = 'SELECT * FROM Customers WHERE Town LIKE ''%che%'';';
widths: Array of Integer = [3, 22, 12, 6];
var
- 19 -
Delphi Prism を用いた Mono 開発
Brian Long
results: StringBuilder := new StringBuilder();
begin
using sqlConnection: OdbcConnection := new OdbcConnection(connectionString) do
begin
sqlConnection.Open();
SetupData(sqlConnection);
var dataAdapter: OdbcDataAdapter := new OdbcDataAdapter(selectSQL, sqlConnection);
var dataTable: DataTable := new DataTable('Results');
dataAdapter.Fill(dataTable);
for each row: DataRow in dataTable.Rows do
begin
results.Append('|');
for I: Integer := 0 to dataTable.Columns.Count - 1 do
begin
var Width := 20;
if I <= High(widths) then
Width := widths[I];
results.AppendFormat('{0,' + Width.ToString() + '}|', row.Item[I]);
end;
results.Append(Environment.NewLine);
end;
TearDownData(sqlConnection);
end;
Result := results.ToString()
end;
このコードは、.NET でうまく動作し、Windows 上の Mono でも同じ結果になります。
メモ: 上のコードでは、Delphi Prism の for each ループ構文で row という変数を使用しています。この変数はルー
プの中でのみ利用可能で、ループ構文自体で宣言されています。
メモ: 各行の終わりに決まった文字シーケンスを入れるのではなく、プラットフォームごとに適した文字シーケンス
を System.Environment.NewLine で取得しています。
このコードは正しく動きますが、ODBC は Microsoft オペレーティング システムで普及している技術であり、それ以
外ではあまり見かけません。Mono では ODBC との通信をサポートしていますが、Windows 以外のプラットフォーム
ではどうなるのでしょうか。Linux では DllNotFoundException: libodbc.so というエラーが、OS X ではいくらか不可解
な OdbcException: ERROR [I というエラーが発生します。
- 20 -
Delphi Prism を用いた Mono 開発
Brian Long
unixODBC プロジェクト(http://www.unixodbc.org)の助けを借りれば、ODBC を Unix ベースのプラットフォーム上
で動かすことができます。http://www.unixodbc.org/drivers.html には多くのネイティブ ドライバが挙げられています
が、おそらく ODBC は避けた方が良いでしょう。
もう 1 つの方法は、MySQL Connector/Net(http://dev.mysql.com/downloads/connector/net から入手可能)などのよ
り優れたクロスプラットフォーム ドライバを使用することです。このダウンロード リンクでは Windows のサポート
が暗示されていますが、実際にアーカイブにはマネージ アセンブリの mysql.data.dll が含まれていて、これを自分のプ
ロジェクトで参照する必要があります(gacutil ツールを使って GAC(グローバル アセンブリ キャッシュ)にインス
トールする予定でなければ、アセンブリ参照のプロパティで Copy Local オプションを True に設定することを忘れない
で く だ さ い ) 。 公 式 の イ ン ス ト ー ル 手 順 は http://dev.mysql.com/doc/refman/5.1/en/connector-net-installationunix.html にあります。このドライバを使う場合には、コードは次のようになります。
uses MySql.Data.MySqlClient;
...
class method ConsoleApp.GetSomeData(): string;
const
rootPassword = '';
//MySQL Connector/Net connection string
connectionString = 'Server=localhost;Username=root;Password=' + rootPassword;
selectSQL = 'SELECT * FROM Customers WHERE Town LIKE ''%che%'';';
widths: Array of Integer = [3, 22, 12, 6];
var
results: StringBuilder := new StringBuilder();
begin
using sqlConnection: MySqlConnection := new MySqlConnection(connectionString) do
begin
sqlConnection.Open();
SetupData(sqlConnection);
var dataAdapter: MySqlDataAdapter :=
new MySqlDataAdapter(selectSQL, sqlConnection);
var dataTable: DataTable := new DataTable('Results');
dataAdapter.Fill(dataTable);
for each row: DataRow in dataTable.Rows do
begin
results.Append('|');
for I: Integer := 0 to dataTable.Columns.Count - 1 do
begin
var Width := 20;
if I <= High(widths) then
Width := widths[I];
results.AppendFormat('{0,' + Width.ToString() + '}|', row.Item[I]);
end;
results.Append(Environment.NewLine);
end;
TearDownData(sqlConnection);
end;
Result := results.ToString()
end;
このコードは、Windows 上の Mono で(.NET でも)同じように動きます。
- 21 -
Delphi Prism を用いた Mono 開発
Brian Long
Windows の場合と同様に、MySQL はデフォルトでは Linux や OS X にもインストールされません。Mac 版は MySQL
の Web サイトからダウンロードできます(サービスの起動手順については README を必ず丁寧に読んでください)。
Linux ディストリビューションによって異なりますが、Ubuntu では sudo apt-get install mysql-server で MySQL をイン
ストールできます。
これでデータ アクセスは、Linux でも OS X でもまったく同じように動きます。
- 22 -
Delphi Prism を用いた Mono 開発
Brian Long
GUI アプリケーションの問題
面白く/厄介になってくるのはここからです(適切に削除してください)。アプリケーションの GUI は、動く OS によ
って明確に異なります。Windows アプリケーションは、バージョンの違いや多少の変化はあっても『Windows User
Experience Interaction Guidelines ( Windows ユ ー ザ ー
エクスペリエンス
ガイドライン ) 』
(http://msdn.microsoft.com/en-us/library/aa511258.aspx)に沿って Windows コントロールを使って構築されている
ため、特徴のあるルック アンド フィールになっています。このガイドラインは何年もの間に変化してきたので、
Windows アプリケーションはさまざまに異なりますが、それでも Windows アプリケーションだということはすぐにわ
かります。
Linux アプリケーションのルック アンド フィールはそれほど共通してはいませんが、同じ GUI ツールキットを使って
構築されたものは通常、ある程度一貫した外見になります。
Mac アプリケーションは、先に述べたように、ほとんどどれも Cocoa ライブラリを使っているため、一定のルック ア
ンド フィールになっています。さらに、Apple の『Human Interface Guidelines』では、Mac アプリケーションとして
認められるにはどのような動きや外観にしなければならないかを明確に規定しています
(http://developer.apple.com/mac/library/documentation/UserExperience/Conceptual/AppleHIGuidelines)
では、クロスプラットフォーム ソリューション、特にクロス OS の場合にはどうすればよいでしょうか。どのような
ユーザーを想定しているか、現在抱えているかなどを検討して、選択しなければなりません。既存の .NET アプリケー
ションをクロスプラットフォーム対応させようとしている場合には、おそらく既に GUI を開発しているかと思います。
クロスプラットフォーム アプリケーションの初期バージョンでは、GUI を WinForms の UI のままにすることを検討し
てもよいでしょう。WinForms は ECMA 仕様には含まれませんが、Mono ではサポートされています。あまり重要でな
い機能の一部は実際にはうまく動きませんが、API はすべてサポートされています。
対象としているユーザーが少数であったり寛容である場合には、既に持っている WinForms の知識を活用することも許
容範囲内かもしれません。問題になるのは、Linux GNOME や KDE デスクトップや OS X などでは、WinForms アプリ
ケーションが周りと調和せずに目立ってしまうことです。特に Mac ユーザーは Cocoa 以外のアプリケーションを嫌が
りますが、どうするかを選択するのは読者の皆さんです。
その他に、アプリケーションの GUI を別に構築する方法もあります。GTK#(GTK+ に Mono レイヤを被せたもの)な
ど、別のクロスプラットフォーム UI ツールキットを使用して、アプリケーション全体を新しく構築することができま
す。GTK+ はよく普及している GNOME デスクトップの構築に使われるツールキットなので、Linux ユーザーには喜ば
れるでしょうが、これもまた Mac では場違いに見えます。他にも、Qyoto(Qt にレイヤを被せて Linux KDE デスクト
ップのルック アンド フィールを実現するもの)や wxNet(wxWindows にレイヤを被せたもの)など、クロスプラッ
トフォーム UI 開発用に Mono がサポートしている同様のツールキットが存在します。
- 23 -
Delphi Prism を用いた Mono 開発
Brian Long
さらに別の方法として、すべてのビジネス ロジックを自己完結したアセンブリにできるだけ分離して、さまざまな呼
び出しインターフェイスを適切に用意し、対象とするプラットフォームごとに異なるフロント エンドを構築すること
もできます。多くのアプリケーションではこの方法を採用しています。この方法では明らかに、学習や開発の工数が相
当余分に必要になりますが、それぞれの OS についてネイティブ同様のアプリケーションを作成することができます。
ここで少し選択の指針になりそうなのは、Delphi Prism にプロジェクト テンプレートが備わっているツールキットに
限定するという方法です。クロス OS プロジェクト用の WinForms や GTK#、OS X 用の Cocoa# や Monobjc などのテ
ンプレートがあります。もちろん、Qyoto や wxNet や、存在するならその他の Mono GUI ツールキット レイヤを検討
してもかまいませんが、プロジェクト テンプレートがあれば少なくとも出発点にはなります。
こういったツールキットについて見てみましょう。
GUI ツールキット
WinForms
Mono での WinForms の実装は、出だしからいくつかの失敗をしました。既存のグラフィック ツールキットにレイヤ
を 追加しようとした ため 、 丸 い 穴 に 四 角い 杭を打つような制約が生じてしまったのです。現在の実装 では、
System.Drawing を使ってすべてのコントロールをレンダリングし、System.Drawing がプラットフォーム依存のドライ
バを使って OS のウィンドウ システムと通信するようになっています。しかし現在のところ、レンダリングは
Windows のテーマを使って行われているだけで、Linux や OS X のテーマは今後対応されるかもしれないという程度で
す。
原則から言うと、作成した .NET の WinForms アプリケーションを Linux や OS X 上の Mono で実行できるはずですが、
アプリケーションを MoMA(Mono Migration Analyzer)で実行してみて、コードの処理内容について問題が予測され
るかを確認するのは有効です。
クロスプラットフォームの WinForms がサポートされているかを確認するには、既存の Delphi Prism WinForms .NET
プロジェクトを使用するか、.NET プロジェクト テンプレートまたは Mac OS X 用の Mono WinForms プロジェクト テ
ンプレートから WinForms プロジェクトを新たに作成します。どちらでも WinForms アプリケーションを開発して構築
することができますが、Mac OS X テンプレートには明らかにいくつかの違いがあります。
大きな違いは次の 2 つです。
Mono プロジェクトは Mono フレームワーク アセンブリにリンクされます。
- 24 -
Delphi Prism を用いた Mono 開発
Brian Long
OS X ユーザー用にアプリケーションのアプリケーション バンドルが作成されます。そのため、Mac の Dock やタスク
スイッチャでカスタム アイコンを使用することができます。これは 1 つの利点です。このアイコンがないと、アプリ
ケーションは汎用の実行可能アプリケーション アイコンで表示されることになるからです。
Delphi Prism では、Mono の macpack ツールを直接呼び出してアプリケーション バンドルを生成するのではなく、同
じ機能を持つカスタム MSBuild ターゲットを使用しています。
TreeView コントロールと NotifyIcon コントロールを使用する WinForms アプリケーションの例を見てみましょう(こ
の例を取り上げた理由については、次のセクションで GTK# に相当するアプリケーションを検討するときに詳しく説明
します)。最初にぶつかる問題は、Mono WinForms サポートが Ubuntu にインストールされていないことです
(ECMA 仕様の範囲外だという理由で Mono の中核部分に含まれていません)。これはいつものように簡単に解決で
きます。
MoMA で簡単に確認すると、使用している WinForms に問題はないという結果になります。デフォルトの最新版
Mono 定義ファイルで確認し、ダウンロードした Mono バージョン 2.0(Ubuntu 9.04 に含まれているもの)の定義フ
ァイルでも確認しました。
Linux で実行すると、きちんと動いてほとんど正しい処理が行われます。ただし、完璧ではありません。それについて
この後で説明します。
アプリケーションの動きはこうです。アプリケーションは、起動した後、プロセス中にロードされたすべてのアセンブ
リと、アセンブリ内のすべての型、型の中のすべてのメンバを洗い出し、それを TreeView にロードします。このすべ
- 25 -
Delphi Prism を用いた Mono 開発
Brian Long
ての情報をロードしている間、進行状況ダイアログが画面に現れ、何がロードされているかを表示します。ロードがす
べて終わると、進行状況ダイアログが消え、TreeView に情報を表示したメイン フォームが現れます。主な機能はそれ
だけですが、アプリケーションではその他に NotifyIcon コントロールをセットアップします。このコントロールは、
タスクバー通知領域(よく間違ってシステム トレイと呼ばれるもの)のアイコンとして Windows に表示されます。こ
の通知コントロールは、マウス クリック(Click イベント)に反応して、フォームの表示/非表示を切り替えます。また、
右クリックに反応して、[Quit](終了)オプションを持つメニューを表示します(ContextMenuStrip プロパティを使用
します)。
Windows ではすべて問題なく、左クリックにも右クリックにも正しく反応します。しかし Linux ではそれほど完璧で
はありません。通知アイコンは、左クリックには正しく反応しますが、右クリックへの反応は一見少し奇妙に思えます。
両方のイベントがトリガされ、ポップアップ メニューの表示とフォーム表示の切り替えの両方が行われるのです。明
らかに、Mono 実装では Click イベントを文字どおりに受け取って、あらゆる種類のクリックでイベントをトリガして
います。これは MoMA で探り出せる種類の問題ではありません。これは Mono WinForms の Linux 側のバグ(一貫性
の欠如)だと私は思っています。そもそも Click イベント ハンドラと ContextMenuStrip の両方を持つのは、あまり賢
明な手段ではなかったのかもしれません。
アプリケーションは OS X でも動きます。大体は動きますが、完全ではありません。NotifyIcon コントロールは OS X
で完全に実装されておらず、コードでアイコンを設定しようとすると、次のダイアログが現れます。
MoMA ではこの問題も見つかりませんでした。機能の漏れよりもメソッドの漏れを探しているからです。Mono クラ
ス
ライブラリのソースをダウンロードすると(方法については先に説明しました)、
msc\class\managed.Windows.Forms\System.Windows.Forms\XplatUICarbon.cs に問題のルーチンが含まれています
(Cocoa Mac UI フレームワーク自体が Carbon フレームワークを基に作成されていることに注意してください)。
具体的に言うと、SystrayAdd()、SystrayChange()、SystrayRemove() はどれも NotImplementedException を送出してい
て、MonoTODO 属性が設定されています。そのため、OS によってコードの条件分けをすることで、これらを使わず
に済ませる必要があります。
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
if not RunningOnOSX then
notifyIcon.Icon := Icon;
end;
それが終わればアプリケーションは OS X 上で問題なく動きます。
- 26 -
Delphi Prism を用いた Mono 開発
Brian Long
Mac のアプリケーション アイコン
カスタムの Dock アイコンに気付かれたでしょうか。これは、Mono WinForms プロジェクト テンプレートでアプリケ
ーション バンドルの内容をセットアップすることで実現しています。アプリケーション バンドルをセットアップする
各テンプレートでは、アプリケーション バンドルに送り込まれるデフォルトの App.icns ファイル(Apple のアイコン
画像ファイル)がプロジェクトに含まれていて、そのファイルを置き換えることができます。ただし、違う名前のファ
イルで置き換える場合には、それが Mac のアイコン ファイルであることを Delphi Prism に知らせなければなりません。
Delphi Prism の最新リリース(2009 年 8 月)では、XML 形式で格納されているプロジェクト ファイル(.oxygene フ
ァイル)を編集する必要があります。主要素 PropertyGroup の中に入れ子にして次のような MacIconFile 要素を追
加します。
<MacIconFile>Properties\Tree.icns</MacIconFile>
メモ: Apple の Xcode ツールには /Developer/Applications/Utilities に Icon Composer アプリケーションが含まれて
おり、それを使って .icns ファイルや .ico ファイルを作成することができます。このツールはファイルを構築するため
のものであって画像を編集するためのものではありませんが、必要とされるさまざまな解像度の適切な .png ファイル
があるなら、Icon Composer で .icns ファイルまたは .ico ファイルのプレースホルダ部分にドラッグするだけで済みま
す。http://www.zoobapps.com/free_icons など、アイコン構築に使用できる画像を無料で提供している Web サイトが
あります。
他のプラットフォームでも実行可能な WinForms アプリケーションの場合には、いつもどおりにアプリケーション ア
イコンを設定したり、フォームの Icon プロパティを設定することを忘れないでください。
- 27 -
Delphi Prism を用いた Mono 開発
Brian Long
これでカスタム アプリケーション アイコンができました。これはアプリケーションの実行中に Dock に表示されるだ
けでなく、アプリケーション バンドルを調べる際の Finder にももちろん表示されます。次のスクリーンショットには、
実際の WinForms 実行可能ファイル(とデバッグ シンボル ファイル)と、アプリケーション バンドル(Finder ではデ
フォルトでアプリケーション バンドル ディレクトリの拡張子 .app が隠されることを思い出してください)が表示さ
れています。
GTK#
GTK+(http://www.gtk.org および http://www.gtk-osx.org)は、クロスプラットフォームのグ
ラフィカル ウィジェット ツールキットです(ウィジェットとはコントロールのことです)。こ
れは、有名な GNU 画像エディタ GIMP(GNU Image Manipulation program)を開発するため
にわざわざ構築された、元の GTK のオブジェクト指向版です(そのため GTK は GIMP Tool Kit
と呼ばれています)。GTK#(http://www.mono-project.com/GtkSharp)は GTK+ の .NET バイ
ンディングであり、これを使って Mono GTK+ アプリケーションを開発することができます。
ドキュメントは、Mono ドキュメント(http://www.go-mono.com/docs)の Gnome Libraries の下にあります。左側の
階層ブラウザを使用してください。元の GTK+ ドキュメントのリンクもあれば便利かもしれません。このドキュメン
トは http://library.gnome.org/devel/references にあります。
GTK+ は、以下に示す、同じチームが保守する別個のライブラリに基づいて作られています。これらのライブラリはど
れも GTK# で表されています。
 Glib - イベント ループなどの実行時機能を提供するコア ライブラリ
- 28 -
Delphi Prism を用いた Mono 開発
Brian Long
 Pango - テキストのレンダリングやレイアウトのライブラリ。国際化をサポートしています
 GDK - GIMP Drawing Kit - ウィンドウ システムから GTK+ を隔離するもの
 Cairo - クロスプラットフォームの 2D グラフィック レンダリング システム
 ATK - アクセシビリティ ツールキット
GTK# アプリケーションの構築は、完全にコード ベースで行うこともできますし、Glade という、Windows 用と OS X
用の両方のバイナリを提供している UI デザイナ(http://glade.gnome.org)を使って行うこともできます。Glade を使
うとコードと完全に切り離してユーザー インターフェイスを設計することができ、その定義は XML ファイルに保存さ
れます。Glade では、ウィンドウ上へのコントロール(ウィジェット)のレイアウト、コントロールのプロパティの設
定、発生する事柄に応答するメソッド名のセットアップといった、標準的な UI 設計パラダイムを提供しています。
GTK+ および GTK# では、イベントはシグナルと、イベント ハンドラはコールバックと呼ばれます。
メモ: Glade の XML ファイルは libglade の機能によってロードされます。表向きには libglade は廃止予定で、
GtkBuilder(2007 年に GTK+ に追加されたもの)が後を継ぐことになっています。バージョン 3.6 の時点で、Glade は
libglade および GtkBuilder の両方のファイルと連携していますが、GTK# は現在 libglade しかサポートしていません。
おそらく今後どこかの時点で GTK# が GtkBuilder をサポートするようになるでしょうが、Glade 使用時にどの形式を
使うかを心に留めておいてください。Delphi Prism GTK# プロジェクト テンプレートには libglade XML ファイルが含
まれています。
メモ: Glade を使って UI を編集するときには、"何度も繰り返し保存する" という真理の呪文に従うことをお勧めし
ます。いら立たしいことに Glade がクラッシュして作業が無駄になったことが何度もあります。
メモ: Mac 上で Glade を使ってテキスト プロパティを編集するときには、キーボードの Delete キーに十分気を付け
てください。直感的には Windows の場合と同様にカーソルの後の文字が削除されると思われるでしょうが、Mac では
現在選択しているウィジェットを削除する傾向があります。その際に、子ウィジェットの階層構造があればすべて一緒
に削除されます。
メモ: Delphi Prism を MonoDevelop(http://monodevelop.com)と統合しようという作業が進められているため、
Glade を使うかどうかは近いうちに任意になるはずです。GTK# アプリケーション用に XML ベースの UI 設計方法も用
意されていますが、MonoDevelop では Stetic という GTK# に適した専用エディタが使われていて、libglade も
GtkBuilder も必要なくなります。
基本の GTK# アプリケーションには Main.pas ファイルがあり、名目上はそこにメイン フォームが含まれます。実際に
は、メイン フォームを代表するクラス(MainForm)と、その他に必要なフォームが含まれます。また、プロジェク
トには libglade XML ファイである Main.glade も含まれます。ここには、VBox コンテナを持つ GTK+ ウィンドウの
簡単な定義と、delete_event コールバック ハンドラとが含まれます。MainForm クラスは次のようになります。
type
MainForm = class(System.Object)
private
{$REGION Glade Widgets}
var
- 29 -
Delphi Prism を用いた Mono 開発
Brian Long
[Widget] window1: Gtk.Window;
{$ENDREGION}
public
constructor(args: array of String);
method on_window1_delete_event(aSender: Object; args: DeleteEventArgs);
end;
constructor MainForm(args: array of String);
begin
inherited constructor;
Application.Init();
with lXml := new Glade.XML(nil, 'GtkApplication1.Main.glade', 'window1', nil) do
lXml.Autoconnect(self);
Application.Run();
end;
method MainForm.on_window1_delete_event(aSender: Object; args: DeleteEventArgs);
begin
Application.Quit();
end;
ここには、ウィンドウの宣言(Widget 属性を指定したもの)と、コールバック ハンドラと、ものごとを動かすため
のコードが含まれています。Glade.XML クラスは、libglade XML ファイルをロードし、内部で定義されたユーザー
インターフェイスを作成します。このコンストラクタでは、指定されたアセンブリ内にリソースとして含まれる XML
ファイルを探します(最初の nil は現在のアセンブリを示します)。2 番目のパラメータはリソース名です。リソース
名がアセンブリ名で始まっていることに注意してください。3 番目のパラメータは、UI の構築を開始するウィジェット
ノードを表し、通常は最上位のウィンドウ名を指定します。そして、最後のパラメータは XML 変換ドメインで、通常
は nil を指定します。
XML オブジェクトが作成された直後に、クラスの参照を引数として Autoconnect() メソッドが呼び出されます。これ
により、Glade ファイルで定義されたシグナルがすべて、クラスで定義されたコールバック ハンドラに接続され、さ
らに、クラスで定義された Widget 属性を持つフィールドも、同じ名前を持つ UI 中のウィジェットに結び付けられま
す。
GTK# プロジェクト コードの微調整
このセットアップ コードの変更がいつも必要になるのには、いくつかの要因があります。XML ファイルはいつもメイ
ンの実行可能アセンブリの中に埋め込まれるため、私は簡単なコンストラクタのオーバーロードを行います。また、プ
ロジェクトを別名で保存したり、ビルド後に実行可能ファイルの名前を変更したりする可能性があるため、XML リソ
ース名からアセンブリ名のリテラル参照を取り除き(実行可能ファイルの名前を変更すると無効になるため)、動的に
算出するよう変更します。最後に、ウィジェット変数に何も代入されていないというコンパイラのエラーが出ないよう
(これらの変数はどのみち見えないところで代入されます)、宣言時に nil を代入します。この変更をすべて行うと、
コンストラクタは次のようになります。Glade の XML オブジェクトは変数を使わずに作成され、Autoconnect() を
直接呼び出した後、ガベージ コレクタに発見されるまでそのまま放置されます。
- 30 -
Delphi Prism を用いた Mono 開発
Brian Long
uses
...
System.Reflection;
type
MainForm = class(System.Object)
private
var AssemblyName: String;
{$REGION Glade Widgets}
var
[Widget] window1: Gtk.Window := nil;
{$ENDREGION}
...
end;
constructor MainForm(args: array of String);
begin
inherited constructor;
Application.Init();
AssemblyName := &Assembly.GetEntryAssembly().GetName().Name;
new Glade.XML(
AssemblyName + '.Main.glade', 'myGladeWindow').Autoconnect(self);
Application.Run();
end;
Glade デザイナで UI に組み込むウィジェットに合わせて、ウィジェット変数の宣言を必要なだけ追加してください。
メモ: 2009 年 8 月の Delphi Prism のリリースでは、GTK# プロジェクト テンプレートがさまざまな GTK# アセンブリ
をすべて参照していて、そのすべてについて Copy Local プロパティが True に設定されています。これは Windows に
は影響しませんが、Linux や OS X では問題になります。実際のところ、GTK# アセンブリは Mono ディストリビュー
ションに含まれているため、コピーする必要がありません。しかし残念なことに、それらのアセンブリを構成ファイル
なしで実行可能ファイルのディレクトリ内に含めると、必要なロード処理が Windows 以外のプラットフォームでうま
く動かなくなり、次のエラーが発生します。Unhandled Exception: System.DllNotFoundException: libglib-2.0-0.dll
(未処理の例外: System.DllNotFoundException: libglib-2.0-0.dll)
GTK# の例
GTK#(とその後で Monobjc)の動きを学ぶために、Web 上に散在するいくつかのチュートリアルで詳しく説明されて
いるサンプル プログラムの実装方法を、Mono ラッパーの GTK# と元の GTK+ の両方について検討してみましょう。
Delphi Prism と Mono を使って同じ結果を得るために必要な違いを知り、元のチュートリアルと手順を比較することが
できるため、これは非常に有益です。今後、他の目的の達成方法を学ぶために探した他のチュートリアルを見る場合に
も役立つでしょう。
GTK# の単純な例
最初に取り上げる例は、Paul Hogan(別名 pachjo)が作成した非常に単純な GTK# アプリケーションで、元のチュー
トリアルは http://www.box.net/public/aqu7jo4uby にあります。このチュートリアルでは、プログラミング言語に
Python を使用していて、さらに Glade については、GtkBuilder や Libglade ではなく GTK+ や GNOME というプロジ
- 31 -
Delphi Prism を用いた Mono 開発
Brian Long
ェクト種別を提供している古いバージョンを使用しているようです。しかし繰り返しますが、尋ねられたら Libglade
を使用してください。
指示に従って Glade で小さな UI を構築すると、次のようなものができます。
2 つのボタンの下に Label コントロールがあることに注意してください。シグナル ハンドラ名は、手で入力すること
もできますし、[Signals] タブのドロップダウン リストから適したものを選択することもできます。
この例は非常に単純で、両方のボタンの clicked シグナル ハンドラからラベルのキャプションを更新しています。次
の部分が重要なコードで、手で入力するシグナル ハンドラのメソッドが含まれています。
type
MainForm = class(System.Object)
private
var AssemblyName: String;
{$REGION Glade Widgets}
var
[Widget] theLabel: Gtk.Label := nil;
{$ENDREGION}
public
constructor(args: array of String);
method on_myGladeWindow_delete_event(aSender: Object; args: DeleteEventArgs);
method on_button1_clicked(sender: Object; args: EventArgs);
method on_button2_clicked(sender: Object; args: EventArgs);
end;
constructor MainForm(args: array of String);
begin
inherited constructor;
Application.Init();
AssemblyName := &Assembly.GetEntryAssembly().GetName().Name;
new Glade.XML(
AssemblyName + '.Main.glade', 'myGladeWindow').Autoconnect(self);
- 32 -
Delphi Prism を用いた Mono 開発
Brian Long
Application.Run();
end;
method MainForm.on_myGladeWindow_delete_event(aSender: Object; args: DeleteEventArgs);
begin
Application.Quit();
end;
method MainForm.on_button1_clicked(sender: Object; args: EventArgs);
begin
theLabel.Text := 'You pressed button1';
end;
method MainForm.on_button2_clicked(sender: Object; args: EventArgs);
begin
theLabel.Text := 'You pressed button2';
end;
ご覧のように、これは非常に簡単な、標準的な PME(プロパティ、メソッド、イベント)コーディングです。それほ
ど大変な思いをせずに Glade デザイナをお使いいただいていることを望みます。
ダイアログの例
GTK#
で 取 り 組 む 次 の チ ュ ー ト リ ア ル は 、 Tadej
Borovšak ( 別 名
tadeboro ) が 作 成 し
た http://tadeboro.blogspot.com/2009/04/gtkdialog-tutorial-part-2.html です。このチュートリアルでは、終了したい
かどうかをユーザーに確認するための確認ダイアログを構築し、バージョン情報ダイアログ ボックス テンプレートの
拡張方法や使い方を学びます。Glade デザイナで少し長い時間がかかるほかに、チュートリアルを進めるには次のよう
ないくつかのシグナル ハンドラが必要です。
method MainForm.on_mainWindow_delete_event(aSender: Object; args: DeleteEventArgs);
begin
new Glade.XML(AssemblyName + '.Main.glade', 'confirmQuitDialog').Autoconnect(self);
try
confirmQuitDialog.Icon := new Pixbuf(nil, AssemblyName + '.Properties.Tux.png');
args.RetVal := confirmQuitDialog.Run <> 1;
confirmQuitDialog.Hide;
finally
confirmQuitDialog.Destroy;
confirmQuitDialog := nil;
end;
end;
method MainForm.on_aboutButton_clicked(aSender: Object; args: EventArgs);
begin
new Glade.XML(AssemblyName + '.Main.glade', 'aboutDialog').Autoconnect(self);
try
aboutDialog.Icon := new Pixbuf(nil, AssemblyName + '.Properties.Tux.png');
aboutDialog.Logo := new Pixbuf(nil, AssemblyName + '.PrismLogo.png');
aboutDialog.Run;
aboutDialog.Hide;
finally
aboutDialog.Destroy;
aboutDialog := nil;
end;
end;
- 33 -
Delphi Prism を用いた Mono 開発
Brian Long
私がプロジェクトの Properties ディレクトリに PNG 画像を追加し、それをアプリケーション内の各ウィンドウ用のア
イコンとして使用していることに注意してください(これは Linux と Windows で表示されます)。さらに、アプリケ
ーションに通常のアイコン ファイルも追加して、Windows で .exe ファイルを調べるときにアイコンが表示されるよう
にしています。
既製のバージョン情報ダイアログ ボックスのテンプレートは、自動的に著作権表示画面を作成できるだけの柔軟性を
持っていることがわかります。
メモ: Glade の自動接続メカニズムは、変数を UI ウィジェットに接続する役割をよく果たしていますが、ときどき失
敗する場合があります。たとえば、バージョン情報ダイアログ テンプレートには dialog-vbox3 という内部 VBox が
あるので、そこにリソース ファイルから画像をロードしたいとします。しかし、画像コントロール サーフェスを変数
として持つという通常のアプローチは失敗します。おそらく、VBox が内部コントロールであることが理由です。
TreeView の例
次のデモ アプリケーションは、GTK+ の TreeView コントロールを使うものです。残念なことに、Glade デザイナ
(もっと具体的に言うと libglade サポート)ではツリー ビューを使った作業をサポートしていないため(GtkBuilder
の方ではサポートしていますが)、コードで対応する必要があります。TreeView が興味深いのは、UI と表示対象デ
ータを分離せざるを得ない点です。データを TreeStore(TreeModel インターフェイスを実装しています)にセッ
トアップし、その後それをコンストラクタまたは Model プロパティを使って TreeView に渡します。また、GTK+ の
TreeView では、階層を表示するほかに、情報の列もサポートしています。これは WinForms の TreeView コントロ
ールではサポートされていないものです。
TreeView クラスのドキュメントは Mono ドキュメント ライブラリ(http://tinyurl.com/Gtk-TreeView)に含まれてい
て、そこには、このクラスを使う 1 つの簡単な例と、より複雑な例がいくつかあり、いずれも C# で書かれています。
- 34 -
Delphi Prism を用いた Mono 開発
Brian Long
そのため、Delphi Prism への移植が非常に簡単になっています。通常の UI 構築コードをすべて無視して、それを
Glade デザイナで行った場合にはさらに簡単です。コードの主要部分は次のようになります。
type
MainForm = class(System.Object)
private
var AssemblyName: String;
{$REGION Glade Widgets}
var
[Widget] mainWindow: Gtk.Window := nil;
[Widget] scrolledWindow: ScrolledWindow := nil;
[Widget] updateDialog: Gtk.Dialog := nil;
[Widget] dialogLabel: Label := nil;
{$ENDREGION}
store: TreeStore;
method UpdateProgress(format: String; params args: Array of object);
method ProcessType(parent: TreeIter; t: &Type);
method ProcessAssembly(parent: TreeIter; asm: &Assembly);
method PopulateStore;
public
constructor(args: array of String);
method on_window1_delete_event(aSender: Object; args: DeleteEventArgs);
method on_updateDialog_response(aSender: Object; args: ResponseArgs);
end;
...
implementation
uses
System.Security;
constructor MainForm(args: array of String);
begin
inherited constructor;
AssemblyName := &Assembly.GetEntryAssembly().GetName().Name;
Application.Init();
PopulateStore;
new Glade.XML(AssemblyName + '.Main.glade', 'mainWindow').Autoconnect(self);
mainWindow.Icon := new Pixbuf(nil, AssemblyName + '.Properties.Tree.png');
var tv: TreeView := new TreeView(store);
tv.HeadersVisible := True;
tv.AppendColumn('Name', new CellRendererText(), 'text', 0);
tv.AppendColumn('Type', new CellRendererText(), 'text', 1);
scrolledWindow.Add(tv);
updateDialog.Destroy;
updateDialog := nil;
mainWindow.ShowAll();
Application.Run();
end;
method MainForm.on_window1_delete_event(aSender: Object; args: DeleteEventArgs);
begin
Application.Quit();
end;
method MainForm.PopulateStore;
begin
if store <> nil then
Exit;
store := new TreeStore(typeof(string), typeof(string));
for each asm: &Assembly in AppDomain.CurrentDomain.GetAssemblies() do
begin
var asmName: String := asm.GetName().Name;
UpdateProgress('Loading {0}', asmName);
- 35 -
Delphi Prism を用いた Mono 開発
Brian Long
ProcessAssembly(store.AppendValues(asmName, 'Assembly'), asm)
end;
end;
method MainForm.UpdateProgress(format: String; params args: Array of object);
begin
var Text := string.Format(format, args);
if updateDialog = nil then
begin
new Glade.XML(AssemblyName + '.Main.glade', 'updateDialog').Autoconnect(self);
updateDialog.Icon := new Pixbuf(nil, AssemblyName + '.Properties.Tree.png');
dialogLabel.Text := Text;
updateDialog.ShowAll();
end
else
begin
dialogLabel.Text := Text;
while Application.EventsPending() do
Application.RunIteration()
end;
end;
method MainForm.ProcessAssembly(parent: TreeIter; asm: &Assembly);
begin
var asmName: String := asm.GetName().Name;
for each t: &Type in asm.GetTypes() do
begin
UpdateProgress('Loading from {0}:'#10'{1}', asmName, t.ToString());
ProcessType(store.AppendValues(parent, t.Name, t.ToString()), t);
end;
end;
method MainForm.ProcessType(parent: TreeIter; t: &Type);
begin
for each mi: MemberInfo in t.GetMembers() do
store.AppendValues(parent, mi.Name, mi.ToString())
end;
method MainForm.on_updateDialog_response(aSender: Object; args: ResponseArgs);
begin
//Application.Quit();
System.Environment.Exit(0);
end;
ア プ リ ケ ー シ ョ ン が 起 動 す る と ( MainForm コ ン ス ト ラ ク タ ) 、 メ イ ン ウ ィ ン ド ウ の 処 理 が 始 ま る 前 に
PopulateStore() で TreeStore がセットアップされます。このメソッドでは、プロセス内のすべてのアセンブリと、
アセンブリ内のすべての型、型の中のすべてのメンバに対して反復処理を行い、ヘルパ メソッドの
ProcessAssembly() および ProcessType() を使ってそれぞれの項目の情報をストアに追加します。さらに、その間
の各ステップでは UpdateProgress() で何が行われているかをユーザーに知らせているため、画面上に(ウィンドウ
アイコンの付いた)進行状況ダイアログを表示し、渡された進行状況メッセージをラベルに反映することができます。
- 36 -
Delphi Prism を用いた Mono 開発
Brian Long
この例の拡張版には、きれいな画像や著作権表示などがセットアップされた GTK+ のバージョン情報ダイアログと、
反復処理の対象に追加するアセンブリをユーザーが選択してツリー ビューに追加するためのメニューが含まれていま
す 。 こ れ ら を 追 加 す る コ ー ド は あ ま り 勉 強 に な る も の で は あ り ま せ ん が 、 http://www.monoproject.com/GtkSharpNotificationIcon の サ ンプル コードを参考に、システムに通知アイコンを追加するため の
StatusIcon ウィジェットを使用しています。例によってコードの移植は簡単ですが、ここでは問題点をいくつか修正
しています。
constructor MainForm(args: array of String);
begin
...
//Set up a tray icon
trayIcon := new Statusicon(new PixBuf(nil, AssemblyName + '.Properties.Tree.png'));
trayIcon.Visible := True;
//Show/hide the window when the icon is clicked
trayIcon.Activate += method begin mainWindow.Visible := not mainWindow.Visible end;
//Set up a context menu for the icon
trayIcon.PopupMenu += OnTrayIconPopup;
trayIcon.Tooltip := 'TreeView Demo Icon';
//Start the app proper
mainWindow.ShowAll();
Application.Run();
end;
method MainForm.OnTrayIconPopup(sender: Object; args: EventArgs);
begin
//Ensure we don't get lots of popups if we keep clicking in different spots
if popupMenu = nil then
begin
popupMenu := new menu();
var menuItemQuit: ImageMenuItem := new ImageMenuItem('Quit');
menuItemQuit.Image := new Gtk.Image(Stock.Quit, IconSize.Menu);
popupMenu.Add(menuItemQuit);
//Quit when the menu item is clicked
menuItemQuit.Activated += method begin ExitNicely end;
end;
popupMenu.ShowAll();
popupMenu.Popup();
end;
method MainForm.ExitNicely;
begin
if trayIcon <> nil then
begin
trayIcon.Visible := False;
Application.Quit;
end;
end;
- 37 -
Delphi Prism を用いた Mono 開発
Brian Long
ポップアップ メニューを複数作成しないためのチェックに着目してください。これがなければ、通知アイコン上で複
数回右クリックすると、そのたびに新しいポップアップ メニューが表示されてしまいます。また、終了コードを拡張
して、通知領域から通知アイコンを明示的に削除し、アイコンの幽霊が残らないようにしています。
メモ: 上記のコードでは、Delphi Prism の構文拡張を利用しています。+= 演算子で新しいイベント ハンドラを追加
することができます。Activate イベントや Activated イベントには、匿名デリゲート(インラインでメソッド本体を
宣言したもの)を追加しています。
これでこの例は、先に取り上げた WinForms の例と同じ機能になりました。Windows と Linux で正しく動作し、状態
アイコンも今回は意図どおりに動きます。左クリックで StatusIcon の Activate シグナルが、右クリックで
PopupMenu シグナルがトリガされます。
- 38 -
Delphi Prism を用いた Mono 開発
Brian Long
OS X では、アプリケーションはそれほどうまく動きません。ウィンドウはサイズを変更すると画面上を跳ね回ります
し、StatusIcon シグナルの生成も異なっています。左クリックで PopupMenu シグナルがトリガされ、Activate シ
グナルはまったくトリガされないようです。このように、通知アイコン コントロールに関してはプラットフォームの
違いが残っていますが、それ以外では、GTK# はクロスプラットフォーム UI をうまく実現しています。
GTK# バンドル形式実行可能ファイル
こういった GTK# アプリケーションをバンドル形式の実行可能ファイルに変換して、mono コマンドを使わずに起動
できるようにしましょう。この方法は、Linux で使用するつもりのアプリケーションや、Mac のコンソール アプリケー
ションに向いています。GUI アプリケーションでは、この次に説明するアプリケーション バンドルを使用します。
メモ: mkbundle は、それが呼び出されたプラットフォームに適したバンドル形式実行可能ファイルを生成します。
実行している OS によって、異なるコードが生成されます。
メモ: mkbundle を実行するときには、ディレクトリ パスに # が含まれないことを確認してください。含まれている
と プ ロ セ ス が 失 敗 し ま す 。 た と え ば 、 対 象 の
Mono
ア プ リ ケ ー シ ョ ン が
~/dev/mono/src/gtk#/TreeViewDemo2/bin/Debug に あ り 、 そ の デ ィ レ ク ト リ か ら .exe フ ァ イ ル に 対 し て
mkbundle を 実 行 す る と 、 # が コ マ ン ド 拡 張 機 能 の コ メ ン ト 文 字 だ と 解 釈 さ れ 、 Unhandled Exception:
System.IO.FileNotFoundException: Could not load file or assembly '/Users/brian/dev/mono/src/gtk' or one of its
dependencies. The system cannot find the file specified(未処理の例外: System.IO.FileNotFoundException: ファ
- 39 -
Delphi Prism を用いた Mono 開発
Brian Long
イルまたはアセンブリ '/Users/brian/dev/mono/src/gtk' またはその依存ファイルをロードできません。システムは指
定されたファイルを見つけられません)というエラーが発生します。
Linux での処理は非常にうまくいきます。依存関係を洗い出して treeviewdemo という単純な名前のプログラムを作
成するよう mkbundle に指示すれば、それで終わりです。
先に述べたように OS X での重要性は高くありませんが、OS X 10.6 ではこの作業はあまりスムーズに進みません。上
のスクリーンショットを見ると、mkbundle が、GNU アセンブラの as と GNU C コンパイラの cc の他に、さまざま
なライブラリへのリンクを成功させるためのコンパイラ スイッチを取得するツールである pkg-config を呼び出して
いることがわかります。OS X では(少なくとも私の Mac Pro では)、このアセンブラとリンカは、デフォルトで、生
成されたアセンブリや C ソース ファイルと互換性のないアーキテクチャになるようです。また、pkg-config はパス
上 の デ ィ レ ク ト リ に 含 ま れ て い ま せ ん し ( Mono で は
pkg-config を パ ス 上 で は な い
/Library/Frameworks/Mono.framework/Commands というディレクトリに入れて提供していますが)、必須のライブ
ラリで使用するデータ ファイルを見つけることができません(構成ファイルがなかったり、Glib 開発者フレームワー
クがインストールされていないかったりするためです)。
幸い、これはすべて、GTK+ for OS X(http://www.gtk-osx.org)をインストールして、環境変数をいくつか変更すると
解決できます。私は自分の ~/.profile ファイル(最初は存在しませんでした)を編集して、新しいセッションごとに事
前設定されるようにしました。ファイルに追加したのは次の内容です。
# scripts is where my own scripts are to be found
# Mono commands path is to allow pkg-config to be found
- 40 -
Delphi Prism を用いた Mono 開発
Brian Long
Export PATH=/Library/Frameworks/GLib.framework/Resources/dev/lib/
pkgconfig:/Library/Frameworks/Mono.framework/Commands:$PATH
# To allow Mono mkbundle to work
export AS="as -arch i386"
export CC="cc -arch i386"
export PKG_CONFIG_PATH=/Library/Frameworks/Mono.framework/Versions/Current/lib/pkgconfig
自分の .profile を追加または変更したら、次のコマンドで即座にシェルを再ロードし、新しいシェルを起動する手間
を省くことができます。
. ~/.profile
これで、実行可能ファイルのバンドルを OS X でも問題なく作成できるはずです。
GTK# Mac OS X のアプリケーション バンドル
OS X への配置を正しく行うには、GTK# アプリケーションのアプリケーション バンドルを作成する時に何をする必要
があるかを知っておかなければなりません。この例では、現在手元にあるのは Mono 実行可能ファイルです。見苦し
くないアプリケーション バンドルを作成するには Mac アイコン(.icns ファイル)が必要なので、私は Tree.icns とい
うファイルを作成しました。次に示すのは macpack の呼び出し例です。
macpack -n TreeViewDemo -i Tree.icns -m console TreeViewDemo2.exe
-i スイッチでアイコン ファイルを指定しているのは見ればわかりますが、他の 2 つのスイッチは説明が必要でしょう。
-n では、アプリケーション バンドルを選択したときに Finder に表示されるアプリケーション名(- 28 - ページのスク
リーンショットを参照)を指定します。これは、タスク スイッチャでも使われますし、実行時に Dock に表示されるア
プリケーション アイコンのツールチップにもなります。-m では、生成されたバンドル スクリプトで正しい設定を行
えるように、アプリケーションの種類を選択することができます。選択可能な値は winforms、cocoa、console で
すが、WinForms や Cocoa の場合に設定しなければならない環境変数はないため、console でよいでしょう。
これは手動でアプリケーション バンドルを生成する方法であり、コンソール アプリケーションや OS X の GTK# アプ
リケーションには現在のところこれで十分です。WinForms アプリケーション(既に説明しました)や Cocoa# アプリ
ケーションや Monobjc アプリケーションの場合には、プロジェクト ファイルから Delphi Prism MacPack MSBuild タス
クを呼び出して、アプリケーション バンドルを自動的に生成するよう、プロジェクトをセットアップします。
好みにより、手動で macpack を呼び出すのをやめ、GTK# プロジェクト ファイル(XML 形式の .oxygene ファイル)
を変更して IDE のアプリケーション バンドル生成機能を使用することもできます。好みのテキスト エディタにプロジ
ェクト ファイルをロードしてもかまいませんし、以下の手順に沿って Visual Studio 内でプロジェクト ファイルを編集
してもかまいません。
- 41 -
Delphi Prism を用いた Mono 開発
Brian Long
1. プロジェクト/ソリューション内の変更を保存したことを確認します。
2. [ソリューション エクスプローラー] ウィンドウの階層構造でプロジェクト ノードを右クリックし、[プロジェ
クトのアンロード] を選択します(これでプロジェクト ノードに利用不可のマークが付きます)。
3. プロジェクト ノードを右クリックし、[編集 <プロジェクト名>.oxygene] を選択して XML プロジェクト
ファイルを Visual Studio エディタにロードします。
4. ファイルに対して必要な編集を行います。
5. エディタの .oxygene ファイルを閉じます。
6. プロジェクト ノードを右クリックし、[プロジェクトの再読み込み] を選択します。
プ ロ ジ ェクト ファイ ルの 主 要 素 PropertyGroup に入れ子にして、 MacPackMode 要素と、必要であれば
MacIconFile 要素を追加します。たとえば次のようなものです。
<MacPackMode>Console</MacPackMode>
<MacIconFile>Properties\Tree.icns</MacIconFile>
また、IDE に含まれる MacPack 機能を有効化するための MSBuild ターゲット ファイルをインポートする必要もありま
す。これは、プロジェクト ファイルの終わりの方で Project 要素に入れ子にして、他の RemObjects インポート要素の
後に追加することができます。
<Import Project="$(MSBuildExtensionsPath)\RemObjects Software\Oxygene\
RemObjects.Oxygene.Cocoa.targets" />
メモ: アプリケーション バンドルがあると、アプリケーションの移動やインストールが簡単になります。また、アプ
リケーションを Dock にドラッグすることで、常に利用可能なショートカットを手近に置いておけます。
メモ: アプリケーション バンドルを IDE で生成する場合には、バンドル スクリプト ファイルの実行ビットが立って
いることを確認する必要があります。Mac が共有している Windows ドライブにビルド結果を出力しているなら、これ
は自動的に行われます。それ以外の場合には、chmod を使って次のように手動で設定しなければなりません。
chmod +x TreeViewDemo.app/Contents/MacOS/TreeViewDemo
Cocoa#
Cocoa#(http://cocoa-sharp.com)は、Mono で Cocoa アプリケーションを構築できるように設計されたいくつかの
プロジェクトの 1 つです。しかし、プロジェクトの動きはあまり速いとは言えず、最初からいくつもの問題が待ち構え
ています。
2009 年 8 月から出荷されている Delphi Prism のバージョンには、OS X 10.4(Tiger)用と 10.5(Leopard)用の 2 つの
Cocoa# プロジェクト テンプレートが含まれています。残念ながら、どちらも OS X 10.6(Snow Leopard)では正しく
動かないようです。10.5 でも、Leopard プロジェクトには、ツールバーの [Colors] ボタンでアプリケーションがクラッ
シュするという、非常に目立つ問題があります。
- 42 -
Delphi Prism を用いた Mono 開発
Brian Long
これは、Delphi Prism の制限事項ではなく、Cocoa# ライブラリの問題です。これを始めとした問題はしばらく前に
Cocoa# の保守担当者に報告されていますが、修正はまだ行われていません。そのため、Cocoa# のプロジェクト テン
プレートは、Delphi Prism の次回のリリースでは取り除かれる予定です。
まとめると、Cocoa# は、理論は素晴らしいけれども実装がお粗末なアイデアなので、忘れた方が良いということです。
Monobjc
ここでは、少し時間をかけて実質的な Mac 開発を行います。Monobjc
(http://www.monobjc.net)は、UI の主要ライブラリである Cocoa を含むさまざまな
Apple Objective-C ライブラリに Mono アプリケーションを接続するためのブリッジ技術
です。API ドキュメントは http://api.monobjc.net にありますが、もちろん元の Cocoa の
ドキュメントも手元に置いておくと便利です。Web 上
の http://developer.apple.com/mac/library/navigation で提供されているほかに、Xcode ツ
ールのおかげで
/Developer/Documentation/DocSets/com.apple.adc.documentation.AppleSnowLeopard.CoreReference.docset
にローカル コピーがあります。
Monobjc は、Mono の一部ではなく、Mono 上で構築されたプロジェクトです。Delphi Prism の 2009 年 8 月バージョ
ンには Monobjc バージョン 2.0.404.0 が同梱されていますが(C:\Program Files\Monobjc-2.0.404.0 にインストール
されています)、執筆時点における最新のリリース済みバージョンは 2.0.413.0 であり、最新のテスト バージョンは
2.0.436.0 です。開発用にライブラリの最新バージョンを入手してください。
ではさっそく取り掛かりましょう。Delphi Prism で Monobjc プロジェクト テンプレートを使って新しい Monobjc ア
プリケーションを作成します。プロジェクトを作成してビルドした後、自動生成されたアプリケーション バンドルを
Finder で探し、その新しいアプリケーションを起動して何が行われるかを試しましょう。
メモ: Finder でアプリケーションを起動しようとしたときに、アプリケーションをビルドした先のディレクトリの場
所によっては、起動が失敗する可能性があります。共有して Mac にマウントされている Windows ドライブにあるなら
問題はありませんが、そうでない場合には、- 16 - ページの「スクリプト」のセクションで説明したように実行ビット
を立てる必要があります。ただしこの例では、スクリプトはアプリケーション バンドル内に入れ子になったディレク
トリにバンドルされています。たとえば MonobjcApplication1 というプロジェクトの場合、アプリケーション バンド
ル ディレクトリを含んでいるディレクトリ(プロジェクト ディレクトリの下の bin/Debug ディレクトリ)に移動し
て次のコマンドを実行すると、問題を修正することができます。
chmod +x MonobjcApplication1.app/Contents/MacOS/MonobjcApplication1
メモ: Finder や open でアプリケーションを実行するために実行ビットを立ててばかりいると感じるのであれば、別
の方法を採用することも可能です。バンドル スクリプトを実行するようシェルに対して明示的に指示するなら、実行
- 43 -
Delphi Prism を用いた Mono 開発
Brian Long
ビットは必要ありません。たとえば MonobjcApp というプロジェクトの場合、プロジェクト ディレクトリを含んでい
るディレクトリに cd して次のコマンドを実行すると、アプリケーションが起動します。
bash MonobjcApp/bin/Debug/MonobjcApp.app/Contents/MacOS/MonobjcApp
メモ: Delphi Prism の Monobjc プロジェクト テンプレートには、すべての Monobjc アセンブリの参照が含まれてい
て、そのすべてについて Copy Local が True に設定されています。そうすることで、起こりうる事態をすべて網羅し、
Monobjc ブリッジ ライブラリ全体にアクセスできるようになっています。つまり、Cocoa や、OpenGL、QuickTime、
PDF フレームワークだけでなく、AddressBook や、Image Kit、Security、Web Kit フレームワークにもアクセスできる
ということです。しかし、標準的な Cocoa アプリケーションを構築しようとしているだけなら、余分なフレームワー
ク アセンブリすべてをアプリケーション ディレクトリに配置したくないかもしれません。ほとんどの Cocoa アプリケ
ーションでは、Monobjc.dll と Monobjc.Cocoa.dll 以外の Monobjc 参照をすべて削除することができます。
これで、Finder から、またはターミナル ウィンドウで open を使って、アプリケーションを起動できるはずです。ア
プリケーションの UI にはツールバーがあり、そこにはカラー パネルを開くための [Colors] ボタンと、フォント パネル
を開くための [Fonts] ボタンがあります。しかし、これらのコントロールで編集するものは今のところ何もありません。
ツールバーには無効化された [Print] ボタンと [Customize] ボタンがあり、それなりの項目を持つメニュー システムがあ
って、メニュー項目の中には実際に何かを引き起こすものもありますが([About]、[Quit]、[Page Setup]、[Print]
など)、大まかに言うとこのアプリケーションに実質的な機能はありません。
.nib ファイル
コントロールを追加して UI を構築するには、Apple の Interface Builder を使ってプロジェクトの .nib ファイル(これ
自体がファイル バンドルです)を編集する必要があります。プロジェクト テンプレートでは、この .nib バンドルは
Interface.nib という名前になっていて、その中には designable.nib および keyedobjects.nib という 2 つのファイルが含
まれています。
.nib というのは NeXTSTEP の頃からある拡張子で、NeXT Interface Builder に由来しています。元々 .nib ファイルはバ
イナリでしたが、最近では XML として格納されることもあり、場合によっては .xib という拡張子が付けられることも
あります。プロジェクト テンプレートを使った場合のセットアップでは、designable.nib は XML、keyedobject.nib は
バイナリです。アプリケーションをビルドした後、.nib バンドルはアプリケーション バンドルの Resources ディレク
トリに格納されます。
Interface Builder
Interface Builder は、Finder で探すと /Developer/Applications/Interface Builder にあります。.nib バンドルを開くと、
Interface Builder のドキュメント ウィンドウ([Window|Document])に表示されます。サンプル プロジェクトの .nib
バンドルの場合にはこのようになります。
- 44 -
Delphi Prism を用いた Mono 開発
Brian Long
ウィンドウ(NSWindow オブジェクト)と一緒に、組み
込みのツールバー(NSToolbar)と、ウィンドウのそれ以
外の部分を占めるコンテンツ ビュー(NSView)が、リス
トに表示されています。これらは、インスペクタ(表示さ
れていない場合には [Tools] メニューから開くことができま
す)のさまざまなページを使って編集することができます。
メニュー(NSMenu )もリストに表示されています。これ
をドキュメント ウィンドウでダブルクリックすると、ビジ
ュアルなメニュー デザイナで編集することができます。
Cocoa アプリケーションには複数の .nib ファイルを含める
ことができますが、そのうちの 1 つにアプリケーション
(NSApplication)を表すオブジェクトが含まれます。それもここに表示されています。
メモ: すべての Cocoa クラスに付いている NS という接頭辞は昔からの命名方法であり、NeXTSTEP の時代を思い出
させます。
ドキュメント ウィンドウでもう 1 つ重要な項目は、メイン ウィン
ドウ コントローラです。このホワイトペーパーを Cocoa の完全な
チュートリアルにするつもりはありませんが、Cocoa ではある程度
MVC(Model-View-Controller)デザイン パターンが強制されると
知っておくことが大切です。ビューは、Interface Builder を使って
設計して .nib ファイルに保存したもので表されます。モデルはコー
ドに表されます(コードにモデルの役割をさせることにした場合)。
コントローラは、この 2 つを結び付けるもので、.nib ファイル内に
置かれ、コード内で表現されます。これについてはこの後のいくつ
かのセクションで説明します。
UI を構築するためにウィンドウにコントロールを追加する際には、
[Library] ウィンドウ([Tools|Library])を使用します。UI を設計
するときには、Apple の『Human Interface Guidelines』のさまざ
ま な 面 を 考 慮 し な け れ ば な り ま せ ん 。 こ の ガ イ ドラ イ ン は 、
Interface Builder で [Help|Human Interface Guidelines] を選択
すると見ることができます。
コントローラ オブジェクトは、通常、Cocoa のデフォルト基底クラ
ス NSObject を継承したクラスとして定義されます。Interface
Builder では、[Library] ウィンドウの [Classes] タブで下位クラスをセットアップすることができます。クラス リストで
- 45 -
Delphi Prism を用いた Mono 開発
Brian Long
基底クラスを選択し、右クリックして [New Subclass...] を選択すると、新しいクラスがリストに追加されて使用でき
るようになります。テンプレート プロジェクト .nib に含まれる MainWindowController クラスは、これと同様に、
NSObject を継承するクラスとして作成されています。このカスタム クラスは、[Library] ウィンドウの [Classes] タブ
でクラス リストを探すと見つかります。
コントローラの準備ができたら、次に、アプリケーション コードや UI とやり取りできるようにコネクションを追加し
ます。ここではアウトレット コネクションを追加します。これは最終的にコントローラ クラスのソース内のインスタ
ンス変数として表現されるもので、このコネクションを使ってコントローラを UI コントロールに結び付けることがで
きます。それにより、コードでは、UI に含まれる重要なコントロールやビューと対話することができます。これは対
話したいコントロールを表す変数を宣言するのと似ています。また、アクション コネクションも追加できます。UI コ
ントロールには、ユーザーがその UI コントロールと対話したときに送信されるいくかのアクション メッセージが備わ
っています(メッセージの送信は、メソッドの呼び出しと似たものであると考えることかできます)。コードでそのア
クションに応答できるよう、アクション メソッドは、アクションを制御するために接続可能なコントロールの中に定
義します。アクション メソッドは本質的にイベント ハンドラです。アウトレットとアクションは、後の例でセットア
ップします。
単純なテキスト エディタ
まず、生成されたテンプレート プロジェクトの Interface.nib を編集し、何か有益な動作をさせることにします。この
最初の例では、メニュー項目をいくつか編集します。メニュー エディタでメニュー項目のテキストを調べると、アプ
リケーション名のプレースホルダとして NewApplication が何箇所かに含まれていることがわかります。メイン アプ
リ ケ ー シ ョ ン メ ニ ュ ー ( こ れ も [NewApplication] と い う 名 前 に な っ て い ま す ) の 下 に は 、 [About
NewApplication]、[Hide NewApplication]、[Quit NewApplication] の 3 箇所にあります。[Help] メニューの下
にも 1 つ、[NewApplication Help] があります。これらの名前は、メニュー項目をダブルクリックして編集すること
も、選択してインスペクタの [Attributes] ペインでテキストを編集することもできます。
メモ: 太字のメニュー項目 NewApplication のテキストを編集してもほとんど意味はありません。実際には、実行
時にアプリケーション名を基に設定されるためです。
次に、ウィンドウの主要部分に Text View コントロールを追加します。これは、[Objects] タブの [Cocoa|Views &
Cells|Inputs & Values] の下にありますが、たいていは [Library] ウィンドウの一番下にあるフィルタ ボックスに名前の
一部を入力した方が早いでしょう。コントロールを追加したらそのサイズを変更することができますが、その際、
『Human Interface Guidelines』に沿った適切なサイズにするためのサイズ変更ガイドが表示されます。
コントロールをさらにカスタマイズするには、まずドキュメント ウィンドウで選択しなければなりません。インスペ
クタの [Size] タブで、[Autosizing] セクションの赤いバネや支柱を使って、自動位置管理(ウィンドウのサイズを変更
するとテキスト ビューのサイズも合わせて変更される)をセットアップすることができます。選択した設定の効果が
- 46 -
Delphi Prism を用いた Mono 開発
Brian Long
アニメーションで表示されるため、希望する効果を簡単に設定できるはずです。インスペクタの [Attributes] ページで
は、テキスト ビューの枠線やスクロール バーをカスタマイズすることができます。
プロジェクトに含まれる App.icns を置き換えればアプリケーション アイコンを変更できることを忘れないでください。
異なる名前のファイルを使用する場合には、- 27 - ページの「Mac のアプリケーション アイコン」のセクションで説明
したように、プロジェクト ファイルの MacIconFile のエントリを変更してください。
これでアプリケーションを動かすことができ、機能の豊富なテキスト エディタができたことがわかります(ただしフ
ァイルをロードしたり保存する機能はありませんが)。
このエディタを拡充するのに手間はほとんどかかりません。プロジェクト テンプレートで追加されたサンプル メニュ
ーには [Format] メニューが含まれていませんが、ライブラリには含まれています。ドキュメント ウィンドウで
[MainMenu] を展開して、適切な場所にドラッグすれば追加できます。これで、フォント パネルやカラー パネルを開く
メニュー項目を始め、テキスト ビューの一番上に表示される非常に機能的なルーラーなどの機能を起動するメニュー
項目が追加されます。
- 47 -
Delphi Prism を用いた Mono 開発
Brian Long
Monobjc と Snow Leopard
OS X Snow Leopard で ス レ ッ ド ロ ー カ ル の ガ ベ ー ジ
コレクションが導入されたおかげで(詳細
は http://www.sealiesoftware.com/blog/archive/2009/08/28/objc_explain_Thread-local_garbage_collection.html を参照
してください)、Monobjc ブリッジ ライブラリの低レベルな部分で想定していた事柄が安全でなくなりました。つま
り、一定の時間が経過すると、Mono ガベージ コレクション コードの途中でアプリケーションがハングしたりクラッ
シュします(https://bugzilla.novell.com/show_bug.cgi?id=537764 を参照してください)。Monobjc の作者である
Laurent Etiemble は、この問題を解析し、新しいビルドで修正しています(このホワイトペーパーの執筆時点で、この
新しいビルド 2.0.436.0 はテスト中です)。
この問題を回避するために、新バージョンの Monobjc では 2 つのネイティブ共有ライブラリ(Objective-C ランタイ
ムのそれぞれのバージョンに 1 つずつ)を導入しています。このライブラリは、アプリケーション バンドル中の、実
行スクリプトを含むディレクトリにコピーする必要があります。しかし、このディレクトリはライブラリ検索パスに含
まれないため、問題を解決するにはスクリプト自体も変更しなければなりません。近く予定されている Delphi Prism
のアップデートにはこれらのライブラリが含まれていて、必要な更新が隠されていますが、それまでの間は以下の手順
で、Snow Leopard の Monobjc アプリケーションに関するスレッドローカルのガベージ コレクションの問題を回避す
る必要があります。
Monobjc の最新版を http://monobjc.net のダウンロード リンクからダウンロードし、C:\Program Files ディレク
トリにある現行バージョンの隣に置きます(ダウンロードされるファイルは gzip で圧縮された tar 形式の .tar.gz ファ
イルなので、Mac では Stuffit Expander サポートを使って、PC では WinRar などの適切な Windows ツールを使って解
凍する必要があります)。私のインストール ディレクトリは C:\Program Files\Monobjc-2.0.436.0 ですが、以下では
$(MONOBJC) と表記します。
1. libmonobjc.1.dylib および libmonobjc.2.dylib の 2 つの共有ライブラリが $(MONOBJC)\dist に含まれているこ
とを確認します。
2. 汎用の実行スクリプト AppLoader が $(MONOBJC)\src\tools\NAnt.MonobjcTasks\Embedded に含まれて
いることを確認します。
3.
次のビルド後イベントをプロジェクトに追加します(プロジェクト プロパティ ウィンドウの [ビルド イベン
ト] タブ)。ここでは改行されていますが、実際には 2 つの copy コマンドです。
copy"$(ProgramFiles)\Monobjc-2.0.436.0\dist\libmonobjc.*"

$(TargetDir)$(TargetName).app\Contents\MacOS
copy
"$(ProgramFiles)\Monobjc-2.0.436.0\src\tools\NAnt.MonobjcTasks\Embedded

\AppLoader" $(TargetDir)$(TargetName).app\Contents\MacOS\$(TargetName)
4. Monobjc アプリケーションを Delphi Prism で普段どおりにビルドします。
5. 繰り返しますが、Delphi Prism の次のアップデートでは、この作業は意識しなくても自動的に行われます。
- 48 -
Delphi Prism を用いた Mono 開発
Brian Long
正しい閉じ方
OS X ツールにおいてすら見逃されているようなのですが、Apple の『Human Interface Guidelines』の、具体的には
シングル ウィンドウ アプリケーションの部分が、最近変更されました。その具体的なガイドラインは、「Part III: The
Aqua Interface, Windows, Window Behavior, Closing Windows」にあります。そこには、シングル ウィンドウ アプ
リケーションのウィンドウを閉じるときにはアプリケーションを終了するべきだと書かれています。これは、すべての
ウィンドウを閉じてもアプリケーションは画面上のメニューで動き続けるという、Mac アプリケーションの通常の動
作と異なっていることに注意してください。通常の動作では同じアプリケーションで新しいウィンドウを作成すること
ができますし、この動作は Windows MDI コンテナですべての子ウィンドウが閉じられた場合とよく似ていますが、こ
のサンプルのテキスト エディタのようなシングル ウィンドウ アプリケーションでは意味がありません。
Apple のガイドラインを守るには、アプリケーション オブジェクト(Interface Builder のドキュメント
ウィンドウにある NSApplication オブジェクト)のデリゲートをセットアップしてください。このデリゲート
を使うことで、選択されたメッセージに対してアプリケーション オブジェクトの代わりに応答し、シャットダ
ウンの方法を制御することができます。NSApplication のデリケート メッセージについては、Monobjc API
ドキュメント(http://api.monobjc.net/html/T_Monobjc_Cocoa_NSApplication.html イベント ハ
ンドラの型定義へのリンクも含まれているため、どのパラメータを定義しなければならないかが正確にわかりま
す)でも Cocoa ドキュメント
(http://developer.apple.com/mac/library/documentation/Cocoa/Reference/NSApplication
Delegate_Protocol/Reference/Reference.html)でも調べることができます。
ウ ィ ン ド ウ が 閉 じ ら れ た と き に ア プ リ ケ ー シ ョ ンが 消 滅 し な け れ ばな ら な い こと を 示 す に は、 少 な く と も
applicationShouldTerminateAfterLastWindowClosed: メッセージを処理する必要があります。しかし、このア
プリケーションはテキスト エディタなので(保存やロードはできませんが)、applicationShouldTerminate: の処
理も行って、アプリケーションを終了するべきどうかをユーザーに確認する必要があります。同じ理由で、ウィンドウ
オブジェクトのデリゲートをセットアップして windowShouldClose: メッセージを処理する必要もあります。ユー
ザーがメニューからアプリケーションを閉じた場合には、アプリケーションから applicationShouldTerminate: メ
ッセージが送られます。ユーザーがウィンドウを閉じた場合には、ウィンドウから windowShouldClose: メッセー
ジ が 送 ら れ ま す ( そ して ア プ リ ケ ー シ ョン を 終了するべきだという応答があれば、アプリケーションからも
applicationShouldTerminate: メッセージが送られます)。
メモ: Objective-C では、パラメータを持つメッセージの名前には接尾辞の : が付きます。
この例のアプリケーション オブジェクトおよびウィンドウ オブジェクトのデリゲートは、どちらもメイン ウィンドウ
コントローラにします。このデリゲート関係のセットアップには Interface Builder を使用します。Ctrl キーを押しなが
ら(ドキュメント ウィンドウ内の)メッセージを送信するオブジェクト(アプリケーション オブジェクトまたはウィ
ンドウ オブジェクト)をドラッグし、デリゲート オブジェクト(メイン ウィンドウ コントローラ)上にドロップして
- 49 -
Delphi Prism を用いた Mono 開発
Brian Long
ください。ドラッグ操作中には青い線が表示され、ドロップするとコネクション ボックスが現れてコントローラ内の
利用可能なアウトレットが一覧表示されます。リストに現在 1 つだけ含まれている delegate という項目を選択すると、
関係がセットアップされます。
両方のデリゲート関 係をセットア ップしたら、次に Monobjc アプリケーションのコードを見てみましょう 。
Program.pas ファイルにはプログラムのエントリ ポイントが含まれていて、Monobjc アプリケーションを始動するた
めの、Cocoa フレームワークをロードし、メインの .nib ファイルをロードし、メインのイベントループを開始すると
いう、標準のコードが実行されます。
部分クラス機能のおかげで、コントローラ クラス MainWindowController は実際には複数のファイルに分かれていま
す。主に着目しなければならないのは MainWindowController.pas で、ここにアクション メソッドやヘルパ メソッド
を追加します。このクラスの別の部分は MainWindowController.Designer.pas に入っていて、そこには、いくつかのコ
ンストラクタ オーバーロードや、いくつかのシステム アクション メッセージ用のアクション メソッドが含まれていま
す(これらのメソッドは partial と empty を指定して宣言されているため、宣言だけで実装はありません。必要があれ
ば コ ントローラ ク ラ ス 内 で 実 装 す る こ と が で きます) 。それ以外 の部分クラ ス定義は、 .nib バンド ル内の
Interface.nib\designable.nib\designable.pas にあります。この部分クラスには、Interface Builder でコントローラ内に
宣言したアクション メソッドやアウトレットが含められます。これについては後で改めて説明します。
メモ: プロジェクトの名前を変更した場合(または別名で保存した場合)、Designable.pas の中の名前空間は自動的
に更新されますが、コントローラ クラスの部分定義を含んでいる他の 2 つのファイルの中の名前空間は変更されませ
ん。その結果、コンパイル時に、同じ名前を持つ 2 つの別のコントローラ クラスが異なる名前空間内で見つかること
になります。そのため、プロジェクト名を変更した場合にユニットの名前空間を更新することが非常に重要になります。
Interface Builder でコントローラにどのような処理をしたかによって、このクラス定義の内容は異なってきます。
Interface Builder でセットアップ(明示的なものも暗黙的なものも含む)した他のクラスを表すものが含まれることも
あります(NSFontManager など)。
現在の要件では、3 つのデリゲート メッセージ処理メソッド(とその作業に必要なヘルパ)を実装する必要があります。
次に示すのはそのコード例です。
- 50 -
Delphi Prism を用いた Mono 開発
Brian Long
type
MainWindowController = public partial class(Monobjc.Cocoa.NSObject)
private
method OKtoTerminate: Boolean;
public
[ObjectiveCMessage('windowShouldClose:')]
method WindowShouldClose(window: NSWindow): Boolean;
[ObjectiveCMessage('applicationShouldTerminate:')]
method ApplicationShouldTerminate(App: NSApplication): Boolean;
[ObjectiveCMessage('applicationShouldTerminateAfterLastWindowClosed:')]
method ApplicationShouldTerminateAfterLastWindowClosed(
App: NSApplication): Boolean;
end;
method MainWindowController.OKtoTerminate: Boolean;
begin
var msgResult: Integer := AppKitFramework.NSRunAlertPanel(
'Cocoa Text Editor', 'Really quit?', 'No', 'Yes', nil);
Result := msgResult = NSPanel.NSAlertAlternateReturn;
end;
method MainWindowController.WindowShouldClose(window: NSWindow): Boolean;
begin
exit OKtoTerminate();
end;
method MainWindowController.ApplicationShouldTerminate(App: NSApplication): Boolean;
begin
if NSApplication.NSApp.MainWindow = nil then exit True;
exit OKtoTerminate();
end;
method MainWindowController.ApplicationShouldTerminateAfterLastWindowClosed(App:
NSApplication): Boolean;
begin
exit True;
end;
メモ: Objective-C や Cocoa の世界を Monobjc アプリケーションの Delphi Prism メソッド(実際にはどの Mono メソ
ッ ド で も ) に 結 び 付 け る に は 、 ObjectiveCMessage 属 性 を 設 定 し な け れ ば な り ま せ ん 。
MainWindowController.Designer.pas のメッセージ処理メソッドでもこの属性を使用していることにお気付きかもしれ
ません。また、元のメッセージにパラメータがある場合には : という接尾辞を含めるのを忘れないでください。含めな
ければ、メッセージ シグニチャが不正確になってしまいます。
このロジックは先に説明した検討事項に沿って書かれています。ウィンドウが閉じられると、OKtoTerminate() ヘルパ
が呼び出され、ユーザーに対して確認ダイアログ(警告パネル)が表示されます。NSRunAlertPanel() の最初の 2 つの
パラメータは、タイトルと、パネルに表示されるテキストです。その後は、デフォルト ボタンのテキスト、代替ボタ
ンのテキスト、3 番目のボタンのテキストです。この例では、[No] がデフォルト ボタンで、3 番目のボタンはありませ
ん 。 こ の メ ソ ッ ド で は 、 ど の ボ タ ン が 押 さ れた かに 応 じ て True ま たは False を返 し ます ( それ を さ ら に
WindowShouldClose() から返します)。[Yes] が押されると、ウィンドウは閉じます。
- 51 -
Delphi Prism を用いた Mono 開発
Brian Long
ウ ィンドウが閉じら れ る か 、 ユ ー ザー が ア プリケーション メニューの [Quit] メニュー項目を選択す ると、
ApplicationShouldTerminate() が呼び出されます。確認ダイアログが必要かどうかを判断するために、アプリケーショ
ンのメイン ウィンドウがまだ存在するかどうかを確認し、その結果に応じて処理を進めます。
メモ: アプリケーション オブジェクトの参照を取得するには NSApplication.NSApp が便利です。
最後に、ApplicationShouldTerminateAfterLastWindowClosed() は True を返して、アプリケーションがよいタイミング
で確実に終了するようにします。
コントロール間の相互作用の例
Monobjc や Cocoa に慣れるために、再び Web 上の簡単なチュートリアルを
試して、同じ効果を Delphi Prism で得るにはどうすればよいかを確認します。
最初に述べておきますが、Cocoa チュートリアルでは、アクションやアウト
レットを Xcode のコードで定義し、その後自動的に Interface Builder に取り
込んでいます。Delphi Prism では、逆の方法を取らなければなりません。
Interface Builder は Delphi Prism ファイルのことを何も知らないため、手動
で Interface Builder にアウトレットやアクションを追加し、その後、Visual
Studio のカスタム ツールを使って Delphi Prism から Designable.pas に宣言
を生成しなければなりません。そうすることで、.nib ファイルに何が含まれ
ているかを確認した上で Designable.pas が出力されることになります。
最初にまねてみる簡単なチュートリアルは、Unix Geek の http://theunixgeek.wordpress.com/2007/11/11/the-cocoatutorial-everyone-needs にあるものです。このチュートリアルでは、コントローラを使って UI とコードとの通信をセ
ットアップする方法に慣れることができます。ボタンからテキスト フィールドに書き出す処理を行います。
新しい Monobjc プロジェクトを作成して、Interface Builder でツールバーを削除し、[Library] ウィンドウからボタンを
1 つ(提供されているうちのどの種類でもかまいません)とテキスト フィールドを 1 つ追加します。さらに、アプリケ
- 52 -
Delphi Prism を用いた Mono 開発
Brian Long
ーション名を含んでいるメニュー項目のテキストを更新し、不必要な Monobjc アセンブリの参照を削除する必要もあ
ります。ここで、アウトレットとアクションをコントローラに追加しなければなりません。アウトレットをテキスト
フィールドと結び付けて、コードからテキスト フィールドにアクセスできるようにします。アクションは、ボタンが
押されたときに応答するメソッドです。
アウトレットをカスタム コントローラに追加するには、[Library] ウィンドウの [Classes] タブで MainWindowController
クラスを探します。クラス リストをスクロールして探してもかまいませんし、ウィンドウ下部の検索ボックスを使っ
てリストを絞り込むこともできます。クラスを選択すると、[Library] ウィンドウの下半分に、継承階層(このクラスは
NSObject を継承しているだけなので空になります)や定義の概要やアウトレットおよびアクションの一覧など、
MainWindowController クラスの情報が表示されます。[Outlets] タブで [+] ボタンをクリックし、words というアウト
レットを追加します。[Actions] タブで sayHello: というアクションを追加します(接尾辞の : を忘れないでください)。
次に、コントローラ アウトレットをテキスト フィールドに結び付けます。ドキュメント ウィンドウ上のコントローラ
から、ドキュメント ウィンドウまたは表示されているアプリケーション ウィンドウ上のテキスト フィールドへと Ctrl
を押しながらドラッグしてください。マウスを離すとアウトレットの一覧が表示されますので、words を選択します。
ボタンをコントローラのアクションに結び付けるには、ボタン(ウィンドウまたはドキュメントの)からコントローラ
へと Ctrl を押しながらドラッグし、マウスを離して sayHello: を選択します。
- 53 -
Delphi Prism を用いた Mono 開発
Brian Long
ドキュメント ウィンドウでコントローラを選択すると、そ
のコントローラのすべてのコネクションを要約した情報がイ
ンスペクタの [Connections] タブに表示されます。実際に、
このタブからコネクションをセットアップすることもできま
す。新しいアウトレットやアクションについてウィンドウ右
側に表示されている円をドラッグし、ドキュメントまたはウ
ィンドウ上の適切なコントロールにドロップするだけです。
[Main Window Controller Connections] のスクリーンショッ
トでは、コントローラがアプリケーション オブジェクトの
デリゲートの役目もしていて、ただ 1 つのウィンドウが閉じられたときに正しい終了動作ができるようにしていること
がわかります。
.nib の UI 定義を変更したので、nib インポート カスタム ツールを実行し、先に説明したコンテキスト メニュー項目を
使って Designable.pas を更新する必要があります。これにより、コントローラの部分クラスが次のように更新されま
す。
type
[ObjectiveCClass]
MainWindowController = public partial class
public
var [ObjectiveCField] words: NSTextField;
[ObjectiveCMessage('sayHello:')]
method sayHello(aSender: NSObject); partial; empty;
end;
words アウトレットが NSTextField として正しく宣言されていること、sayHello() メソッドが partial として宣言されて
いることがわかります。どちらも適切な Monobjc 属性が設定されていて、Objective-C が認識できるようになってい
ます。
- 54 -
Delphi Prism を用いた Mono 開発
Brian Long
他にしなければならないのは、MainWindowController.pas での sayHello() の実装だけです。Designable.pas を変更し
ても、次にカスタム nib ツールを実行するとその変更内容がすべて失われてしまうので無駄だということを忘れないで
ください。
type
MainWindowController = public partial class(Monobjc.Cocoa.NSObject)
public
method sayHello(aSender: NSObject); partial;
[ObjectiveCMessage('applicationShouldTerminateAfterLastWindowClosed:')]
method ApplicationShouldTerminateAfterLastWindowClosed(
App: NSApplication): Boolean;
end;
method MainWindowController.sayHello(aSender: NSObject);
begin
//Give the text field a new value
words.StringValue := 'Hello world';
//Ensure it is updated ASAP
words.NeedsDisplay := True;
//Make the app speak as well, just for the sake of it
var Speech: NSSpeechSynthesizer := new NSSpeechSynthesizer();
Speech.StartSpeakingString(words.StringValue);
end;
method MainWindowController.ApplicationShouldTerminateAfterLastWindowClosed(
App: NSApplication): Boolean;
begin
Result := True;
end;
これを見るとわかるように、アウトレット コネクションを作成したため、テキスト フィールドの更新は非常に簡単で
す。このコードでは、新しい値をテキスト フィールドに書き込むだけでなく、単なる贅沢ですが新しい値を発音させ
てもいます。
次の例は、Korrupted(別名 DaxTsurugi)が作成した http://www.insanelymac.com/forum/index.php?showtopic=14778
にあるチュートリアルで、コードを書かずに UI に "追加の" 機能を持たせる方法を示しています。この例では、スライ
ダ コントロールが自分の位置をテキスト フィールドに設定します。新しい Monobjc プロジェクトを作成し、ツールバ
ーを削除し、メニュー項目を編集し、不必要な Monobjc アセンブリ参照を削除します。水平方向のスライダとテキス
ト フィールドをウィンドウに追加します。スライダのセットアップは、インスペクタの [Attributes] タブで行うことが
できます。ここで最も重要な設定は [Continuous] です。これは、スライダが止まるのを待たないで、位置を変更して
いる間ずっと、リスンしているすべてのものに対してメッセージを送り続けるという意味です。
- 55 -
Delphi Prism を用いた Mono 開発
Brian Long
この機能を実現するには、スライ
ダからテキスト フィールドへと
Ctrl を押したままドラッグし、ス
ライダの
takeIntegerValueFrom: という
受信アクションを選択します。こ
れは、スライダが新しい位置に移
動したときに、位置を示す整数値
をテキスト フィールドの値へ設定
するよう、スライダがテキスト フ
ィールドに指示するという意味で
す。これだけでサンプルは終わり
です。
左に示すように、テキスト フィー
ルドには、トリガすることのでき
るさまざまなアクションや、いくつかのアウトレットが用意されています。
コントロールが提供する機能を利用して不要なコードを書かずに済ますと
いうのは、Cocoa の学習過程の便利な部分です。テキスト ビュー コント
ロールには、呼び出すことのできるアクションが数多く用意されています。
色選択の例
次に、同じアウトレットおよびアクション コネクションの原則を何度も利用する、もう少し大きな例を見てみましょ
う。この例は、Michael Beam が作成した http://macdevcenter.com/pub/a/mac/2001/06/15/cocoa.html にあるチュー
トリアルをまねて作成したもので、次に示すカラー値エディタを構築します。スライダとテキスト フィールドのどち
らを使っても、カラー ウェル コントロールに表示される色を更新することができます。テキスト フィールドを編集す
ると対応するスライダがそれに合わせて更新され、逆も同様に更新されます。カラー ウェル コントロールをクリック
すると色選択パネルが表示され(これはカラー ウェル コントロールの標準動作です)、色選択パネルで色を選択する
とウィンドウ上のコントロールがすべて更新されます。
- 56 -
Delphi Prism を用いた Mono 開発
Brian Long
いつものように、Monobjc プロジェクトを作成し、ツールバーを削除し、メニューを編集し、不必要な Monobjc アセ
ンブリ参照を削除します。それから UI に、4 つのラベル、4 つのテキスト フィールド、4 つの水平スライダ、1 つのカ
ラー ウェルを配置します。次に、コントローラ クラスに 9 つのアウトレットと 6 つのアクションを宣言する必要があ
ります([Library] ウィンドウの [Classes] タブを使用します)。次のスクリーンショットを見ると、すべてのアウトレ
ットおよびアクションの名前と、該当するコントロールにそれがどう結び付けられているかがわかります。また、
Designable.nib に対してカスタム ツールを実行すると、これらはすべて Designable.pas に出力されます。
- 57 -
Delphi Prism を用いた Mono 開発
Brian Long
このコードはあまり面白いものではありませんが、目的は達成しています。ここでは、
MainWindowController.Designer.pas 内で partial empty として宣言されている AwakeFromNib() メッセージ ハンドラ
を実装していることに注意してください。これは、nib が完全にロードされ、その中で定義されたオブジェクトがすべ
てインスタンス化された後で、すぐに実行されます。
MainWindowController = public partial class(Monobjc.Cocoa.NSObject)
private
redValue: Single := 0.5;
greenValue: Single := 0.5;
blueValue: Single := 0.5;
alphaValue: Single := 0.5;
method updateRedUIControls;
method updateBlueUIControls;
method updateGreenUIControls;
method updateAlphaUIControls;
method UpdateColorWell;
protected
public
method AwakeFromNib; partial;
method setRed(aSender: NSObject); partial;
method setBlue(aSender: NSObject); partial;
method setGreen(aSender: NSObject); partial;
method setAlpha(aSender: NSObject); partial;
method updateControls(aSender: NSObject); partial;
end;
method MainWindowController.AwakeFromNib;
begin
updateRedUIControls();
updateGreenUIControls();
updateBlueUIControls();
updateAlphaUIControls();
- 58 -
Delphi Prism を用いた Mono 開発
Brian Long
UpdateColorWell();
end;
method MainWindowController.setRed(aSender: NSObject);
begin
redValue := NSControl(aSender).FloatValue;
updateRedUIControls();
UpdateColorWell();
end;
method MainWindowController.setBlue(aSender: NSObject);
begin
blueValue := NSControl(aSender).FloatValue;
updateBlueUIControls();
UpdateColorWell();
end;
//Similar code for green & alpha
method MainWindowController.UpdateColorWell;
begin
colorWell.Color :=
NSColor.ColorWithCalibratedRedGreenBlueAlpha(
redValue, greenValue, blueValue, alphaValue);
end;
method MainWindowController.updateControls(aSender: NSObject);
begin
var color: NSColor :=
NSColorWell(aSender).Color.ColorUsingColorSpaceName('NSDeviceRGBColorSpace');
redValue := color.RedComponent;
blueValue := color.BlueComponent;
greenValue := color.GreenComponent;
alphaValue := color.AlphaComponent;
updateRedUIControls();
updateGreenUIControls();
updateBlueUIControls();
updateAlphaUIControls();
end;
method MainWindowController.updateRedUIControls;
begin
redField.FloatValue := redValue;
redSlider.FloatValue := redValue;
end;
method MainWindowController.updateBlueUIControls;
begin
blueField.FloatValue := blueValue;
blueSlider.FloatValue := blueValue;
end;
//Similar code for green & alpha
Cocoa の UI 手法 - ウィンドウを振動させてエラーを示す
CocoaOS X ログイン画面の小粋な機能を見たことがあるでしょうか。不正確な情報を入力すると、エラー ダイアログ
を出すのではなく、ログイン ウィンドウを左右に数回振動させて、間違っていることを示すのです。この UI メカニズ
ムが OS X で採用されるほど気が利いているなら、自分のアプリケーションでエラー状態を示すのに使ってもいいはず
- 59 -
Delphi Prism を用いた Mono 開発
Brian Long
です。エラー状態を取り上げている Monobjc のサンプルがないので、先に説明した Cocoa のテキスト エディタに、
ウィンドウを振動させるアイデアを押し込みましょう。ユーザーが終了するかを尋ねられて "No" と答えた場合に、ウ
ィンドウを振動させてそれを強調することにします。
ウィンドウをこのようにアニメーションとして動かすには、Core Animation フレームワークを利用します。次のコー
ドは http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect を書き換えたもので、Objective-C
の Cocoa 呼び出しとそれに相当する Monobjc のものとを比較できる有益なものになっています。
method MainWindowController.ShakeAnimation(frame: NSRect): CAKeyframeAnimation;
const
numberOfShakes = 4;
durationOfShake = 0.5;
vigourOfShake = 0.05;
begin
//Create an animation, and the define a path for it to follow
var animation: CAKeyframeAnimation := CAKeyframeAnimation.Animation;
var shakePath: IntPtr := CGPath.CreateMutable();
var identityTransform: CGAffineTransform :=
CGAffineTransform.CGAffineTransformIdentity;
CGPath.MoveToPoint(shakePath, var identityTransform,
NSRect.NSMinX(frame), NSRect.NSMinY(frame));
for I: Integer := 1 to numberOfShakes do
begin
CGPath.AddLineToPoint(shakePath, var identityTransform,
NSRect.NSMinX(frame) - frame.size.width * vigourOfShake, NSRect.NSMinY(frame));
CGPath.AddLineToPoint(shakePath, var identityTransform,
NSRect.NSMinX(frame) + frame.size.width * vigourOfShake, NSRect.NSMinY(frame));
end;
CGPath.CloseSubpath(shakePath);
animation.path := shakePath;
animation.duration := durationOfShake;
exit animation;
end;
method MainWindowController.ShakeWindow;
begin
//Add our animation to the animation dictionary, with key 'frameOrigin'
mainWindow.Animations:= NSDictionary.DictionaryWithObjectsAndKeys(
ShakeAnimation(mainWindow.Frame), new NSString('frameOrigin'), nil);
mainWindow.Animator.SetFrameOrigin(mainWindow.Frame.origin);
end;
function MainWindowController.OKtoTerminate: Boolean;
begin
var msgResult: Integer := AppKitFramework.NSRunAlertPanel(
'Cocoa Text Editor', 'Really quit?', 'No', 'Yes', nil);
Result := msgResult = NSPanel.NSAlertAlternateReturn;
//If we are not closing, let's shake the window
if not Result then
ShakeWindow();
end;
元のコードにあったさまざまなグローバル関数が静的メソッドに変わっています。また、MoveToPoint() の 2 番目の
パラメータが var パラメータとして宣言されているため、元の呼び出しで渡していた NULL を恒等変換の参照に置き
換えて、移動対象を実際に変換しないようにしなければなりません。
- 60 -
Delphi Prism を用いた Mono 開発
Brian Long
コードのロジックでは、ウィンドウに行わせたい動きを表すパスを作成し、それをアニメーションに適用します。ウィ
ンドウを振動させたいときに、ウィンドウのフレームの起点に対してそのアニメーションを適用すると、ウィンドウが
アニメーション パスに沿って移動します。
Cocoa の UI 手法 - スライドイン シートを使った確認
- 49 - ページの「正しい閉じ方」のセクションでは、ポップアップの NSPanel を使った確認処理を追加しました。実
際の Mac アプリケーションではウィンドウ上部から滑り込んでくるシート ダイアログを使う方が一般的ですが、果た
す役割はまったく同じです。下のスクリーンショットでは、警告シートが使われています。Dock アイコンが上下に跳
ねてアプリケーションに注意を引いていることに着目してください。
- 61 -
Delphi Prism を用いた Mono 開発
Brian Long
プログラミングに関して警告パネルと警告シートが最も違っている点は、警告パネルではパネルが開いた後、ユーザー
がオプションを選択してパネルが要らなくなってから NSRunAlertPanel() 関数が結果を返すことです。それに対して
警告シートでは、NSBeginAlertSheet() を呼び出すと即座に結果が返されます。そして、ユーザーがシートでオプシ
ョンを選択すると、別のメソッドがトリガされます。このメソッドには ObjectiveCMessage 属性を設定し、メソッ
ドをセレクタという特別な種類のパラメータとして NSBeginAlertSheet() に渡さなければなりません。Objective-C
のセレクタは @selector を使って取得することができます。Monobjc では ObjectiveCRuntime.Selector() を呼び
出します。変更後のコードは次のようになります。
type
MainWindowController = public partial class(Monobjc.Cocoa.NSObject)
private
terminating: Boolean := False;
method OKtoTerminate: Boolean;
public
[ObjectiveCMessage('windowCloseConfirmationDelegate:')]
method WindowCloseConfirmationDelegate(sheet: NSWindow;
returnCode: Integer; contextInfo: IntPtr);
end;
method MainWindowController.OKtoTerminate: Boolean;
begin
AppKitFramework.NSBeginAlertSheet(
'Cocoa Text Editor', 'No', 'Yes', nil, mainWindow, Self,
//Pass delegate selector here to have it called as soon as user presses a button
nil,
//Pass delegate selector here to have it react after sheet closes
ObjectiveCRuntime.Selector('windowCloseConfirmationDelegate:'),
nil, 'Really quit');
//The sheet will hang about for a bit, so we'll say "No" for now,
//and act accordingly when the user shuts the sheet with a button
exit False;
end;
method MainWindowController.WindowCloseConfirmationDelegate(sheet: NSWindow; returnCode:
Integer; contextInfo: IntPtr);
begin
//Now the sheet has been used we can decide whether to terminate the app or not
if returnCode = NSPanel.NSAlertAlternateReturn then
begin
terminating := True;
NSApplication.NSApp.Terminate(Self);
end
else
ShakeWindow()
end;
- 62 -
Delphi Prism を用いた Mono 開発
Brian Long
ウィンドウの幅が十分にある場合には、シートはウィンドウ上部から滑り下りてきます。幅が足りなければ、シートはアニメ
ーションで広がって出てきます。これは見て美しいものです。
まとめ
このホワイト ペーパーでは、既に持っている .NET プラットフォームおよび Delphi 言語のスキルを活かして Linux や
Mac OS X で動くアプリケーションを作成するために、Delphi Prism をどう利用できるかを説明しました。クロスプラ
ットフォーム開発では数多くの問題を考慮する必要があり、既存のコードをどの程度再利用できるかはさまざまです。
このホワイト ペーパーを読んだなら、どのような選択肢があり、自分のコードにどのような可能性があるかを把握で
きていることと思います。ネイティブ同様のアプリケーションが必要な場合には、新しい GUI ツールキットについて
いくらか学ぶ必要があります。しかしそうすることで、UI を持たないビジネス ロジックを最大限に再利用するために、
アーキテクチャを注意深くセットアップするという恩恵が得られます。
謝辞
さまざまな技術分野について理解し、さまざまな技術的ハードルを乗り越えるのを助けてくれた、marc hoffman、
Richy King、Carlo Kok、Adrian Milliner、Steve Scott に感謝します。
- 63 -
Delphi Prism を用いた Mono 開発
Brian Long
Brian Long は、この 15 年間、Delphi、C#、C++ の言語と、Win32、.NET、Mono のプラットフォームを中心に、トレ
ーニングやトラブルシューティングやメンタリングを行ってきました。余暇には、Unix オペレーティング システムの
特異性や問題点を再発見したり改めて楽しんだりしています。90 年代半ばに Pascal の問題解決についての書籍を書い
た以外にも、さまざまな書籍の一部の章を執筆したり、数え切れないほどの雑誌記事を書いたり、ときどきは Sybex
の技術編集も行っています。Brian はオンライン記事もいくつも書いていて、http://blong.com で読むことができます。
© 2009 Brian Long Consulting and Training Services Ltd. All Rights Reserved.
エンバカデロ・テクノロジーズについて
エンバカデロ・テクノロジーズは、1993 年にデータベースツールベンダーとして設立され、2008 年にボーランドの開発ツ
ール部門「CodeGear」との合併によって、アプリケーション開発者とデータベース技術者が多様な環境でソフトウェアア
プリケーションを設計、構築、実行するためのツールを提供する最大規模の独立系ツールベンダーとなりました。米国企
業の総収入ランキング「フォーチュン 100」のうち 90 以上の企業と、世界で 300 万以上のコミュニティが、エンバカデロ
の Delphi®、C++Builder®、JBuilder®といった CodeGear™製品や ER/Studio®、DBArtisan®、RapidSQL®をはじめとす
る DatabaseGear™製品を採用し、生産性の向上と革新的なソフトウェア開発を実現しています。エンバカデロ・テクノ
ロジーズは、サンフランシスコに本社を置き、世界各国に支社を展開しています。詳細は、www.embarcadero.com/jp を
ご覧ください。
Embarcadero、Embarcadero Technologies ロゴならびにすべてのエンバカデロ・テクノロジーズ製品またはサービス名は、Embarcadero Technologies, Inc.
の商標または登録商標です。その他の商標はその所有者に帰属します。
- 64 -