在 Delphi 人群中,有些人总想通过名称,然后动态创建指定类的实例而不得其门而入,不得不将类型先注册,然后再回调解决这个问题。实际上,通过 RTTI 来创建实例是有办法的。下面是基本的步骤:
1、找到类型对应的 TRttiType 信息。这里有几个方法:
- GetType 方法,这个需要知道目标类型的 TClass/PTypeInfo 信息。
- FindType 方法,这个需要知道目标类型的完整名称,我知道这就是许多人想要的,通过它可以找到对应的 TRttiType 信息。
- 自己遍历所有的类型,找到符合自己要求的目标类。
下面是参考的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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、根据前两步的信息,我们创建出新的实例。参考代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
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; |
好了,本教程到此结果,感谢您到此一游。