Free Pascal Hack 之访问对象的 protected 成员

默认情况下,Free Pascal 类中 protected 成员只能被当前类、子类以及同一个 Unit 中的代码访问。然而在实践中,往往会碰到访问其他 Unit 中定义的类实例对象的 protected 成员的需求。通常我们会通过类继承以及修改原始类成员的代码来暴露 protected 成员,然而当该对象来自第三方库,甚至 FCL 和 LCL 时,情况就会比较棘手。有一种 Hack 方法可以实现这个需求,且不用修改原始 Unit 的代码,没有额外副作用。

假设在 unit1.pas 中定义了类 TFoo

pascalunit Unit1; 

interface

type
  TFoo = class
  protected
    FMember: string;
  end;

var
  Foobar: TFoo;

implementation

type
  TBar = class(TFoo);

initialization 

Foobar := TBar.Create;

finalization

FreeAndNil(Foobar);

end.

在 unit2.pas 中访问 Foo.FMember 成员:

pascalunit Unit2; 

interface

uses 
  Unit1;

implementation

type
  TFooAccessProtected = class(TFoo);

function GetFooProtectedMember: string;
begin
  Result := TFooAccessProtected(Foobar).FMember;
end;

end.

虽然 TFooAccessProtectedTBar 两个类型并不在同一条继承链上,但是 TFooAccessProtectedTFoo 对象的内存布局是一致的。而在 Unit2 中的代码是可以访问 TFooAccessProtected 及其父类中所有的 protected 成员的。通过强制类型转换,使得 TFooFMember 暴露在 Unit2 中。

不过需要注意:在 Debug 模式下,Hack 代码 TFooAccessProtected(Foobar) 运行时会抛出 RunError(219) 以及 EInvalidCast 异常。这是因为 Debug 模式开启了编译指令“验证方法调用 (-CR)”。可以在代码里临时禁用该编译器指令:

pascalfunction GetFooProtectedMember: string;
begin
{$IFDEF DEBUG}{$objectChecks-}{$ENDIF}
  Result := TFooAccessProtected(Foobar).FMember;
{$IFDEF DEBUG}{$objectChecks+}{$ENDIF}
end;