Incus J 10 Posted March 26, 2021 I have a class descended from TInterfacedPersistent like this: type TMyClass=class(TInterfacedPersistent,IMyInterface); …and several classes descended from this, all registered with RegisterClasses([TMyClass1, TMyClass2, …etc…]); I’d like to make a very general function to create instances of these (and other classes) at runtime, by simply passing in a string containing the class name: function CreateAnObjectByClassName(AClassName:string):TPersistent; begin result := GetClass(AClassName).Create; end; This kind of works, except that when creating the object, it looks like its ancestor TObject.Create constructor is called, instead of TMyClass2.Create; When I step through the code at runtime I end up at constructor TObject.Create; in the System unit. As a result the newly created object is not initialised correctly. Having just the class name (e.g. 'TMyClass2') as a string, how can I create a genuine instance of that class, as if I had hard coded TMyClass2.Create; ? Share this post Link to post
Remy Lebeau 1393 Posted March 26, 2021 (edited) 11 minutes ago, Incus J said: This kind of works, except that when creating the object, it looks like its ancestor TObject.Create constructor is called, instead of TMyClass2.Create; That is because neither TObject, TPersistent, nor TInterfacedObject have a virtual constructor that your descendants can override. The lowest RTL class that introduces a virtual constructor is TComponent. Quote Having just the class name (e.g. 'TMyClass2') as a string, how can I create a genuine instance of that class, as if I had hard coded TMyClass2.Create; ? You need to add a virtual constructor to TMyClass and have desccendants override it as needed. And then declare your own metaclass type for your creation function to use. For example: type TMyClass = class(TInterfacedPersistent, IMyInterface) public constructor Create; virtual; end; TMyClassType = class of TMyClass; function CreateAnObjectByClassName(const AClassName: string): TPersistent; begin Result := TMyClassType(GetClass(AClassName)).Create; end; ... type TMyClass1 = class(TMyClass) public constructor Create; override; end; TMyClass2 = class(TMyClass) public constructor Create; override; end; RegisterClasses([TMyClass1, TMyClass2, ...]); Edited March 26, 2021 by Remy Lebeau 2 Share this post Link to post
Stefan Glienke 2002 Posted March 26, 2021 Take a look into TActivator.CreateInstance (typename needs to be fully qualified including unit name) 1 1 Share this post Link to post
Incus J 10 Posted March 26, 2021 Thank you Remy - I'll give that a try! From what you've said, I'm guessing it is not going to be possible to make a 'very general' CreateAnObjectByClassName function, because of the necessity to hard code the TMyClassType cast? Share this post Link to post
Incus J 10 Posted March 26, 2021 (edited) Thank you Stefan - I'll take a look at that too - looks like a different approach. Seems quite complex at a glance. Edited March 26, 2021 by Incus J Share this post Link to post
Incus J 10 Posted March 26, 2021 TMyClassType = class of TMyClass; Can the metaclass type TMyClassType be passed into a function as a parameter? TMetaClass...? function CreateAnObjectByClassName(ClassName:string; BaseClass:TMetaClass..?):TPersistent; begin result := BaseClass(GetClass(ClassName)).Create; end; If that's possible, then I can have the caller provide the base class (the ancestor which introduced the virtual constructor) like this: object := CreateAnObjectByClassName('TMyClass2', TMyClassType); I'm not sure what type BaseClass would be - I don't think it's just a TClass. Share this post Link to post
Stefan Glienke 2002 Posted March 26, 2021 57 minutes ago, Incus J said: Thank you Stefan - I'll take a look at that too - looks like a different approach. Seems quite complex at a glance. Yes, it uses RTTI because it does not require the existence of a virtual ctor and can work with parameters as well - if you can go with Remys approach and change your class architecture, then please go with that. Share this post Link to post
Remy Lebeau 1393 Posted March 26, 2021 1 hour ago, Incus J said: Can the metaclass type TMyClassType be passed into a function as a parameter? It can be passed in as a normal parameter, and even used in ClassType comparisons, but it can't be used for type-casts, eg: function CreateAnObjectByClassName(const ClassName: string; BaseClass: TPersistentClass): TPersistent; var Cls: TPersistentClass; begin Cls := GetClass(ClassName); if not Cls.InheritsFrom(BaseClass) then raise Exception.Create('...'); Result := BaseClass(Cls).Create; // <-- doesn't work end; The compiler needs to know the actual class type that exposes the necessary constructor in order to call it directly. Otherwise, you have to resort to using RTTI to call the constructor, as demonstrated in the code Stefan linked to. 1 hour ago, Incus J said: If that's possible, then I can have the caller provide the base class (the ancestor which introduced the virtual constructor) There are alternative ways to accomplish that, just not as clean as what you are thinking. Share this post Link to post
Guest Posted March 26, 2021 (edited) i think that the big problem with this approach is access the real Class-DESIRED.properties not class-GENERIC.properties if you understand me. or be, at end, for pratical use, the code needs know all properties of final-use. ex.: we have TDataset class -> accept any others sub-class but if we have a FDQuery then in fact, using TDataset.PropSpecificFromFDQuery is not knowed by TDataset.... you see me? then, would be needs some like this TFDQuery(xDatasetClass).xxxProp and using Register the Class before, I think that dont help in generic case, because stay more specific use... I dont know if I get explain right???? hug Edited March 26, 2021 by Guest Share this post Link to post
mikak 2 Posted February 8, 2022 (edited) I was searching solution this object creation also and remembered finally that Delphi rtl json library does that. uses ... system.rtti, rest.jsonreflect; var ctx: Trtticontext; a, another: TMybaseClass; begin a := tMyancestorClass.create; //actually no need to call ctx := trtticontext.create; another := TJSONUnMarshal.ObjectInstance(ctx, a.QualifiedClassName) as tmybaseclass; assert(a.classname = another.classname); end; Only default constructor is supported (constructor create) Edited February 8, 2022 by mikak Share this post Link to post
Anders Melander 1782 Posted February 8, 2022 5 hours ago, mikak said: I was searching solution this object creation also and remembered finally that Delphi rtl json library does that. Every Delphi application that contains a form (or frame or datamodule) does it. That's how components and controls gets created when the form is streamed in from the DFM resource. See TReader.GetFieldClass and TReader.ReadComponent Share this post Link to post
Uwe Raabe 2057 Posted February 8, 2022 16 minutes ago, Anders Melander said: Every Delphi application that contains a form (or frame or datamodule) does it. Yes, but it only works for components because TComponent has a virtual constructor. Share this post Link to post
Anders Melander 1782 Posted February 8, 2022 15 minutes ago, Uwe Raabe said: Yes, but it only works for components because TComponent has a virtual constructor. Yes. What's your point? Share this post Link to post
Uwe Raabe 2057 Posted February 8, 2022 The Delphi streaming system knows that it handles TComponent descendants only. That way it can create each instance calling <SomeComponentClass>Create(<Owner>). In contrast TJSONUnMarshal.ObjectInstance works on any class having a parameterless constructor. It cannot rely on TObject.Create for that as it is not virtual. So the mechanism behind both approaches is totally different. I just wanted to emphasize that. 2 Share this post Link to post
Anders Melander 1782 Posted February 8, 2022 Yes, okay. Consider it emphasized - again. Share this post Link to post