Call a protected method (constructor) via RTTI

2020-07-13 13:01发布

问题:

am using XE-2.

Is it possible to invoke a protected method (constructor) using RTTI?

I searched the web but did not find any conclusive answer. I understand that prior to XE only published methods/properties were available. I do have write access to private fields, so I was expecting to be able to invoke protected methods.

The following code works as long as the constructor is 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;

回答1:

RTTI information increases the size of executable files. Because of this the designers provided a means for the developer to specify how much RTTI information is to be linked to the executable.

By default, no RTTI for protected methods is linked. So you have to specify that you want this RTTI to be added. For example, this program

{$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.

produces no output. However, this program

{$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.

outputs

Create

For more information, refer to these documentation topics:

  • http://docwiki.embarcadero.com/RADStudio/en/RTTI_directive_%28Delphi%29
  • http://docwiki.embarcadero.com/RADStudio/en/Working_with_RTTI_Index


回答2:

By the default the RTTI doesn't include info about the protected methods or constructors, However you can use RTTI EXPLICIT Directive to include RTTI info of protected methods like so.

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


回答3:

If you do not need RTTI and were simply under the impression that this was the only way to call a protected method from anywhere other than within a sub-class, then there may another way to achieve what you want.

The protected visibility specifier in Delphi has a useful idiosyncracy: As well as limiting visibility to sub-classes, protected members are also visible to other classes declared in the same unit as those sub-classes. This enables a unit to gain access to the protected members of any arbitrary class by creating a sub-class and using that sub-class rather than the original. You can even use the sub-class to type-cast a reference to an instance that is a sub-class of the required base class incorporating the required protected methods, irrespective of the actual class of the object involved.

Strictly Speaking

This however does NOT apply if the methods are declared as strict protected. But as long as the methods are only protected, then you can use the technique.

By Way of Example

For example, The DestroyWindowHandle method of a VCL TWinControl is a protected method. But using this technique you can call this directly on any instance of a TWinControl, regardless of it's actual class. As a silly demonstration of this, consider a button that for some crazy reason wanted to directly destroy it's own underlying HWND when clicked, without destroying the button itself:

type
  TWinControlEx = class(TWinControl);


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

This allows you access to protected methods on objects, but what about constructors, which are members of classes rather than objects ?

Well, the same principle applies. You can create a sub-class directly of the class you wish to instantiate and use that sub-class to instantiate an instance via the protected constructor as you normally would:

unit Unit2;

interface

   type
     TFoo = class
     protected
       constructor Create;
     end;

implementation

  constructor TFoo.Create;
  begin
    inherited Create;

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

end.

And then in some other unit...

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;    

The caveat in this case however is that the foo instance is actually an instance of TFooEx and not TFoo. This may or may not be a problem in your case. A prime consideration is that any code that includes tests such as:

if foo is TFoo then

Will still work entirely as expected. However, more specific tests such as:

if foo.ClassType = TFoo then

Will not (since foo.ClassType = TFooEx, not TFoo).

Since the methods you are attempting to obtain access to are protected, your sub-class does not have to extend directly from the class that introduces the methods, but can extend any class that derives from that class. So, for example if the actual class you needed to instantiate is itself some sub-class of the (example) TFoo then you simply ensure that your class derives from that one, not the base:

// unit2

     TFoo = class
     protected
       constructor Create; virtual;
     end;

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

And then in another unit...

TFooEx = class(TBar);


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

Advantages

One advantage of this over an RTTI based approach is that it avoids the need to have all that RTTI embedded in your application.

Another advantage is that if the signatures of the protected methods that you call in this way are changed then the code will simply not compile, rather than only failing at runtime. It also means you get assistance from code completion and insight when composing your constructor call, reducing the chance of making an error in the number or type of parameters.