« 2013年4月 | トップページ | 2013年10月 »

2013年5月

2013年5月27日 (月)

本当はあまり何もしてくれないDelphi-ObjectiveCブリッジ(クラスインポート編)

今回は

本当はあまり何もしてくれないDelphi-ObjectiveCブリッジ(クラスインポート編)

です。Windows APIを呼ぶ感覚であんま深く考えないでCocoa APIを呼べるのかなと思ってたのですが、最低限押えておかなきゃいけないことが、結構あるじゃねぇか・・・

ということで、まずは基本的なObjective-Cのクラスのインポートの仕方ですが、既存のインポートの定義を真似ればいいでしょう。例えば、NSString クラスのMacapi.Foundationユニットのインポートは次のようになっています。

見てわかるように、Objective-Cのクラスのクラスメソッド、インスタンスメソッドを定義するDelphiのインターフェース型をそれぞれ用意して、それらを型パラメータとして、TOCGenericImportジェネリッククラスを特化するだけです。

難しくないでしょう。

ちなみに、Objective-Cのクラスのインスタンスメソッドを定義するDelphiのインターフェース型の名前(上記の例ではNSString)で内部でObjecitve-Cのランタイムにクラス情報を問い合わせてるので、ここはインポートするObjective-Cのクラスの名前としっかり合わせておきましょう(もちろん、Case-Sensitiveです)。クラスメソッドを定義するDelphiのインターフェース型の名前(上記の例ではNSStringClass)とTOCGenericImportジェネリッククラスの特化した型の名前(上記の例ではTNSString)は適当でもかまいませんが、ひねくれずに、同じように命名しておけばいいと思います。

また、Delphiのインターフェース型というのは元々、WindowsのCOM(Component Object Model)のインターフェースを扱うために導入されたとの事で、COMのインターフェースを定義する時は、基本的にメソッドの名前よりメソッドを宣言する順番が意味を持ちますが、Delphi-ObjectiveCブリッジでの実装(というより、Objective-Cランタイムでは文字列をベースにしたセレクタというものでメソッドを識別して呼び出す)では、メソッドを宣言する順番は意味を持ちませんが、逆にメソッドの名前(とパラメータの名前)が意味を持ちますので、自分でインポートする時、使用しないメソッドを定義しなくても問題はありません(更に言うと、継承元のインターフェースも別にNSObjectやNSObjectClassである必要もなし・・嘘でした、TOCGenericImportジェネリッククラスの型パラメータにインターフェース型の制約がかかってますね)。

ここまでを踏まえて、次はObjective-Cのオブジェクトのやり取りの仕方を見ていきます。

まずはObjective-Cのオブジェクトへのポインタを入力パラメータとして渡すパターンを見てみます。例えば、NSURL クラスのURLWithStringクラスメソッドを見てみます。

これのMacapi.FoundationユニットのDelphiのインポートの定義を見てます。

入力パラメータの型が対応するDelphiのインターフェース型になってますが、この場合、Delphi-ObjectiveCブリッジの方で自動でDelphiのオブジェクトによってラップされた内部のObjective-Cのオブジェクトのポインタを取り出してくれます。また、次のようにポインタ型で宣言する場合は手動で取り出したりして渡しますが、あえて、こんな面倒な事を誰もしたくないでしょう。

次はObjective-Cのオブジェクトへのポインタを戻り値として返すパターンを見てます。例えば、NSURLクラスのhostインスタンスメソッドを見てます。

これのMacapi.FoundationユニットのDelphiのインポートの定義を見ると、

戻り値の型が対応するDelphiのインターフェース型になってますが、この場合、Delphi-ObjectiveCブリッジの方で自動でDelphiのオブジェクトでラップしてくれますdashdash。ポインタ型で宣言する場合は手動でラップします。

ちなみに、Macapi.Foundationユニットを眺めるとObjective-Cのオブジェクトを戻り値として返すメソッドでも戻り値の型をポイ ンタ型で定義してたり、Delphiのインターフェース型で定義してたりと一貫性がすごく見られないんですけどどういうことなんでしょうかsign02sign02誰か知ってる人がいたら教えて下さいsweat02

で、これで事足りればいいのですが、Cocoa APIを眺めるともう1つパターンがあります。Objective-Cのオブジェクトのポインタを出力パラメータとして返すメソッドです。例えば、NSURLConnection クラスのsendSynchronousRequestインスタンスメソッドを見てみます。

これのMacapi.FoundationユニットのDelphiのインポートの定義を見てます。

とりあえず、第2引数の定義がずっこけてますよねsign02ということで、第3引数と同じように再定義しておきます。

Objective-Cのオブジェクトをポインタとして受け取るように定義したので、いつも通り自分でラップする必要があります。

でも、これだと、いちいちラップするのが面倒なので、戻り値と同じように自動でラップしてもらおうと、例えば、outパラメータを使って、

とDelphiのインターフェース型として定義しても駄目なんですね。現状、戻り値しか自動でラップしてくれません。

というように、中途半端感が拭えなく、本当はあまり何もしてくれないんですdash

うぬぬぬ・・・

ちなみに、これは、エンバカデロによる正式な仕様でもありません。ドキュメントが全くない?ので、Delphi-ObjectiveCブリッジの現状の実装から勝手に読み取ったものですので・・・dashdash

2013年5月23日 (木)

本当はあまり何もしてくれないDelphi-ObjectiveCブリッジ(メモリ管理編)・・・おまけ

前回 のおまけです。前回の最後に書いたように、、Objective-Cのオブジェクトを保持しておこうとしたら、一々、retainだのreleaseだの面倒くさいです。

というか、そもそも、せっかくObjective-CのオブジェクトをDelphiのオブジェクトでラップして自動参照カウントが働くDelphiのインターフェース型を経由して操作させるのなら、ラップする段階でDelphiのオブジェクトに所有権を必ず移して管理させれば楽なような気がするが・・??

現状の実装として、Macapi.ObjectiveCユニットで定義されてるTOCImportクラスがObjective-Cのオブジェクトのポインタをラップしていているのですが、

ポインタ型のIDがObjective-Cのオブジェクトのポインタと思って問題ないのですが、本当に何もしてないんです。

これをラップする段階で所有権をDelphiのオブジェクトに持たせてあげれば、いちいち、Delphiから操作する時にretain/releaseをする必要なくなるんじゃないかなぁーーと。

イメージとしてはこんな感じに。

で、Wrapメソッドもパラメータをとりあえず追加して同じように

まぁ、こんな事すると他の問題があるのかもしれませんが。


2013年5月22日 (水)

本当はあまり何もしてくれないDelphi-ObjectiveCブリッジ(メモリ管理編)

Delphi2010からDelphi XE4にアップグレードしましたdashdashということでさっそくFireMonkeyでMacOS XアプリというかいきなりCocoa APIを直接呼び出そうとして遊んでますが、あまりにもネット上に情報が少なすぎて死ぬ。いや、別に難しい事はするつもりはありません。普通にCocoa APIを呼ぶだけでも、色々調べると、知っておかなきゃいけない事があって面倒くせぇ・・・

というか、XE4を購入しCocoa/Objective-Cの事を調べ初めて、1週間もたってないので、用語とかうまく使いこなせないのであしからずsweat02しかも、根本的に間違ってるかもしれませんsweat01sweat01

ということで、今回は

本当はあまり何もしてくれないDelphi-ObjectiveCブリッジ(メモリ管理編)

です。

まず、基本として、Objective-CにはObjective-C Runtime という一連のただの関数群(DelphiのユニットでいうとMacapi.ObjCRuntimeに宣言されてます)があり、これを使えば、他言語とObjective-Cとのブリッジを作れるとのことです。ということで、Delphiのブリッジでも内部でこれらを使ってObjective-Cのメソッドを呼んだり、操作しています。実際どのようにブリッジしてるかは、Objective-Cのオブジェクトへのポインタがあって、それをDelphiのオブジェクトでラップして、操作する時にDelphiのインターフェース型を経由して、ラップしたObjective-Cのオブジェクトのメソッドを呼んだりしてるぐらいのイメージで当面は十分でしょう。

で、本題のメモリ管理についてです。まず、Objective-Cは主に3つのメモリ管理の仕組みをサポートしてるそうです。

  • 非自動参照カウント(MRC、Manual Reference CountingあるいはMRR、Manual Retain/Release)
  • 自動参照カウント(ARC、Automatic Reference Counting)
  • ガベージコレクション

自動参照カウントはObjective-Cコンパイラが提供してる機能で、コンパイラのサポートなしにObjective-CランタイムだけではDelphiからでは利用できません。実際は非自動参照カウントを使ってます。

で、ポイントはここです。Objective-CのオブジェクトをラップしてるDelphiのオブジェクトはDelphiのインターフェース型を経由してアクセスするので、Delphiのオブジェクト自体はコンパイラによって参照カウントが適切に増減され、参照がなくなったら適切に破棄されるのですが、Delphiのオブジェクト自体はラップしている内部のObjective-Cのオブジェクトの参照カウントつまり所有権に関してはノータッチということです。

ということで、自分で内部のObjective-Cのオブジェクトの参照カウントつまり所有権を適切に管理(retain/release)する必要があります。

具体的に見てみます。とりあえず、NSStringクラスのオブジェクトを作ってみます。

Delphiにヘルパ関数NSSTRが用意されてますが、NSSTRが内部で呼んでいるように、NSStringクラスのクラスメソッドstringWithUTF8Stringを使ってみました。内部のObjective-Cのオブジェクトの所有権を自分でretain/releaseで管理しろといっておきながらいきなりそうしてないsign02ですが、これはこれで問題ないです。というのも返されるNSStringクラスのオブジェクトの所有権を元々もってないからです。ですので、むしろ所有権が必要なければこれはそのままほっておかなければなりません(すでにautoreleaseされ、自動解放プールというものに入れられてるので適当なタイミングでreleaseメソッドが呼ばれる)。

次が問題ですが、他人様のサイトから例をとりあげます。Delphi XE2 から Mac OS X の Cocoa API を呼び出す です。

現在の日付を表示している例ですが、問題はNSDateFormatterです。NSDateFormatterクラスのオブジェクトをクラスメソッドのAllocで作成してますので、作成されたオブジェクトの所有権を既に持っています。ということで自分で、最後にreleaseメソッドを呼んで所有権を放棄しないとメモリリークを起こすよーなsign02。一方、この例ではNSDateとNSStringクラスのオブジェクトをそれぞれNSDateクラスのdateクラスメソッド、NSDateFormatterクラスのstringFromDateインスタンスメソッドから受け取ってますが、これは返されるオブジェクトの所有権をもってないので、releaseメソッドを呼んではいけません。ちなみに、上記はXE2の例ですので、XE2では問題ないのかもしれませんdash

高度なメモリ管理プログラミングガイド のCocoaにおける基本的なメモリ管理方法より

  • 自分が作成したオブジェクトはすべて自分が所有する

    オブジェクトの作成は、「alloc」、「new」、「copy」、「mutableCopy」で始まる名前のメソッ ド(たとえば、alloc、newObject、mutableCopy)で行います。

  • retainメソッドでオブジェクトの所有権を獲得できる

    一般に、(オブジェクト作成メソッドから)受け取ったオブジェクトは、そのメソッドから抜け るまで有効であることが保証されています。また、呼び出し元にそのオブジェクトを返しても安全です。retainメソッドは、(1)アクセサメソッドやinitメソッドの実装で、プロパティ値と して格納するオブジェクトの所有権を得るため、(2)他の処理の副作用でオブジェクトが無効 になるのを防ぐため(“使用中のオブジェクトの割り当て解除を避ける” (17 ページ)を参照) に使います。

  • 所有するオブジェクトが不要になったら、その所有権を放棄する

    オブジェクトの所有権の放棄は、releaseメッセージまたはautoreleaseメッセージを送ることによって行います。そのため、Cocoa用語では、オブジェクトの所有権を放棄することを一般に、 オブジェクトの「解放」と言います。

  • 自分が所有していないオブジェクトの所有権を放棄することはできない

    これは前項までの規則の帰結にすぎませんが、念のためここで述べておきます。

alloc、new、copy、mutableCopyで名前が始まるメソッドを使ってオブジェクトを作成したら所有権を持っているので、必要なくなったらreleaseして放棄しましょう。

しかし、ここまではいいんですが、ちょっと、Objective-Cのオブジェクトをしばらく保持しておこうとすると途端に死にたくなります。

ということで、Delphi側からはノータッチなのでObjective-Cのメモリ管理の仕組みを勉強する必要がありますし、最大の問題は所有権必要ならいちいちretain/releaseとかめんどくせぇだろ・・やばいよこれ・・・・

« 2013年4月 | トップページ | 2013年10月 »

自作ソフトウェア

無料ブログはココログ

メモ