未だに信じられない。 ありましたよA#。 Adaの.NET向けコンパイラ。 正確には、MSIL(.NETの中間コードと一対一対応するアセンブリ言語)を吐くようですが、コンパイラを作る時直接バイナリを吐くのが面倒な時はよく使われる手法です。
まずは、asharp.pdfを読んでいきます。 直接的な翻訳は断りも無しにできないでしょうし僕も飽きるので、要点だけ。
とばす。
経緯とかはすっとばす。 AdaからMSILへ、MSILからAda用定義ファイルへ、のコンパイル/インポートが可能らしい。 それだけわかれば充分でしょー?
単にCLRのよくある説明ですね。 今更なのでとばす。
移植のやり方みたいなものが書いてあります。 Adaには既にJVM向けコンパイラがあったので、最初はそれで開発して、J#のインポート機能を使ってたらしいです。 最新バージョンではJ#への依存は無くなっているようです。 RAPIDという名前のGUIデザインツールも、A#向けに修正されてるようですが、RAPIDなんて名前は初めて目にしましたので、当面無視。
最初のころはJGNAT(JVM向けコンパイラ)とJBIMP(Javaバイトコードインポートツール)を使っていた、と、それだけの図。
msil2ada(MSILからAda用定義ファイルを作るツール)の使い方。 ILDASMを用いて、.dllからMSILを抜き出すようです。 AdaGOOPというツールが自動的にやってくれるようですが、AdaGOOPも僕は知りません。
例が書かれています。
やはりプロパティは無くSetとGetを介するようです。
しかし、pragrma Extensions_Allowed(On);
という記述がありますので、何らかの構文拡張はあるようです。
with type X is access;
という見慣れない構文がありますので、これかもしれません。
Ada95には無い構文であることは確認しました。
Ada95は単一継承のみで、多重継承やインターフェースは持っていません。 .NETにはインターフェースがあるので、導入方法でしょう。 もっとも、Javaにもインターフェースはあるので、構文は既に決まっていたようです。 …引数付きの型の構文を拡張したのでしょうが…長ったらしい構文ですね。
参照が入り組んでいる時にmsil2adaが失敗する問題も書かれていますが、とりあえずとばす。
with type
についての説明です。
Java向けコンパイラの時点で既にあった構文の様ですね。
Adaの型は、通常、定義されたパッケージの中で完結していなければならないので、外からインポートしてくるための構文らしいです。
コンストラクタの扱いが書いてあります。 MSIL_Constructorというpragmaがあるそうです。 Ada95本来のコンストラクタは、Controlled型から承しなければ使えない上、引数も取れません(初期化された値をもとに組み上げることはできますが)ので、まあ当然かも。 で、.NET向けコンストラクタですが…ただのメソッドつーか関数ですね。これは。 どう見てもnullが返されるようにしか見えないのですが、これでうまくいくようです。 …ということは、new演算子は使わない?
値型です。Adaは(Javaと異なり)C++同様「値」を扱える言語なので、何も問題無いです。
参照型の場合はRefでしたが、値型の場合はValueTypeという名前でインポートされるようです。
値型のときは件のwith type
構文を、is tagged
で使うようです。
.NETの列挙型は、Enumクラスと派生関係を持っていますが、とりあえずA#では、Adaの列挙型にマップされるようです。 ただし、.NETでは、列挙型と集合型を混同するという、AdaやPascalから見れば、いや、それどころかC++から見てすら(←ビットフィールドあるし)、許せない仕様があります。 Delphi for .NET(集合型有り)では、キャストして使う、という、堪え忍ぶ道を選んだようです。 A#では、そのような場合は、列挙型に"+"演算子が用意されるようです。 流石演算子オーバーロードを持つ言語、といったところでしょうか。 でも、本当は、packedなBoolean配列としてインポートして欲しかったです。まあ、msil2adaから見れば、区別の手段は無いわけですが…。
A#はふたつの構文拡張を持つようです。 ひとつは、お馴染みOBJECT.METHODなメソッド呼びだし。 もうひとつは、文字列型の暗黙の変換。
Ada95では、多態を行う仮想関数すら、METHOD(OBJECT)の形で呼び出します。 これはこれで興味深い点もある構文なのですが、この構文の場合当然期待されるダブルディスパッチができないので、ほとんどの場合は面倒なだけの構文と化しています。 で、A#では、OBJECT.METHODな呼び出しをサポートしたようです。 Windows用のGNATでも3.15以降はサポートしてる??マジか??
procedure Test is pragma Linker_Options("-s"); type T is tagged with null record; type Ref is access all T; procedure Method(This: access T) is begin null; end; A: Ref; begin A.Method; end Test;
GNAT(MingW版)でコンパイル通っちゃいましたよ。 すげー、マジだ。
Adaの文字列は文字型の(半)動的配列です。D言語と似ています。 対する.NETの文字列は単独のクラスです。 変換用に"+"演算子がオーバーロードされていて、とりあえず"+"付けとけば、型チェックとかもすり抜けて上手くいくようです。
前述した通り、僕はRAPID知らないのでパス。
コンパイラやmsil2adaの問題、COMとの相互運用、Unconstrained Arrayが返せない!?問題、列挙型の問題、J#からの独立、その他諸々将来の課題が書かれています。 いまいちわからないのですが、この.pdfいつ書かれたのでしょうね? J#からの独立は既に果たされている筈なので、他の問題も幾つか解決されている可能性があります。
結論。よって無視。
参考文献。よって無視。
C:\Program Files以下にmgnatというディレクトリを作って(mgnat.zipはディレクトリ付きで圧縮されてますけど)展開。 その下にmsil2adaもディレクトリ付きでぶち込んでおくことにしましょうか。
binにPATH環境変数を通します。 また、こんな感じでレジストリキーを追加します。
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Ada Core Technologies] [HKEY_LOCAL_MACHINE\SOFTWARE\Ada Core Technologies\mgnat] [HKEY_LOCAL_MACHINE\SOFTWARE\Ada Core Technologies\mgnat\root] @="C:\\progra~1\\mgnat" [HKEY_LOCAL_MACHINE\SOFTWARE\Ada Core Technologies\mgnat\Standard Libraries] [HKEY_LOCAL_MACHINE\SOFTWARE\Ada Core Technologies\mgnat\Standard Libraries\DotNET] @="C:\\Progra~1\\mgnat\\include"
register_mgnat.batを走らせます。 .NETなdllが登録されます。これってランタイムでしょうか。配布時も必要…? ともかく、これでmgnat本体の方はいいみたいです。
コンパイラのソース付きなのは嬉しいですが(GPLだから当然かも)….ads/.adbあわせて13メガのソースコードは圧巻です。読む気が失せた。
さて、msil2adaですが、readme.txtによると、AdaGOOPがあるときはcompileall.bat、無い時はrebuild.batを走らせろと書いてあります。なので、rebuild.bat。 gnatmakeを呼んでいるようですが、僕は既にMinGW版を入れているので問題なしです。 で、走らせると、gccがCPU使用率100%でがんばってくれます。
C:\Program Files\mgnat\msil2ada>rebuild C:\Program Files\mgnat\msil2ada>gnatmake msil2ada gcc -c msil2ada.adb gcc -c msil_model.ads gcc -c msil_parser.adb gnat1.exe: Cannot allocate 729647880 bytes gnatmake: "msil_parser.adb" compilation error
ほほう、メモリが足りないと仰せられますか。
でも、msil2ada.exeがあるなあ…と思ってタイムスタンプを見たら赤面。 最初っから入ってたのですね。消し消し。展開し直し。 8MBの巨大.exeですね…。メモリ256MBでは足りないのも何となく納得。 もっとも、コンパイラがメモリ不足を訴えたのは初めての経験ですけどね…。 で、msil2ada.exeを、mgnat\binへコピーします。
testsディレクトリに、hello.exeなるものがあるので、実行。
C:\Program Files\mgnat\tests>hello [Enter] かなりの秒数待たされてやっぱ.NETだなあと実感した後 hello
hello.adbがソースみたいです。
ではコンパイル、と、mgnatmake hello
と打つと…コンパイルできません。
system.adsがどうたら言われます。
経過をすっとばして結論を書くと、環境変数が必要でした。
例のADA_INCLUDE_PATHとADA_OBJECTS_PATHです。
どうやらmingw版と微妙にバージョンが異なり、共存はできない様子なので、バッチファイルで切り抜けます。
set ADA_INCLUDE_PATH=C:\Progra~1\mgnat\lib\mgnat\adainclude;C:\Progra~1\mgnat\include set ADA_OBJECTS_PATH=C:\Progra~1\mgnat\lib\mgnat\adalib
で、テストコード。
with Ada.Text_IO; procedure Classic_Hello is begin Ada.Text_IO.Put_Line("Hello"); end Classic_Hello;
...\Hello>mgnatmake classic_hello mgnatbind -x classic_hello.ali mgnatlink classic_hello.ali Assembling 'b~classic_hello.il' , no listing file, to EXE --> 'classic_hello.exe ' Source file is ANSI Assembling './classic_hello.il' , no listing file, to EXE --> 'classic_hello.exe ' Source file is ANSI Operation completed successfully ...\Hello>classic_hello やっぱり待たされる Hello ...\Hello>
Adaの標準ライブラリも使える様です。
で、お次は、.NETのConsole.WriteLineバージョン。 tests\hello.adbにトライ&エラーで手を加えています。
with Mssyst.Console; with Mssyst.String; procedure Hello is use type Mssyst.String.Ref; begin Mssyst.Console.WriteLine(+"Hello"); end Hello;
これも上手くいきました。 後者の方が.exeサイズは小さいということは、全部.exeに含まれているのでしょうか。 機会があったら他の環境に持っていって試そうと思います。
重くなるだけという説が濃厚なWindows.Formsですが、兎に角使ってみます。
with Mssyst.Windows.Forms.Form; with Mssyst.Windows.Forms.Application; procedure Form_Test is pragma Linker_Options("-subsystem=2"); begin Mssyst.Windows.Forms.Application.Run(Mssyst.Windows.Forms.Form.New_Form); end Form_Test;
クラス毎に.adsを作るかなあ?Adaは折角ひとつのソースコードにパッケージを入れ子にできるのに…と思うのですが、msil2adaに代わるものを自作する気は無いので、ひたすらwithを並べることになるのでしょうね。
難しいことは何も無いのですが、pragmaは書いておかないと、コンソールがくっついてきてしまいます。
何故2かと言えば、winnt.hにおいて#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2
と定義されているので2なのです。
で、コンパイルすると…。 Windows.Forms関連の.aliがずらずらと…。 毎回コンパイルしているのでしょうか。 何か間違っている様子。
set ADA_INCLUDE_PATH=C:\Progra~1\mgnat\lib\mgnat\adainclude;C:\Progra~1\mgnat\include set ADA_OBJECTS_PATH=C:\Progra~1\mgnat\lib\mgnat\adalib;C:\Progra~1\mgnat\include
…ですね。
で、再コンパイルすると、ファイルのバージョンが云々言われてコンパイルできません。 C:\Progra~1\mgnat\includeに最初から入っている.aliはやや古いようです。 とりあえず、全.aliのリードオンリー属性を解除。 それで、mgnatmakeに全てを任せると、バージョン違いな.aliは再コンパイルしてくれます。 …カレントディレクトリに。 そんなわけで、カレントディレクトリにmssyst-*.aliができた場合は、C:\Progra~1\mgnat\includeへ上書き移動してやれば、次からはいちいちインポート用.adsがコンパイルされなくなります。
できた.exeも普通に動いているようです。
フォームの継承に挑戦してみます。
…約二時間経過。 これが試行錯誤の成果だ!
pragma Extensions_Allowed(On); with MSSyst.IDisposable; with MSSyst.Windows.Forms.IContainerControl; with MSSyst.ComponentModel.ISynchronizeInvoke; with MSSyst.Windows.Forms.IWin32Window; with MSSyst.ComponentModel.IComponent; with MSSyst.Windows.Forms.Form; with MSSyst.Windows.Forms.ContainerControl; with MSSyst.String; package My_Form is type Typ( I_IContainerControl : MSSyst.Windows.Forms.IContainerControl.Ref; I_ISynchronizeInvoke : MSSyst.ComponentModel.ISynchronizeInvoke.Ref; I_IWin32Window : MSSyst.Windows.Forms.IWin32Window.Ref; I_IComponent : MSSyst.ComponentModel.IComponent.Ref; I_IDisposable : MSSyst.IDisposable.Ref) is new MSSyst.Windows.Forms.Form.Typ( I_IContainerControl => I_IContainerControl, I_ISynchronizeInvoke => I_ISynchronizeInvoke, I_IWin32Window => I_IWin32Window, I_IComponent => I_IComponent, I_IDisposable => I_IDisposable) with null record; type Ref is access all Typ'Class; function ActivateControl( This : access Typ; Active : MSSyst.Windows.Forms.Control.Ref) return Standard.Boolean; function new_My_Form(This : Ref := null) return Ref; pragma MSIL_Constructor(new_My_Form); end My_Form;
インターフェースを、パラメータ付き型で表現している都合上、スーパークラスが実装しているインターフェースを、サブクラスでもだらだらとパラメータとして取って、スーパークラスにだらだらと渡さないといけないのですね。 こりゃ手間です。
でも、そんなのはむしろ枝葉末節。 もっと重要なのが、ActivateControlです。 C#ではこんなもの毎回overrideする必要はありませんでしたね。 無論今回も、したくてしてるわけじゃありません。 これを書かないと、こうなります。
...\my_form>mgnatmake test mgnat -c test.adb mgnat -c my_form.adb my_form.ads:12:09: must override interface operation "ActivateControl" at mssyst -windows-forms-icontainercontrol.ads:23 mgnatmake: "my_form.adb" compilation error
ActivateControlはIContainerControlのメソッドで、当然継承階層中IContainerControlを実装しているSystem.Windows.Forms.ContainerControlが実装している「はず」のものです。
…で、MSSyst.Windows.Forms.ContainerControl.adsを見ると、無いです。 .NETのSDKのヘルプでContainerControlを見ても、無いです。 ildasmで、System.Windows.Forms.dllを、時間はかかりますがとにかく開いて、ContainerControlを探し出してメソッド一覧を見るも、無いです。
そう、ActivateControlという名前のメソッドが、無いのです。 これは何事でしょう?
代わりに、ActivateControlInternalという、シグネチャが全く同じ関数があります。 C#ってインターフェースの実装を別名で行えましたっけ…? そんなわけで、仕方なくActivateControlをここで実装し、中身は単にActivateControlInternalを呼び出すだけにします。
pragma Extensions_Allowed(On); package body My_Form is function ActivateControl( This : access Typ; Active : MSSyst.Windows.Forms.Control.Ref) return Standard.Boolean is begin -- return MSSyst.Windows.Forms.ContainerControl.ActivateControlInternal( -- MSSyst.Windows.Forms.ContainerControl.Ref(This), Active); return Ref(This).ActivateControlInternal(Active); end ActivateControl; function new_My_Form(This : Ref := null) return Ref is use type MSSyst.String.Ref; Super : MSSyst.Windows.Forms.Form.Ref := MSSyst.Windows.Forms.Form.new_Form(MSSyst.Windows.Forms.Form.Ref(This)); begin -- MSSyst.Windows.Forms.Control.set_Text(MSSyst.Windows.Forms.Control.Ref(This), +"TEST"); This.set_Text(+"TEST"); return This; end; end My_Form;
コメントにしたのが、Ada本来の呼び出し方、使っているのは、OBJECT.METHODな呼び出し方、です。 記述量が減っていい感じです。
さて…ActivateControlInternalも、MSSyst.Windows.Forms.ContainerControl.adsに無いのですね。 インポートされて無いです。 別にprivateとかでは無いように見えますが、.NETのSDKヘルプにも書いて無いので微妙です。 では作り直し、と、ildasmにSystem.Windows.Forms.dllのILを吐かせ、それをmsil2adaにかけると…スタックオーバーフローします。 仕方ないから手動で書き足しておきます。
function ActivateControlInternal( This : access Typ; Active : MSSyst.Windows.Forms.Control.Ref) return Standard.Boolean; pragma Import(MSIL,ActivateControlInternal,"ActivateControlInternal");
で、メインを書いて、めでたしめでたし。
with Mssyst.Windows.Forms.Form; with Mssyst.Windows.Forms.Application; with My_Form; procedure Test is pragma Linker_Options("-subsystem=2"); begin Mssyst.Windows.Forms.Application.Run(Mssyst.Windows.Forms.Form.Ref(My_Form.new_My_Form)); end Test;
Adaはアップキャストを自動で行ってくれませんので、手間な感じです。
dbgclr.exeでデバッグできました。 コンパイルオプションなどは特に要らないようですが、ブレークポイントを仕掛けない限り止まりません。 まる。
2003-05-24 | 発見 |
2003-05-25 | Windows.Forms(1) |
2003-05-26 | Windows.Forms(2) |
2003-06-07 | デバッグ |