10.1 Berlinのclass helper仕様変更は全然改良じゃないと思った?
皆さんご存じかと思われますが、Delphi 10.1 Berlinがリリースされました。で、新機能の一覧の中にこんなことがさらっと書いてありました。
Delphi コンパイラのその他の改良点 (中略) 可視性のセマンティクスを実行するため、クラス ヘルパやレコード ヘルパでは、拡張元のクラスやレコードの private メンバにはアクセスできません。
なんと、class helperによるprivateメンバーアクセスが制限されてしまいました。class helper内では可視性がprivateとなっていて公開されていないフィールドの読み書きやメソッドのコールなどができるため、VCLやFMXが実装していない機能を自分で追加する時や未修正のバグの対処などで非常に役立つ機能でした。
これが制限されるとなると、こういった処理を記述したソースコードが軒並みコンパイルできなくなるという、バージョン1からの互換性を謳い文句にさえしていたDelphi*1とは思えない愚行です。
とはいえ制限された形でリリースされた以上、何らかの対応を取らなければならないわけで、早速Stack Overflowにも質問が寄せられてるみたいです。しかしそこで回答として寄せられている方法はclass helperを使わずに対処するものなので、それぞれ一長一短があります。
そこで制限を回避して、class helper内からprivateメンバーに再びアクセスする方法を紹介します。
この方法もx86/x64限定という短所はあるにはあるんですが、その代わり以前と同じようにclass helperからprivateメンバーへアクセスすることができます。答えとしては単純なもので、インラインアセンブラを使用するだけです。
unit UnitA; type THoge = class private FPrivateValue: Integer; procedure PrivateMethod; end; end. unit UnitB; type THogeHelper = class helper for THoge public function GetValue: Integer; procedure CallMethod; end; function THogeHelper.GetValue: Integer; asm MOV EAX,Self.FPrivateValue end; procedure THogeHelper.CallMethod; asm CALL THoge.PrivateMethod end;
このようにインラインアセンブラでアクセスすることで10.1 Berlinで導入された制限を回避できます。
たぶんインラインアセンブラで制限することを忘れてるだけなので、アップデートか何かで塞がれそうな気もしますが、少なくともそれまでの繋ぎとしては使えそうです。
(追記)
メソッドコールは引数があると全部アセンブラ上で積まないといけなくて面倒だと思うので、次のような方法を取るとわりと簡単にコールできます。
type THoge = class private procedure PrivateMethod(Arg1, Arg2, Arg3: Integer); end; // 方法1 // メソッドポインタだけ頂く(メソッドポインタをどこかに代入する必要がある等の場合) type THogePrivateProc = procedure(Self: THoge; Arg1, Arg2, Arg3: Integer); THogePrivateMethod = procedure(Arg1, Arg2, Arg3: Integer) of object; function THogeHelper.GetMethodAddr: Pointer; asm LEA EAX,THoge.PrivateMethod end; var hoge: THoge; proc: THogePrivateProc; method: THogePrivateMethod; begin // こっちの方法でもいいし、 proc := hoge.GetMethodAddr; proc(hoge, 1, 2, 3); // こっちの方法でもいい TMethod(method).Code := hoge.GetMethodAddr; TMethod(method).Data := hoge; method(1, 2, 3); end; // 方法2 // ジャンプさせる(単純に呼び出すだけならこっちが簡単) procedure THogeHelper.CallMethod(Arg1, Arg2, Arg3: Integer); asm JMP THoge.PrivateMethod end;
*1:まあ最近は破壊的仕様変更も結構多いけど…