./index.html ../index.html

A#!

未だに信じられない。 ありましたよA#。 Adaの.NET向けコンパイラ。 正確には、MSIL(.NETの中間コードと一対一対応するアセンブリ言語)を吐くようですが、コンパイラを作る時直接バイナリを吐くのが面倒な時はよく使われる手法です。

ドキュメントを読む

まずは、asharp.pdfを読んでいきます。 直接的な翻訳は断りも無しにできないでしょうし僕も飽きるので、要点だけ。

ABSTRACT

とばす。

1. INTRODUCTION

経緯とかはすっとばす。 AdaからMSILへ、MSILからAda用定義ファイルへ、のコンパイル/インポートが可能らしい。 それだけわかれば充分でしょー?

2. MICROSOFT'S COMMON LANGUAGE RUNTIME

単にCLRのよくある説明ですね。 今更なのでとばす。

3. ADDING ADA TO THE .NET FAMILY

3.1 Overall Approach

移植のやり方みたいなものが書いてあります。 Adaには既にJVM向けコンパイラがあったので、最初はそれで開発して、J#のインポート機能を使ってたらしいです。 最新バージョンではJ#への依存は無くなっているようです。 RAPIDという名前のGUIデザインツールも、A#向けに修正されてるようですが、RAPIDなんて名前は初めて目にしましたので、当面無視。

3.2 Initial Prototype Work

最初のころはJGNAT(JVM向けコンパイラ)とJBIMP(Javaバイトコードインポートツール)を使っていた、と、それだけの図。

3.3 Recovering Ada Specification form MSIL

msil2ada(MSILからAda用定義ファイルを作るツール)の使い方。 ILDASMを用いて、.dllからMSILを抜き出すようです。 AdaGOOPというツールが自動的にやってくれるようですが、AdaGOOPも僕は知りません。

例が書かれています。 やはりプロパティは無くSetとGetを介するようです。 しかし、pragrma Extensions_Allowed(On);という記述がありますので、何らかの構文拡張はあるようです。 with type X is access;という見慣れない構文がありますので、これかもしれません。 Ada95には無い構文であることは確認しました。

3.3.1 Interfaces

Ada95は単一継承のみで、多重継承やインターフェースは持っていません。 .NETにはインターフェースがあるので、導入方法でしょう。 もっとも、Javaにもインターフェースはあるので、構文は既に決まっていたようです。 …引数付きの型の構文を拡張したのでしょうが…長ったらしい構文ですね。

参照が入り組んでいる時にmsil2adaが失敗する問題も書かれていますが、とりあえずとばす。

3.3.2 Circler Type Dependency

with typeについての説明です。 Java向けコンパイラの時点で既にあった構文の様ですね。 Adaの型は、通常、定義されたパッケージの中で完結していなければならないので、外からインポートしてくるための構文らしいです。

3.3.3 Constructors

コンストラクタの扱いが書いてあります。 MSIL_Constructorというpragmaがあるそうです。 Ada95本来のコンストラクタは、Controlled型から承しなければ使えない上、引数も取れません(初期化された値をもとに組み上げることはできますが)ので、まあ当然かも。 で、.NET向けコンストラクタですが…ただのメソッドつーか関数ですね。これは。 どう見てもnullが返されるようにしか見えないのですが、これでうまくいくようです。 …ということは、new演算子は使わない?

3.3.4 Value Types

値型です。Adaは(Javaと異なり)C++同様「値」を扱える言語なので、何も問題無いです。 参照型の場合はRefでしたが、値型の場合はValueTypeという名前でインポートされるようです。 値型のときは件のwith type構文を、is taggedで使うようです。

3.3.5 Enumeration

.NETの列挙型は、Enumクラスと派生関係を持っていますが、とりあえずA#では、Adaの列挙型にマップされるようです。 ただし、.NETでは、列挙型と集合型を混同するという、AdaやPascalから見れば、いや、それどころかC++から見てすら(←ビットフィールドあるし)、許せない仕様があります。 Delphi for .NET(集合型有り)では、キャストして使う、という、堪え忍ぶ道を選んだようです。 A#では、そのような場合は、列挙型に"+"演算子が用意されるようです。 流石演算子オーバーロードを持つ言語、といったところでしょうか。 でも、本当は、packedなBoolean配列としてインポートして欲しかったです。まあ、msil2adaから見れば、区別の手段は無いわけですが…。

3.4 Compiling Ada Directory to MSIL

A#はふたつの構文拡張を持つようです。 ひとつは、お馴染みOBJECT.METHODなメソッド呼びだし。 もうひとつは、文字列型の暗黙の変換。

3.4.1 Object.Method syntax

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版)でコンパイル通っちゃいましたよ。 すげー、マジだ。

3.4.2 Implict string conversions

Adaの文字列は文字型の(半)動的配列です。D言語と似ています。 対する.NETの文字列は単独のクラスです。 変換用に"+"演算子がオーバーロードされていて、とりあえず"+"付けとけば、型チェックとかもすり抜けて上手くいくようです。

3.5 Porting RAPID to .NET

前述した通り、僕はRAPID知らないのでパス。

4. FUTURE WORK

コンパイラやmsil2adaの問題、COMとの相互運用、Unconstrained Arrayが返せない!?問題、列挙型の問題、J#からの独立、その他諸々将来の課題が書かれています。 いまいちわからないのですが、この.pdfいつ書かれたのでしょうね? J#からの独立は既に果たされている筈なので、他の問題も幾つか解決されている可能性があります。

5. CONCLUSION

結論。よって無視。

6. REFERENCES

参考文献。よって無視。

インストール

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

で、テストコード。

classic_hello.adb

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にトライ&エラーで手を加えています。

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を使う(1)

重くなるだけという説が濃厚なWindows.Formsですが、兎に角使ってみます。

form_test.adb

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も普通に動いているようです。

Windows.Formsを使う(2)

フォームの継承に挑戦してみます。

…約二時間経過。 これが試行錯誤の成果だ!

my_form.ads

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を呼び出すだけにします。

my_form.adb

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にかけると…スタックオーバーフローします。 仕方ないから手動で書き足しておきます。

MSSyst.Windows.Forms.ContainerControl.ads (追加)

  function ActivateControlInternal(
    This : access Typ;
    Active : MSSyst.Windows.Forms.Control.Ref) return Standard.Boolean;
  pragma Import(MSIL,ActivateControlInternal,"ActivateControlInternal");

で、メインを書いて、めでたしめでたし。

test.adb

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でデバッグできました。 コンパイルオプションなどは特に要らないようですが、ブレークポイントを仕掛けない限り止まりません。 まる。