
おそらく、ゴム製のアヒルのデバッグ方法と呼ばれるプログラミングの問題を解決する素晴らしい方法を聞いたことがあるでしょう。 この方法の本質は、トイレに座って、リラックスし、水におもちゃのアヒルを置いて、解決策が見つからない問題の本質を彼に説明することです。 そして、奇跡的に、そのような会話の後、解決策が見つかりました。
Habrに関する前回の記事では 、macOS用のWi-Fiネットワークを検査するプログラムの開発について話しましたが、Habrがアヒルの子であることが判明しました。DelphiのObjective-Cからコードブロックを実装する方法を考え出すことができなかった。 そしてそれは助けた! 啓発が来て、それはすべてうまくいきました。 思考の流れと最終結果についてお伝えしたいと思います。
したがって、前の記事を読んでいない人のために、もう一度問題の本質を簡単に概説してください。 コード下ブロック - C ++、及びDelphiでサポートされていないのObjective-Cの言語機能。 より正確には、Delphiはその対応のコードブロックを持っていますが、それは私達のMacOS用APIの各1から期待するコードブロック、と互換性がありません。 実際、多くのクラスには、完了ハンドラーとしてコードブロックを使用する関数があります。 最も単純な例- beginWithCompletionHandlerクラスNSSavePanel
とNSOpenPanel
。 送信されたコードブロックは、ダイアログが閉じたときに実行されます。
- (IBAction)openExistingDocument:(id)sender { NSOpenPanel* panel = [NSOpenPanel openPanel];
アヒルの子と話した後、私は間違った終わりから問題に近づいていることに気付きました。 確かに、この問題はDelphiだけに存在するわけではありません。 したがって、他の言語での問題の解決方法から始める必要があります。 Googleの手、そして私たちは、非常に近いPythonとJavaScriptのための私達のトピックコードにあるこことここ 。 良いスタート:彼らが成功すれば、我々は成功するでしょう。 実際には、我々は、フィールドだけで、正しい形式で塗りつぶしを構造を作成する必要があり、そのような構造体へのポインタで、それによって魔法のランプ、私たちは、私たちはブロックするように期待し、これらのクラスのメソッドのMacOSの、渡すことができるようになりますことを。 もう少しGugleniya、我々は見つけるヘッダーアップルのサイト上に:
struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor;
パスカルでそれを述べています:
Block_Descriptor = packed record Reserved: NativeUint; Size: NativeUint; copy_helper: pointer; dispose_helper: pointer; end; PBlock_Descriptor = ^Block_Descriptor; Block_Literal = packed record Isa: pointer; Flags: integer; Reserved: integer; Invoke: pointer; Descriptor: PBlock_Descriptor; end; PBlock_Literal = ^Block_Literal;
今、ブロックのビットを読み出す(後ブロックが実装されている方法とHabré、 Objective-Cの:両方の作業単位 )最も単純な実施例においては、我々は膝で、確立をブロックするように進みます。
Var OurBlock: Block_Literal; function CreateBlock: pointer; var aDesc: PBlock_Descriptor; begin FillChar(OurBlock, SizeOf(Block_Literal), 0);
ILocalobjectとして'NSBlock')).GetObjectID)。 Var OurBlock: Block_Literal; function CreateBlock: pointer; var aDesc: PBlock_Descriptor; begin FillChar(OurBlock, SizeOf(Block_Literal), 0);
フィールドflags
我々は簡単にするために予備ゼロを行います。 後で便利になります。 今のところ、空のコールバック関数を宣言する必要があります。 コールバック関数への最初の引数は、のインスタンスへのポインタであるNSBlock
、および他のパラメータのリストは、コードブロックの原因となりココアクラスの特定の方法に依存します。 上記の例では、とNSSavePanel
、タイプの単一の引数を持つプロシージャNSInteger
。 それでは、初心者向けに書きましょう。
procedure InvokeCallback (aNSBlock: pointer; i1: NSInteger); cdecl; begin Sleep(0); end;
重要な瞬間、ゴールで撃った:
FSaveFile := TNSSavePanel.Wrap(TNSSavePanel.OCClass.savePanel); NSWin := WindowHandleToPlatform(Screen.ActiveForm.Handle).Wnd; objc_msgSendP2( (FSaveFile as ILocalObject).GetObjectID, sel_getUid(PAnsiChar('beginSheetModalForWindow:completionHandler:')), (NSWin as ILocalObject).GetObjectID, CreateBlock );
。 FSaveFile := TNSSavePanel.Wrap(TNSSavePanel.OCClass.savePanel); NSWin := WindowHandleToPlatform(Screen.ActiveForm.Handle).Wnd; objc_msgSendP2( (FSaveFile as ILocalObject).GetObjectID, sel_getUid(PAnsiChar('beginSheetModalForWindow:completionHandler:')), (NSWin as ILocalObject).GetObjectID, CreateBlock );
:'))、 FSaveFile := TNSSavePanel.Wrap(TNSSavePanel.OCClass.savePanel); NSWin := WindowHandleToPlatform(Screen.ActiveForm.Handle).Wnd; objc_msgSendP2( (FSaveFile as ILocalObject).GetObjectID, sel_getUid(PAnsiChar('beginSheetModalForWindow:completionHandler:')), (NSWin as ILocalObject).GetObjectID, CreateBlock );
ファイル保存ダイアログが開き、[OK]または[キャンセル]を押して...はい! 私たちは、に設定されているポイント、破るために取得Sleep(0)
およびはい、引数i1
、我々がクリックされたダイアログ内のどのボタンに応じて、0または1のどちらかになります。 勝利! アヒルの子と私は幸せですが、多くの仕事があります。
- コールバック引数の数とタイプは異なる場合があります。 最も人気のある特定のセットがありますが、柔軟性が必要です。
- 多数のコードブロックを同時に動作させることができます。 例えば、我々は完了のためのコール完了ハンドラを使用してファイルをダウンロードして、並行して、オープンしてファイル保存ダイアログを閉じることができます。 まず、2番目に作成したブロックコードが機能し、ファイルがダウンロードされると、最初のブロックコードが機能します。 記録を残しておくといいでしょう。
- 何らかの方法でコールバックの原因となったブロックを特定し、このブロックに対応するDelphiコードを呼び出す必要があります。
- それはすべての利便性と美しさを失わずに匿名のデルファイの方法とコードブロックの間の橋渡しをするために素晴らしいことです。 次のような呼び出しをお願いします。
SomeNSClassInstance.SomeMethodWithCallback ( Arg1, Arg2, TObjCBlock.CreateBlockWithProcedure( procedure (p1: NSInteger) begin if p1 = 0 then ShowMessage ('Cancel') else ShowMessage ('OK'); end) );
コールバックビューから始めましょう。 明らかに、最も簡単で信頼性の高い方法は、関数の種類ごとにコールバックを作成することです。
procedure InvokeCallback1 (aNSBlock: pointer; p1: pointer); cdecl; procedure InvokeCallback2 (aNSBlock: pointer; p1, p2: pointer); cdecl; procedure InvokeCallback3 (aNSBlock: pointer; p1, p2, p3: pointer); cdecl;
などなど。 しかし、どういうわけか退屈でエレガントではないでしょうか? したがって、思考はさらに私たちを導く。 あなたは、コールバックの一種類のみを宣言した場合、必要な数の引数を読んで、スタックを引数とクロールの番号を知るために、コールバックを引き起こしたユニットの識別情報をキャプチャしますか?
procedure InvokeCallback (aNSBlock: pointer); cdecl; var i, ArgNum: integer; p: PByte; Args: array of pointer; begin i:= FindMatchingBlock(aNSBlock); if i >= 0 then begin p:= @aNSBlock; Inc(p, Sizeof(pointer));
いい考え? いいえ、悪いです。 これは、32ビットのコードで動作しますが、64ビットコードにはCDECLが起こらないいないため、64ビットで地獄にクラッシュしますが、一つの共通の呼び出し規約、CDECLとは異なり、引数なしでは、スタック内にあり、しかしプロセッサのレジスタに。 それでは、さらに簡単に、次のようなコールバックを宣言します。
function InvokeCallback(aNSBlock, p1, p2, p3, p4: pointer): pointer; cdecl;
そして、必要なだけの引数を読みます。 残りの引数にはゴミがありますが、それらには対処しません。 また、コードブロックで結果が必要な場合に備えて、プロシージャを機能するように変更しました。 免責事項:このアプローチの安全性がわからない場合は、個々のコールバック関数の種類ごとに使用しています。 アプローチはかなり安全だと思うが、彼らが言うように、好みは異なる。
識別部に関しては、すべての非常に簡単であることが判明: aNSBlock
コールバックへの最初の引数として、私たちに来て、まったく同じ示しDescriptor
、我々は、作成時にブロックを割り当てます。
今、あなたは、異なる種類の匿名メソッドを行うことができ、私たちは教室のMacOSの中で実際に発生する、引数の可能なセットの90%をカバーし、我々は常にリストを展開することができます:
type TProc1 = TProc; TProc2 = TProc<pointer>; TProc3 = TProc<pointer, pointer>; TProc4 = TProc<pointer, pointer, pointer>; TProc5 = TProc<pointer, pointer, pointer, pointer>; TProc6 = TProc<NSInteger>; TProc7 = TFunc<NSRect, boolean>; TProcType = (ptNone, pt1, pt2, pt3, pt4, pt5, pt6, pt7); TObjCBlock = record private class function CreateBlockWithCFunc(const aTProc: TProc; const aType: TProcType): pointer; static; public class function CreateBlockWithProcedure(const aProc: TProc1): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc2): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc3): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc4): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc5): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc6): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc7): pointer; overload; static; end;
、PT3、PT4、PT5、PT6、PT7)。 type TProc1 = TProc; TProc2 = TProc<pointer>; TProc3 = TProc<pointer, pointer>; TProc4 = TProc<pointer, pointer, pointer>; TProc5 = TProc<pointer, pointer, pointer, pointer>; TProc6 = TProc<NSInteger>; TProc7 = TFunc<NSRect, boolean>; TProcType = (ptNone, pt1, pt2, pt3, pt4, pt5, pt6, pt7); TObjCBlock = record private class function CreateBlockWithCFunc(const aTProc: TProc; const aType: TProcType): pointer; static; public class function CreateBlockWithProcedure(const aProc: TProc1): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc2): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc3): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc4): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc5): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc6): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc7): pointer; overload; static; end;
; CONST ATYPE:TProcType):ポインタ。 type TProc1 = TProc; TProc2 = TProc<pointer>; TProc3 = TProc<pointer, pointer>; TProc4 = TProc<pointer, pointer, pointer>; TProc5 = TProc<pointer, pointer, pointer, pointer>; TProc6 = TProc<NSInteger>; TProc7 = TFunc<NSRect, boolean>; TProcType = (ptNone, pt1, pt2, pt3, pt4, pt5, pt6, pt7); TObjCBlock = record private class function CreateBlockWithCFunc(const aTProc: TProc; const aType: TProcType): pointer; static; public class function CreateBlockWithProcedure(const aProc: TProc1): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc2): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc3): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc4): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc5): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc6): pointer; overload; static; class function CreateBlockWithProcedure(const aProc: TProc7): pointer; overload; static; end;
したがって、例えば、二つの引数のサイズであり、手順の作成部SizeOf(pointer)
、次のようになります。
class function TObjCBlock.CreateBlockWithProcedure(const aProc: TProc3): pointer; begin result:= CreateBlockWithCFunc(TProc(aProc), TProcType.pt3); end;
):ポインタ。 class function TObjCBlock.CreateBlockWithProcedure(const aProc: TProc3): pointer; begin result:= CreateBlockWithCFunc(TProc(aProc), TProcType.pt3); end;
)、TProcType.pt3)。 class function TObjCBlock.CreateBlockWithProcedure(const aProc: TProc3): pointer; begin result:= CreateBlockWithCFunc(TProc(aProc), TProcType.pt3); end;
CreateBlockWithCFuncは次のようになります。
class function TObjCBlock.CreateBlockWithCFunc(const aTProc: TProc; const aType: TProcType): pointer; begin result:= BlockObj.AddNewBlock(aTProc, aType); end;
; CONST ATYPE:TProcType):ポインタ。 class function TObjCBlock.CreateBlockWithCFunc(const aTProc: TProc; const aType: TProcType): pointer; begin result:= BlockObj.AddNewBlock(aTProc, aType); end;
そうです。 我々はBlockObj、シングルトンクラスのインスタンスにアピールTObjCBlockList
すべてこの経済を管理するために必要とされ、ユニット利用可能外ではありません。
TBlockInfo = packed record BlockStructure: Block_Literal; LocProc: TProc; ProcType: TProcType; end; PBlockInfo = ^TBlockInfo; TObjCBlockList = class (TObject) private FBlockList: TArray<TBlockInfo>; procedure ClearAllBlocks; public constructor Create; destructor Destroy; override; function AddNewBlock(const aTProc: TProc; const aType: TProcType): pointer; function FindMatchingBlock(const aCurrBlock: pointer): integer; procedure ClearBlock(const idx: integer); property BlockList: TArray<TBlockInfo> read FBlockList ; end; var BlockObj: TObjCBlockList;
私たちのクラスの「心」はここで勝ちます:
function TObjCBlockList.AddNewBlock(const aTProc: TProc; const aType: TProcType): pointer; var aDesc: PBlock_Descriptor; const BLOCK_HAS_COPY_DISPOSE = 1 shl 25; begin
CONST ATYPE:TProcType):ポインタ。 function TObjCBlockList.AddNewBlock(const aTProc: TProc; const aType: TProcType): pointer; var aDesc: PBlock_Descriptor; const BLOCK_HAS_COPY_DISPOSE = 1 shl 25; begin
]にSizeOf(TBlockInfo)、 function TObjCBlockList.AddNewBlock(const aTProc: TProc; const aType: TProcType): pointer; var aDesc: PBlock_Descriptor; const BLOCK_HAS_COPY_DISPOSE = 1 shl 25; begin
BlockStructure.Isa:= NSClassFromString((StrToNSStr( 'NSBlock') function TObjCBlockList.AddNewBlock(const aTProc: TProc; const aType: TProcType): pointer; var aDesc: PBlock_Descriptor; const BLOCK_HAS_COPY_DISPOSE = 1 shl 25; begin
:= @InvokeCallback ;. function TObjCBlockList.AddNewBlock(const aTProc: TProc; const aType: TProcType): pointer; var aDesc: PBlock_Descriptor; const BLOCK_HAS_COPY_DISPOSE = 1 shl 25; begin
BlockStructure.Flags:= BLOCK_HAS_COPY_DISPOSE。 function TObjCBlockList.AddNewBlock(const aTProc: TProc; const aType: TProcType): pointer; var aDesc: PBlock_Descriptor; const BLOCK_HAS_COPY_DISPOSE = 1 shl 25; begin
まあ、すべての基本を書きました。 わずかな微妙な点のみが残っています。
まず、異なるスレッドのクラスインスタンスを操作できるように、スレッドセーフを追加する必要があります。 それは非常に単純であり、適切なコードを追加しました。
第二に、私たちは知る必要があり、作成した構造を最終的に「ネイル」することができます。 配列要素FBlockList
。 一見すると、それはすぐにシステムがコールバックを起こしているようとして、ユニットを除去することができるようです: - さんが行われ、すべてのファイルをロードし、完了ハンドラーと呼ばれていました。 実際、これは常にそうではありません。 何度でも呼び出されるブロックがあります。 例えば、この方法ではimageWithSize:ひっくり返さ:drawingHandler:クラスNSImage
万回を発生することが、あなたが知っているように、という絵を描きますブロックへのポインタを渡す必要があります。 それは私たちが便利になることをここにあるaDesc.dispose_helper := @DisposeCallback
。 プロシージャコールDisposeCallback
ちょうどユニットがもはや必要ないことを通知します、あなたが安全に削除することができます。
ケーキの上のチェリー
そして、同じユニットでセルフテストを書きましょうか? コンパイラの次のバージョンまたは64ビットに切り替えると、突然何かが壊れます。 Cocoaクラスにアクセスせずにブロックをテストするにはどうすればよいですか? これには、Delphiで次のように宣言する必要がある特別な低レベル関数があることがわかります。
function imp_implementationWithBlock(block: id): pointer; cdecl; external libobjc name _PU + 'imp_implementationWithBlock'; function imp_removeBlock(anImp: pointer): integer; cdecl; external libobjc name _PU + 'imp_removeBlock';
最初の関数は、引数として渡したブロックを呼び出すC関数へのポインターを返します。 2番目は単にメモリを「クリーン」にします。 我々は素晴らしいクラスの助けを借りて、ブロックを作成する必要がありますので[OK]を、それを渡すimp_implementationWithBlock
、ユニットが働いたかを確認するために受信したアドレスにして屏息で関数を呼び出します。 すべてをやろうとしています。 オプション1、 ナイーブ:
class procedure TObjCBlock.SelfTest; var p: pointer; test: NativeUint; func : procedure ( p1, p2, p3, p4: pointer); cdecl; begin test:= 0; p:= TObjCBlock.CreateBlockWithProcedure( procedure (p1, p2, p3, p4: pointer) begin test:= NativeUint(p1) + NativeUint(p2) + NativeUint(p3) + NativeUint(p4); end); @func := imp_implementationWithBlock(p); func(pointer(1), pointer(2), pointer(3), pointer(4)); imp_removeBlock(@func); if test <> (1 + 2 + 3 + 4) then raise Exception.Create('Objective-C code block self-test failed!'); end;
起動して...おっと。 P1 = 1、P2 = 3、P3 = 4、P4 =デブリ:匿名メソッドに陥ります。 なに...? 誰がデュースを食べましたか? そして、最後のパラメーターのゴミはなぜですか? これは、という事実が判明しimp_implementationWithBlock
あなたのようにブロックを呼び出すことができますトランポリン、返すIMP
。 問題は、あるIMP
のObjective-Cには常に2つの最初の引数、必要としていた(id self, SEL _cmd)
すなわち オブジェクトおよびセレクターへのポインター、およびコードブロックの最初に必要な引数は1つだけです。 引数のリスト編集を呼び出すときにトランポリンが返さ:第二引数は_cmd
、不要として捨て、その場所に最初の引数を書かれているが、最初の引数の代わりにへのポインタを代入するNSBlock
。
はい、このように、トランポリンは気付かれずに忍び寄りました。 さて、第二の選択肢、 右:
class procedure TObjCBlock.SelfTest; var p: pointer; test: NativeUint; func : procedure ( p1, _cmd, p2, p3, p4: pointer); cdecl; begin test:= 0; p:= TObjCBlock.CreateBlockWithProcedure( procedure (p1, p2, p3, p4: pointer) begin test:= NativeUint(p1) + NativeUint(p2) + NativeUint(p3) + NativeUint(p4); end); @func := imp_implementationWithBlock(p);
これですべてがスムーズになり、ブロックの操作を楽しむことができます。 ユニット全体をダウンロードすることができ、ここで以下閲覧します。 コメント(「記憶、ここにあなたの記憶が流れている」)と改善のための提案を歓迎します。
完全なソースコード
、いかなる種類の保証もなく、明示または
商品性の保証、
請求、損害またはその他の
、不法行為もしくはその他から生じます、
.Wnd。
completionHandler:'))、
、
、PT3、PT4、PT5、PT6、PT7)。
; CONST ATYPE:TProcType):ポインタ。
、P3、P4:ポインタ):ポインタ。
.LocProc)();
.LocProc)(P1)。
.LocProc)(P1、P2)。
.LocProc)(P1、P2、P3)。
.LocProc)(P1、P2、P3、P4)。
.LocProc)(NSinteger(P1))。
[I] .LocProc)(aRect))。
):ポインタ。
)、TProcType.pt1)。
):ポインタ。
)、TProcType.pt2)。
):ポインタ。
):ポインタ。
):ポインタ。
):ポインタ。
)、TProcType.pt6)。
):ポインタ。
)、TProcType.pt7)。
; CONST ATYPE:TProcType):ポインタ。
);
P2、P3、P4:ポインタ)。
コードブロックのセルフテストが失敗しました!');
。
CONST ATYPE:TProcType):ポインタ。
]にSizeOf(TBlockInfo)、
BlockStructure.Isa:(ILocalobjectとして(StrToNSStr( 'NSBlock')).GetObjectID)= NSClassFromString。
:= @InvokeCallback ;.
BlockStructure.Flags:= BLOCK_HAS_COPY_DISPOSE。
:= aDesc ;.
)] BlockStructure ;.
:整数。
(aCurrBlock).Descriptor