Delphi calling a virtual constructor based on the TObject type

346 Views Asked by At

I have an object that is derived from the TStringList object that I call a "TAutoString." It allows you to specify an object type when the list is created. Then each time a new entry is added to the string list, it also creates a copy of the object associated with that string entry. This makes it easy to store all kinds of additional information along with each string. For example:

type TMyObject = class(TObject)
 public
  Cats: integer;
  Dogs: integer;
  Mice: integer;
 end;

MO := TAutoString.Create(TMyObject);

Inside the object, the class information is stored in a class variable:

 private
  ObjectClass: TClass;


constructor TAutoString.Create(ObjClass: TClass);
begin
    inherited Create;
    ObjectClass:=ObjClass;
end;

Now, every time a new item is added, it creates a new object of the specified type:

function TAutoString.Add(const S: string): Integer;
begin
    Result:=inherited Add(S);
    Objects[Result]:=ObjectClass.Create;
end;

I can now add or read information associated with each string entry.

   TMyObject(MO.Objects[25]).Cats := 17;
   D:=TMyObject(MO.Objects[25]).Dogs;

This works great as along as the object doesn't have a constructor. If the object has a constructor, its constructor won't get called when the object is created because the constructor for TObject is not virtual.

Can anyone think of a way around this problem. I've seen solutions that use the RTTI libraries, but this is in Delphi-7, which doesn't have an RTTI library.

As an aside, it seems a bit strange that TObject's constructor is not virtual. If it were, it would enable all sorts of useful features like the one I'm trying to implement.

EDIT: Remy's suggestion below was just the nudge I needed. I had originally tried a similar strategy, but I couldn't make it work. When it didn't seem to work the way I thought it should, I assumed there must be something that I didn't understand about virtual methods. His post pushed me to look at it again. It turned out that I had left off the "Override" directive for the constructor of the object I wanted to attach. Now it works just the way it should.

The other issue I was concerned about was that I had already used the Auto Strings in a bunch of other applications where the object was based on "TObject" and I didn't want to go back and change all that code. I solved that issue by overloading the constructors and having one for TObject-based objects and another my TAutoClass objects:

  constructor Create(ObjClass: TAutoClass); overload; virtual;
  constructor Create(ObjClass: TClass); overload; virtual;

Depending on which constructor is called, the object class stored in a different in a different variable.

 private
  AutoClass: TAutoClass;
  ObjectClass: TClass;

Then when the object is constructed I check to see which has been assigned and use that one:

procedure TAutoString.CreateClassInstance(Index: integer);
begin
   if AutoClass<>nil then Objects[Index]:=AutoClass.Create
   else Objects[Index]:=ObjectClass.Create
end;

The new version works perfectly with either type of object.

2

There are 2 best solutions below

1
Remy Lebeau On BEST ANSWER

To do what you want, you will have to define a base class for your list objects to derive from, and then you can add a virtual constructor to that class. Your ObjectClass member will have to use that class type instead of using TClass.

For example:

type
  TAutoStringObject = class(TObject)
  public
    constructor Create; virtual;
  end;

  TAutoStringObjectClass = class of TAutoStringObject;

  TAutoString = class(TStringList)
  private
    ObjectClass: TAutoStringObjectClass;
  public
    constructor Create(ObjClass: TAutoStringObjectClass);
    function Add(const S: string): Integer; override;
    ...
  end;

...

constructor TAutoStringObject.Create;
begin
  inherited Create;
end;

constructor TAutoString.Create(ObjClass: TAutoStringObjectClass);
begin
  inherited Create;
  ObjectClass := ObjClass;
end;

function TAutoString.Add(const S: string): Integer;
var
  Obj: TAutoStringObject;
begin
  Obj := ObjectClass.Create;
  try
    Result := inherited AddObject(S, Obj);
  except
    Obj.Free;
    raise;
  end;
end;

...

Then, you simply adjust your derived object classes to use TAutoStringObject instead of TObject, eg:

type
  TMyObject = class(TAutoStringObject)
  public
    ...
    constructor Create; override;
  end;

MO := TAutoString.Create(TMyObject);
...

And their constructor will be called, as expected.

0
oOo On

Here is a hint for a cleaner solution:

It is possible to add a virtual constructor to Tobject.

To do so you will need to use what is called a "class helper".

Here is an example:

type
    TobjectHelper = class helper for Tobject
    public
        constructor Create; virtual; // adds a virtual constructor to Tobject.
    end;

(My use case was a TryCreateObject function to detect out of memory situation during object creation, wraps try except end and simply returns true/false to prevent try except blocks in code and instead use more logic controleable if statements)

Class helpers were apperently(?) introduced in Delphi 8 and later. Your requirements is for Delphi 7 so this may not work.

Unless you coding for Windows 95/Windows 98/Windows XP it may be time for you to upgrade to a more recent version of Delphi, especially the Delphi XE versions for unicode support, otherwise you coding against an aging platform that is about to become obsolete etc.

However for Windows 95/Windows 98 and Windows XP I do believe Delphi 2007 may be of some use, I believe it can compile code that can run on those older windows platforms, I could be wrong though.

Later versions of Delphi require certain windows system DLLs to be present otherwise the build/compiled executable will not run, w95/w98/wxp lack these dlls.