lyborko
lyborko

Reputation: 2619

Dynamically created object (providing its classname as a string) do not call its constructor

Here is the object:

TCell = class(TPersistent)
private
  FAlignmentInCell :byte;
public
  constructor Create; virtual;
published
  property AlignmentInCell:byte  read FAlignmentInCell write FAlignmentInCell;
end;

this is its constructor:

constructor TCell.Create;
begin
  inherited;
  FAlignmentInCell:=5;
end;

Here is a function, which dynamically creates any object derived form TPersistent (parameter is class name provided as a string)

function  CreateObjectFromClassName(AClassName:string):TPersistent;
var DynamicObject:TPersistent;
    TempObject:TPersistent;
    DynamicPersistent:TPersistent;
    DynamicComponent:TComponent;
    PersistentClass:TPersistentclass;
    ComponentClass:TComponentClass;
begin
  PersistentClass:=TPersistentclass(FindClass(AClassName));
  TempObject:=PersistentClass.Create;
  if TempObject is TComponent then
         begin
         ComponentClass:=TComponentClass(FindClass(AClassName));
         DynamicObject:=ComponentClass.Create(nil);
         end;
  if not (TempObject is TComponent) then
         begin
         DynamicObject:=PersistentClass.Create; // object is really TCell, but appropriate constructor seems to be not called. 
         end;
  result:=DynamicObject;
end;

My idea is to create new Cell (TCell) like this:

procedure TForm1.btn1Click(Sender: TObject);
var p:TPersistent;
begin
  p := CreateObjectFromClassName('TCell');
  ShowMessage(IntToStr(TCell(p).AlignmentInCell)); // it is 0. (Why?)
end;

When I want to check AlignmentInCell property I get 0, but I expected 5. Why? Is there way to fix it?

Upvotes: 1

Views: 825

Answers (2)

nil
nil

Reputation: 1328

The compiler can't know for sure what value your variable of type TPersistentClass will hold at run time. So he assumes that it is exactly that: a TPersistentClass.

TPersistentClass is defined as a class of TPersistent. TPersistent has no virtual constructor, the compiler will therefore not include a call to dynamically look up the address of the constructor in the VMT of the actual class, but a 'hard-coded' call to the only matching constructor TPersistent has: the one it inherits from its base class TObject.

It might be a decision with reasons I don't know, but if you had chosen to define TCell as following

TCell = class(TComponent)
private
  FAlignmentInCell: byte;
public
  constructor Create(AOwner: TComponent); override;
published
  property AlignmentInCell:byte  read FAlignmentInCell write FAlignmentInCell;
end;

you wouldn't need TempObject and all the decision making in your CreateObjectFromClassName function (and the possible leaks as pointed out by others):

function CreateObjectFromClassName(AClassName:string): TComponent;
var 
  ComponentClass:TComponentClass;
begin
  ComponentClass:=TComponentClass(FindClass(AClassName));
  Result := ComponentClass.Create(nil);  
end;

And make sure to manage the Results life-time as it has no Owner.

Upvotes: 2

Rudy Velthuis
Rudy Velthuis

Reputation: 28806

This is similar to a recent question.

You use TPersistentClass. But TPersistent does not have a virtual constructor, so the normal constructor for TPersistent is called, which is the constructor it inherits from TObject.

If you want to call the virtual constructor, you will have to declare a

type
  TCellClass = class of TCell;

Now you can modify CreateObjectFromClassName to use this metaclass instead of TPersistenClass, and then the actual constructor will be called.

Also, TempObject is never freed. And instead of is, I would rather use InheritsFrom.

I did not test the following, but it should work:

function CreateObjectFromClassName(const AClassName: string; AOwner: TComponent): TPersistent;
var 
  PersistentClass: TPersistentclass;
begin
  PersistentClass := FindClass(AClassName);
  if PersistentClass.InheritsFrom(TComponent) then
    Result := TComponentClass(PersistentClass).Create(AOwner)
  else if PersistentClass.InheritsFrom(TCell) then
    Result := TCellClass(PersistentClass).Create
  else
    Result := PersistentClass.Create;
end;

Upvotes: 5

Related Questions