[教程]基于 RTTI 创建特定类型的实例

在 Delphi 人群中,有些人总想通过名称,然后动态创建指定类的实例而不得其门而入,不得不将类型先注册,然后再回调解决这个问题。实际上,通过 RTTI 来创建实例是有办法的。下面是基本的步骤:

1、找到类型对应的 TRttiType 信息。这里有几个方法:

  • GetType 方法,这个需要知道目标类型的 TClass/PTypeInfo 信息。
  • FindType 方法,这个需要知道目标类型的完整名称,我知道这就是许多人想要的,通过它可以找到对应的 TRttiType 信息。
  • 自己遍历所有的类型,找到符合自己要求的目标类。
    下面是参考的代码:
function FindType(const AName:String):TRttiType;
var
   I:Integer;
   ATypes:TArray<TRttiType>;
begin
  Result:=nil;
  ATypes:=TRttiContext.Create.GetTypes();
  for I:=0 to High(ATypes) do
  begin
    //注意这种直接比较名称的局限性,如果不同单元有同名的类型,可能会得到你不想要的结果,更保险的是用 QualifiedName,它包含了单元名,比如 test.TTest,这样可以避免单元间重名的问题,但参数传过来时,也要注意。这里如果不要区分大小写,换成
    //if CompareText(ATypes[I].Name,AName)=0 then
    //如果考虑的更多一点,将 Package.Name 的检查也加上,避免 DLL 和主程序里,单元名重的问题
    if ATypes[I].Name=AName then
      Exit(ATypes[I]);
  end;
end;

2、不管用什么办法,反正你已经找到了目标类型的 TRttiType 信息,那么现在,你就需要做第二步:想办法找到创建实例的方法。这个操作实例上就牵涉到几个问题:

  • 我们是否允许创建包含纯虚函数的实例。Delphi 本身虽然允许创建,但实际上并不是什么好习惯,我们可以通过 TRttiType.GetMethods 返回对应的方法数组,检查其中是否存在 Code=nil 的函数,来判断它是否包含纯虚方法。
  • 找到符合我们要求的构造函数版本。一般来说,如果用户将指定的构造函数放到 protected /private 里时,说明作者不希望我们去调用它,所以我们只找 public 里的构造函数(如果你放 published 里算我输,反正我找不到放到 published 里的理由)。
    除非事先约定,否则我们无法知道带参数的构造函数的参数该传啥值。所以,我们要创建实例时,该实例必需是我们能够明确掌控的。后面的例子,我们使用的是无参数数的构造函数版本。如果要带参数的版本,比如创建一个 TComponent 的子类实例,那你完全可以将其唯一的参数传递为 Application 一类的值。具体大家自己研究。

3、根据前两步的信息,我们创建出新的实例。参考代码如下

procedure TForm1.Button1Click(Sender:TObject)
var
  AObj:TSimpleObject;
  AType:TRttiType;
  AMethod,AConstructor:TRttiMethod;
  AMethods:TArray<TRttiMethod>;
begin
   //找到类型,这里简化,直接使用已知的类型
   AType:=TRttiContext.Create.GetType(TypeInfo(TSimpleObject));  
   //找到要求的构造函数并检查没有纯虚函数
   AConstructor := nil;
   AMethods := AType.GetMethods;
   for I := 0 to High(AMethods) do
      begin
      if AMethods[I].IsConstructor then
         begin
         if (AMethods[I].Visibility = mvPublic) and
            (Length(AMethods[I].GetParameters) = 0) then
            AConstructor := AMethods[I];
           end
        else if AMethods[I].CodeAddress = nil then
          raise EAbstractError.CreateFmt(SCreateAbstractClass,
            [AType.Name]);
      end;
   if not Assigned(AMethods) then
      raise EConvertError.CreateFmt(SNoDefaultConstructor,[AType.Name]);
   //创建对应类型的实例,注意此时没有调用构造函数,只是分配了内容并初始化了类型信息
   AObj := AType.Handle.TypeData.ClassType.NewInstance;
   try
      // 调用默认构造函数创建实例
      AConstructor.Invoke(AObj, []);
   Except
      on E: Exception do
         begin
         FreeAndNil(AObj);
         raise;
         end;
   end;
end;

好了,本教程到此结果,感谢您到此一游。

分享到: