Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

10.1 Berlinのclass helper仕様変更は全然改良じゃないと思った?

皆さんご存じかと思われますが、Delphi 10.1 Berlinがリリースされました。で、新機能の一覧の中にこんなことがさらっと書いてありました。

Delphi コンパイラのその他の改良点
(中略)
可視性のセマンティクスを実行するため、クラス ヘルパやレコード ヘルパでは、拡張元のクラスやレコードの private メンバにはアクセスできません。

なんと、class helperによるprivateメンバーアクセスが制限されてしまいました。class helper内では可視性がprivateとなっていて公開されていないフィールドの読み書きやメソッドのコールなどができるため、VCLFMXが実装していない機能を自分で追加する時や未修正のバグの対処などで非常に役立つ機能でした。
これが制限されるとなると、こういった処理を記述したソースコードが軒並みコンパイルできなくなるという、バージョン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:まあ最近は破壊的仕様変更も結構多いけど…