How to determine if a Delphi class has a virtual constructor?

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

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

For example, in this code, I would like to check 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 until 2009?

+2
rtti delphi delphi-2009
source share
3 answers

Looking through the TypInfo module, it doesn't seem like there is any way to determine if the method is virtual using RTTI or not. However, if you have a reference to the class, you can probably collapse your own method by learning VMT.

According to Allen Bauer, in response to this question , you can find the end of VMT just before the value that vmtClassName points to. The first user-defined virtual method (if any) is found at the class reference address. In other words, pointer(Clazz)^ . Now that you know the start and end points of the user VMT section, it shouldn't be too hard to make a while loop that compares each pointer in the table with the Code section of the method pointer on Clazz.create is thrown into TMethod. If you get a match, then this is a virtual method. If not, then this is not so.

Yes, it’s a little hack, but it will work. If someone can find a better solution, they will have more power.

+4
source share

You know, the more I think about it, the less I like the answer I gave, which eventually gained recognition. The problem is that written code can only process information known at compile time. If Clazz is defined as TClass, then cloning Clazz.Create in TMethod will always give you a TObject.Create method pointer.

You can try defining Clazz as a "TMyClass class". The fact is that you already have a virtual constructor, so it will give you the highest level constructor that can be reached, which overrides this constructor. But from your comments, it seems that you are trying to find a non-virtual constructor (using reintroduce; ) that breaks your virtual construct. Most likely you are using a factory template where this can be a problem.

The only solution for this is to use RTTI to find a constructor that is really class bound. You can get the method pointer for the "method named Create" and use it in a trick, which I explained in another answer. To do this, your base virtual constructor must be declared published . This will force all methods that override it to be published as well. The problem is that someone else can reuse to declare the unpublished constructor above, and your circuit is crashing to the ground. You have no guarantee what the descendant classes will do.

There is no technical solution to this issue. The only thing that really works is education. Your users should be aware that this class is created using the factory (or whatever you do to create the virtual constructor), and that if they reintroduce the constructor in the derived class, this can break things. Put a note in the documentation on this subject and a comment in the source code. That is almost all you can do.

+3
source share

Michael

I ask your question, but since your source code does not compile, I think you will miss the point of your question; -)

My answer is a bit of detailed information about what Mason tried to explain in his second answer.

The problem is that your question shows that you have a "class reference" (for example, TClass or TComponentClass) that refers to a base class with a virtual constructor. However, TClass does not (TClass refers to a class that has a non-virtual constructor), but TComponentClass does.

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

  • virtual constructor call has indirect binding
  • a call to a non-virtual constructor makes a direct call

This showdown 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 such as a reference to a class for which the constructor is virtual, and you call this constructor through this variable, you are sure that the actual class in this variable will have a virtual constructor.

You cannot determine on which particular class this constructor is implemented (well, not without additional debugging information, for example, from .DCU, .MAP, .JDBG or other sources).

Here is an example of the code that compiles:

 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 return to the original question: When a class reference refers to a base class that has a virtual constructor, you are sure that you will always call the virtual constructor using an indirect direction. When a class reference refers to a base class that has a non-virtual constructor, you are sure that you always call a non-virtual constructor using a direct call.

Hope this sheds some light on your question.

- Jeroen

+2
source share

All Articles