views:

836

answers:

3

For example, is there a way to find out that this class has a virtual constructor (at runtime)?

   TMyClass = class(TObject)
     MyStrings: TStrings;
     constructor Create; virtual;   
   end;

For example, in this code I would like to test if the class referenced by Clazz has a virtual constructor:

procedure Test;
var
  Clazz: TClass;
  Instance: TObject;
begin
  Clazz := TMyClass;
  Instance := Clazz.Create;
end;

Is there a simple solution, for example using RTTI, which works in Delphi 6 to 2009?

+3  A: 

Looking through the TypInfo unit, it doesn't look like there's any way to tell if a method is virtual using RTTI or not. If you have a class reference, though, you can probably roll your own method by examining the VMT.

According to Allen Bauer, in an answer to this question, you can find the end of the VMT immediately before the value pointed to by vmtClassName. The first user-defined virtual method (if any) is found at the address of the class reference. In other words, pointer(Clazz)^. Now that you know the start and end points of the user-defined section of the VMT, it shouldn't be too difficult to make a while loop that compares each pointer in the table against the Code section of a method pointer to Clazz.create casted to a TMethod. If you get a match, then it's a virtual method. If not, then it isn't.

Yes, it's a bit of a hack, but it'll work. If anyone can find a better solution, more power to them.

Mason Wheeler
Do I understand correctly that a constructor, if declared virtual, will be referenced in the VMT?
mjustin
Yes, same as any other method.
Mason Wheeler
+2  A: 

You know, the more I think about it, the less I like the answer I gave that ended up getting accepted. The problem is, the code as written can only deal with information known at compile-time. If Clazz is defined as a TClass, then putting Clazz.Create in a TMethod is always going to give you a method pointer to TObject.Create.

You could try defining Clazz as a "class of TMyClass". Thing is, you've already got a virtual constructor there, so it's going to give you the highest-level constructor it can reach that overrides that constructor. But from your comments, it looks like what you're trying to find is a non-virtual constructor (using reintroduce;) that will break your virtual construction. Most likely you're using a factory pattern, where this could be an issue.

The only solution to that is to use RTTI to find the constructor that's actually attached to the class. You can get a method pointer for "the method named Create" and use it in the trick I explained in my other answer. To do this, your base virtual constructor has to be declared published. This will force all methods that override it to also be published. Problem is, someone can still use reintroduce; to declare a non-published constructor higher up, and your scheme comes crashing to the ground. You don't have any guarantees about what descendant classes will do.

There's no technical solution to this question. The only thing that really works is education. Your users need to know that this class is instantiated by a factory (or whatever your reason is for needing a virtual constructor) and that if they reintroduce the constructor in a derived class, it could break things. Put a note in the documentation to this effect, and a comment in the source code. That's pretty much all you can do.

Mason Wheeler
+2  A: 

Michael,

I get your question, but since your sourcecode does not compile, I think you miss the point of your question ;-)

My answer is a bit of an elaboration on what Mason tried to explain in his second answer.

The issue at hand is that your question imples that you have a 'class reference' (like TClass or TComponentClass) that references to a base class that has a virtual constructor. However, TClass doesn't (TClass references a class that has a non-virtual constructor), but TComponentClass does.

You see the difference when disassembling the call to the constructor by using a class reference. When you call a virtual constructor through a class reference, the code is slightly different than when you call a non-virtual constructor:

  • calling a virtual constructor has an indirection
  • calling a non-virtual constructor does a direct call

This disassembly shows what I mean:

TestingForVirtualConstructor.dpr.37: ComponentClassReference := TMyComponentClass;
00416EEC A1706D4100       mov eax,[$00416d70]
TestingForVirtualConstructor.dpr.38: Instance := ComponentClassReference.Create(nil); // virtual constructor
00416EF1 33C9             xor ecx,ecx
00416EF3 B201             mov dl,$01
00416EF5 FF502C           call dword ptr [eax+$2c]
TestingForVirtualConstructor.dpr.39: Instance.Free;
00416EF8 E8CFCDFEFF       call TObject.Free
TestingForVirtualConstructor.dpr.41: ClassReference := TMyClass;
00416EFD A1946E4100       mov eax,[$00416e94]
TestingForVirtualConstructor.dpr.42: Instance := ClassReference.Create(); // non-virtual constructor
00416F02 B201             mov dl,$01
00416F04 E893CDFEFF       call TObject.Create
TestingForVirtualConstructor.dpr.43: Instance.Free;
00416F09 E8BECDFEFF       call TObject.Free

So when you have a variable of type class reference for which the constructor is virtual, and you call that constructor through that variable, you are sure that the actual class in that variable will have a virtual constructor.

You can not determine on which actual class that constructor is implemented (well, not without extra debugging info, for instance from the .DCU, .MAP, .JDBG, or other sources).

Here is the example code that does compile:

program TestingForVirtualConstructor;

{$APPTYPE CONSOLE}

uses
  Classes, SysUtils;

type
  TMyComponentClass = class(TComponent)
    MyStrings: TStrings;
    constructor Create(Owner: TComponent); override;
  end;

constructor TMyComponentClass.Create(Owner: TComponent);
begin
  inherited;
end;

type
  TMyClass = class(TObject)
    MyStrings: TStrings;
    constructor Create();
  end;

constructor TMyClass.Create();
begin
  inherited;
end;

procedure Test;
var
  // TComponentClass has a virtual constructor
  ComponentClassReference: TComponentClass;
  ClassReference: TClass;
  Instance: TObject;
begin
  ComponentClassReference := TMyComponentClass;
  Instance := ComponentClassReference.Create(nil); // virtual constructor
  Instance.Free;

  ClassReference := TMyClass;
  Instance := ClassReference.Create(); // non-virtual constructor
  Instance.Free;
end;

begin
  try
    Test;
  except
    on E: Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

To get back to your original question: When your class reference references a base class having a virtual constructor, you are sure that you will always call a virtual constructor using an indirection. When your class reference references a base class having a non-virtual constructor, you are sure that you will always call a non-virtual constructor using a direct call.

Hope this sheds some more light on your question.

--jeroen

Jeroen Pluimers
Yes, that's exactly the point I was trying to get across in my second post. Good explanation. These things always make more sense (to a programmer who understands what's really going on, at least) when you give an illustration in ASM. +1.
Mason Wheeler
Thanks. It's always tough to get a concise example that both works and explains well :-)
Jeroen Pluimers