通过 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 TWinControl 的 DestroyWindowHandle 方法是一个 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 嵌入到您的应用程序中的需要。
另一个优点是,如果您以这种方式调用的受保护方法的签名发生更改,那么代码将根本无法编译,而不仅仅是在运行时失败。这也意味着您在编写构造函数调用时可以从代码完成和洞察力中获得帮助,从而减少在参数数量或类型方面出错的可能性。
我正在使用 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 TWinControl 的 DestroyWindowHandle 方法是一个 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 嵌入到您的应用程序中的需要。
另一个优点是,如果您以这种方式调用的受保护方法的签名发生更改,那么代码将根本无法编译,而不仅仅是在运行时失败。这也意味着您在编写构造函数调用时可以从代码完成和洞察力中获得帮助,从而减少在参数数量或类型方面出错的可能性。