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

时间:2015-04-14 17:40:28

标签: delphi delphi-xe2

我正在使用XE-2。

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

我在网上搜索但没有找到任何确凿的答案。据我所知,在XE之前,只有已发布的方法/属性可用。我对私有字段有写访问权限,所以我希望能够调用受保护的方法。

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

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;

3 个答案:

答案 0 :(得分:10)

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

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

答案 1 :(得分:8)

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

有关更多信息,请参阅以下文档主题:

答案 2 :(得分:4)

如果你不需要RTTI而且只是觉得这是从子类以外的任何地方调用受保护的方法的唯一方法,那么可能还有另一种方法可以实现你想要什么。

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

严格说话

如果方法声明为严格保护,则 NOT 适用。但只要方法只是受保护,那么您就可以使用该技术。

通过示例

例如,VCL TWinControl DestroyWindowHandle 方法是受保护的方法。但是使用这种技术,你可以直接在 TWinControl 的任何实例上调用它,无论它是什么类型的实际类。作为一个愚蠢的演示,考虑一个按钮,出于某些疯狂的原因想要在点击时直接销毁它自己的底层HWND,而不会破坏按钮本身:

type
  TWinControlEx = class(TWinControl);


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

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

嗯,同样的原则适用。您可以直接创建要实例化的类的子类,并像通常那样使用该子类通过受保护的构造函数实例化实例:

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 )。

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

// 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嵌入到您的应用程序中的需要。

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

相关问题