Jump to content
Incus J

How to create an object instance from its class name?

Recommended Posts

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
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 by Remy Lebeau
  • Thanks 2

Share this post


Link to post

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

Thank you Stefan - I'll take a look at that too - looks like a different approach.  Seems quite complex at a glance.

Edited by Incus J

Share this post


Link to post
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
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
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

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 by Guest

Share this post


Link to post

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 by mikak

Share this post


Link to post
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
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
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

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.

  • Like 2

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×