通过 RTTI 调用受保护的方法(构造函数)

Call a protected method (constructor) via RTTI

我正在使用 XE-2。

是否可以使用 RTTI 调用受保护的方法(构造函数)?

我在网上搜索过,但没有找到任何确定的答案。据我了解,在 XE 之前仅发布 methods/properties 可用。我确实有对私有字段的写入权限,所以我希望能够调用受保护的方法。

只要构造函数是public,下面的代码就可以工作。

function GetDefaultConstructor(aRttiType: TRttiType): TRttiMethod;
var
   Method: TRttiMethod;
begin
   for Method in aRttiType.GetMethods('Create') do
   begin
      if (Method.IsConstructor) and (length(Method.GetParameters) = 0) and (Method.Parent = aRttiType) then
         Exit(Method);
   end;
   Result := nil;
end;

默认情况下,RTTI 不包含有关受保护方法或构造函数的信息,但是您可以使用 RTTI EXPLICIT 指令来包含受保护方法的 RTTI 信息。

  {$RTTI EXPLICIT METHODS([vcPrivate, vcProtected, vcPublic, vcPublished])}
  TFoo= class
  protected
    constructor Create;
  end;

RTTI 信息增加了可执行文件的大小。因此,设计人员为开发人员提供了一种方法来指定将多少 RTTI 信息链接到可执行文件。

默认情况下,没有链接受保护方法的 RTTI。所以你必须指定你想要添加这个 RTTI。比如这个程序

{$APPTYPE CONSOLE}

uses
  System.TypInfo, System.Rtti;

type
  TMyClass = class
  protected
    constructor Create;
  end;

constructor TMyClass.Create;
begin
end;

var
  ctx: TRttiContext;
  method: TRttiMethod;

begin
  for method in ctx.GetType(TMyClass).GetMethods do
    if method.Visibility=mvProtected then
      Writeln(method.Name);
end.

不产生任何输出。但是,这个程序

{$APPTYPE CONSOLE}

uses
  System.TypInfo, System.Rtti;

type
  {$RTTI EXPLICIT METHODS([vcProtected])}
  TMyClass = class
  protected
    constructor Create;
  end;

constructor TMyClass.Create;
begin
end;

var
  ctx: TRttiContext;
  method: TRttiMethod;

begin
  for method in ctx.GetType(TMyClass).GetMethods do
    if method.Visibility=mvProtected then
      Writeln(method.Name);
end.

产出

Create

有关详细信息,请参阅以下文档主题:

如果您不需要 RTTI,并且只是觉得这是从子 [=100= 之外的任何地方调用 protected 方法的唯一方法]],那么可能还有另一种方法可以达到你想要的效果。

Delphi 中的 protected 可见性说明符有一个有用的特性:除了将可见性限制在 class 之外, protected 成员也对其他 classes 在同一单元中声明的其他 classes 成员可见。这使一个单位能够通过创建子class并使用该子class而不是原始子class来访问任意class的受保护成员。您甚至可以使用 sub-class 对实例的引用进行类型转换,该实例是所需基础 class 的 sub-class 并包含所需的受保护方法,而不管实际class 涉及的对象。

严格来说

然而,如果方法被声明为 strict protected,这 适用。但是只要这些方法只是受保护,那么您就可以使用该技术。

举例

例如,VCL TWinControlDestroyWindowHandle 方法是一个 protected 方法。但是使用这种技术,您可以直接在 TWinControl 的任何实例上调用它,而不管它是实际的 class。作为对此的愚蠢演示,考虑一个按钮,出于某种疯狂的原因想要在单击时直接销毁它自己的底层 HWND,而不销毁按钮本身:

type
  TWinControlEx = class(TWinControl);


procedure TMyForm.MyButtonClick(Sender: TObject);
begin
  TWinControlEx(Sender).DestroyWindowHandle;  // button window is destroyed but the button itself remains!
end;

这允许您访问对象的受保护方法,但是构造函数呢,它们是 classes 的成员而不是对象?

嗯,同样的原则也适用。您可以直接从您希望实例化的 class 中创建一个子 class,并像往常一样使用该子 class 通过受保护的构造函数实例化一个实例:

unit Unit2;

interface

   type
     TFoo = class
     protected
       constructor Create;
     end;

implementation

  constructor TFoo.Create;
  begin
    inherited Create;

    // Foo specific, protected initialisation here....
  end;

end.

然后在其他单元中...

unit Unit1;

  ..

implementation

  uses
    Unit2;

  type
    TFooEx = class(TFoo);


  procedure TMyForm.FormCreate(Sender: TObject);
  var
    foo: TFoo;
  begin
    foo := TFooEx.Create;  // << the protected constructor!
  end;    

然而,在这种情况下需要注意的是 foo 实例实际上是 TFooEx 的实例,而不是 TFoo 。在您的情况下,这可能是问题,也可能不是问题。一个首要的考虑是任何包含测试的代码,例如:

if foo is TFoo then

仍将完全按预期工作。但是,更具体的测试如:

if foo.ClassType = TFoo then

不会(因为foo.ClassType = TFooEx,而不是TFoo) .

由于您尝试获取访问权限的方法受到保护,您的子class不必直接扩展 来自引入方法的 class,但可以扩展 派生自 class 的 的任何 class。因此,例如,如果您需要实例化的实际 class 本身是(示例)TFoo 的某个子 class,那么您只需确保您的 class 派生自那个,而不是基数:

// unit2

     TFoo = class
     protected
       constructor Create; virtual;
     end;

     TBar = class(TFoo)
     protected
       constructor Create; override;
     end;

然后在另一个单元...

TFooEx = class(TBar);


procedure ....;
var
  foo: TFoo;
begin
  foo := TFooEx.Create;  // << creates a TBar using protected TBar constructor
end;

优势

与基于 RTTI 的方法相比,它的一个优点是它避免了将所有 RTTI 嵌入到您的应用程序中的需要。

另一个优点是,如果您以这种方式调用的受保护方法的签名发生更改,那么代码将根本无法编译,而不仅仅是在运行时失败。这也意味着您在编写构造函数调用时可以从代码完成和洞察力中获得帮助,从而减少在参数数量或类型方面出错的可能性。