Objective-Cやってみよう

安楽OOPいいなあ、ということで、Objective-Cで遊んでみます。
1. Hello, world!
Objective-Cの文字列はNXConstantStringのインスタンスを静的に確保したものです。cSteingとlengthメッセージで中身を取り出せます。やってみよう。

with Interfaces.ObjC;
use Interfaces.ObjC;
use type Interfaces.ObjC.unsigned;
with Text_IO;
procedure gnu_step1_str is
   --  文字列本体
   Data : constant String := "Hello, world!" & ASCII.NUL;
   --  クラス名
   Class_Name : constant String := "NXConstantString" & ASCII.NUL;
   --  NXConstantStringのインスタンスを静的に作る
   Instance : constant NXConstantString := (
      super => (isa => objc_get_class (Class_Name)),
      c_string => Data (1)'Unrestricted_Access,
      len => Data'Length - 1);
   --  id Object = @"Hello, world!"
   Object : id := Instance.super.isa'Unchecked_Access;
   --  unsigned Returned_Length = [Object length];
   length_Name : constant String := "length" & ASCII.NUL;
   length_Selector : SEL := sel_get_uid (length_Name);
   length_Body : length := objc_msg_lookup (Object, length_Selector);
   Returned_Length : unsigned := length_Body (Object, length_Selector);
   --  char const *Returned_Data = [Object cString];
   cString_Name : constant String := "cString" & ASCII.NUL;
   cString_Selector : SEL := sel_get_uid (cString_Name);
   cString_Body : cString := objc_msg_lookup (Object, cString_Selector);
   Returned_Data : not null access constant Character :=
      cString_Body (Object, cString_Selector);
   --  得られた文字列にアクセス
   Data_As_String : String (1 .. Natural (Returned_Length));
   for Data_As_String'Address use Returned_Data.all'Address;
begin
   Text_IO.Put_Line (Data_As_String);
end gnu_step1_str;

実行結果。

Hello, world!

2. Objectをインスタンス
ううむ、どうもらしくない。静的に割り当てたオブジェクトではやっぱObjective感はしないですねー。
やっぱり[ [ClassName alloc] init]でインスタンス化しないと。とりあえずルートのObjectクラスをインスタンス化して、動作確認のためにクラス名を取得できるメッセージnameを呼んでみます。

with Interfaces.ObjC;
use Interfaces.ObjC;
use type Interfaces.ObjC.unsigned;
procedure gnu_step2_obj is
   Object_Class : Class := objc_get_class ("Object" & ASCII.NUL);
   Object_Class_As_id : id := Object_Class.class_pointer'Access;
   alloc_Selector : SEL := sel_get_uid ("alloc" & ASCII.NUL);
   alloc_Body : alloc := objc_msg_lookup (Object_Class_As_id, alloc_Selector);
   Creating_Object : id := alloc_Body (Object_Class_As_id, alloc_Selector);
   init_Selector : SEL := sel_get_uid ("init" & ASCII.NUL);
   init_Body : alloc := objc_msg_lookup (Creating_Object, init_Selector);
   Object : id := init_Body (Creating_Object, init_Selector);
begin
   declare
      name_Selector : SEL := sel_get_uid ("name" & ASCII.NUL);
      name_Body : name := objc_msg_lookup (Object, name_Selector);
      Name : not null access constant Character :=
         name_Body (Object, name_Selector);
      procedure puts (s : not null access constant Character);
      pragma Import (C, puts);
   begin
      puts (Name);
   end;
   declare
      free_Selector : SEL := sel_get_uid ("free" & ASCII.NUL);
      free_Body : alloc := objc_msg_lookup (Object, free_Selector);
      Returned : id := free_Body (Object, free_Selector);
   begin
      null;
   end;
end gnu_step2_obj;

ゼロ終端文字列の扱いが面倒でC関数のputs呼んでしまった時点で負けた気がします。ええい、Objectiveな入出力は無いのか!……といってもランタイムにはObjectとNXConstantStringとProtocolの3つしかクラスが定義されて無いのでした。なむさん。[stdout d:1 s:@"STR" f:2.0]なんて書けたらprintfより便利な気も。
とりあえず実行結果。

Object

3. 継承して自作クラス
オブジェクトがインスタンス化できたことで、ちょっとはObjectiveになりましたが、まだこれからです。クラスは自分で作ってこそですからね。なにしろランタイムには3つしか(ry
Objectから派生して、My_Classを作ってみます。整数型のデータメンバを増やして、initをオーバーライドして123を設定、またputに応答して値を出力するようにします。

with Interfaces.ObjC;
use Interfaces.ObjC;
use type Interfaces.ObjC.int;
use type Interfaces.ObjC.long;
with Ada.Text_IO;
procedure gnu_step3_inh is
   type My_Class_Record is record
      super : aliased Object;
      Data : Integer;
   end record;
   function My_Class_init (self : id; op : SEL) return id;
   pragma Convention (C, My_Class_init);
   function My_Class_put (self : id; op : SEL) return id;
   pragma Convention (C, My_Class_put);
   My_Class_Name : constant String := "My_Class" & ASCII.NUL;
   Parent_Class_Name : constant String := "Object" & ASCII.NUL;
   Data_Name : constant String := "Data" & ASCII.NUL;
   Data_Type : constant String := "i" & ASCII.NUL;
   My_Class_Variables : aliased constant objc_ivar_list := (
      ivar_count => 1,
      ivar_list => (1 => (
         Data_Name (1)'Unrestricted_Access,
         Data_Type (1)'Unrestricted_Access,
         Object'Size / Standard'Storage_Unit)));
   init_Name : constant String := "init" & ASCII.NUL;
   put_Name : constant String := "put" & ASCII.NUL;
   Method_Type : constant String := "@8@0:4\0" & ASCII.NUL;
   My_Class_Methods : aliased constant objc_method_list := (
      method_next => null,
      method_count => 2,
      method_list => (
         1 => (
            Address_To_SEL (init_Name'Address),
            Method_Type (1)'Unrestricted_Access,
            My_Class_init'Unrestricted_Access),
         2 => (
            Address_To_SEL (put_Name'Address),
            Method_Type (1)'Unrestricted_Access,
            My_Class_put'Unrestricted_Access)));
   My_MetaClass : aliased constant objc_class := (
      class_pointer => Address_To_Class (Parent_Class_Name'Address),
      super_class => Address_To_Class (Parent_Class_Name'Address),
      name => My_Class_Name (1)'Unrestricted_Access,
      version => 0,
      info => CLS_META,
      instance_size => objc_class'Size / Standard'Storage_Unit,
      ivars => null,
      methods => null,
      dtable => null,
      subclass_list => null,
      sibling_class => null,
      protocols => null,
      gc_object_type => null);
   My_Class : aliased constant objc_class := (
      class_pointer => My_MetaClass'Unchecked_Access,
      super_class => Address_To_Class (Parent_Class_Name'Address),
      name => My_Class_Name (1)'Unrestricted_Access,
      version => 0,
      info => CLS_CLASS,
      instance_size => My_Class_Record'Size / Standard'Storage_Unit,
      ivars => My_Class_Variables'Unchecked_Access,
      methods => My_Class_Methods'Unchecked_Access,
      dtable => null,
      subclass_list => null,
      sibling_class => null,
      protocols => null,
      gc_object_type => null);
   My_Symbol_Table : aliased constant Symtab_With_defs := (
      cls_def_cnt => 1,
      cat_def_cnt => 0,
      symtab => (0, null, 1, 0),
      cls_defs => (1 => My_Class'Unchecked_Access),
      cat_defs => (1 .. 0 => <>),
      end_defs => null);
   My_Module : aliased constant Module := (
      symtab => My_Symbol_Table.symtab'Unchecked_Access,
      others => <>);
   function My_Class_init (self : id; op : SEL) return id is
      Object : My_Class_Record;
      pragma Import (Ada, Object);
      for Object'Address use self.all'Address;
      Super_init : IMP := objc_msg_lookup_super (
         (self, My_Class.super_class), op);
   begin
      Ada.Text_IO.Put_Line ("init");
      Object.Data := 123;
      return Super_init (self, op);
   end My_Class_init;
   function My_Class_put (self : id; op : SEL) return id is
      Object : My_Class_Record;
      pragma Import (Ada, Object);
      for Object'Address use self.all'Address;
   begin
      Ada.Text_IO.Put_Line ("put(" & Integer'Image (Object.Data) & ")");
      return self;
   end My_Class_put;
begin
   objc_exec_class (My_Module'Access);
   declare
      My_Class_As_id : id := My_Class.class_pointer'Unchecked_Access;
      alloc_Selector : SEL := sel_get_uid ("alloc" & ASCII.NUL);
      alloc_Body : alloc := objc_msg_lookup (My_Class_As_id, alloc_Selector);
      Creating_Object : id := alloc_Body (My_Class_As_id, alloc_Selector);
      init_Selector : SEL := sel_get_uid ("init" & ASCII.NUL);
      init_Body : alloc := objc_msg_lookup (Creating_Object, init_Selector);
      Object : id := init_Body (Creating_Object, init_Selector);
   begin
      declare
         name_Selector : SEL := sel_get_uid ("name" & ASCII.NUL);
         name_Body : name := objc_msg_lookup (Object, name_Selector);
         Name : not null access constant Character :=
            name_Body (Object, name_Selector);
         procedure puts (s : not null access constant Character);
         pragma Import (C, puts);
      begin
         puts (Name);
      end;
      declare
         put_Selector : SEL := sel_get_uid ("put" & ASCII.NUL);
         put_Body : IMP := objc_msg_lookup (Object, put_Selector);
         Returned : id := put_Body (Object, put_Selector);
      begin
         null;
      end;
      declare
         free_Selector : SEL := sel_get_uid ("free" & ASCII.NUL);
         free_Body : alloc := objc_msg_lookup (Object, free_Selector);
         Returned : id := free_Body (Object, free_Selector);
      begin
         null;
      end;
   end;
end gnu_step3_inh;

実行結果。

init
My_Class
put( 123)

この後も、カテゴリだのプロトコルだのObjectiveな機能はまだまだあります。
そして何よりも大きいのは、Objective-Cは、gccのランタイムとAppleのランタイムが全然違うという事実……。
次回は、今回のサンプルのAppleランタイムバージョンをやりたいと思います。安楽OOPのはずがひどく手間がかかったような感じですが、AppleランタイムならGCもあるので、きっとずっと楽なはず。乞うご期待。最終目標は勿論Cocoaです。