Every Delphi class is defined internally by its vmt—​its virtual-method table. The vmt contains a list of pointers to a class’s virtual methods. It also contains some other class-specific information, including the name, the size, a reference to the parent’s vmt, and run-time type information. A class reference (i.e., a ​​TClass​​ value) is a pointer to the class’s vmt, but not to the start of the table. Instead, it is a pointer to the entry for the first virtual method after the methods introduced in​​TObject​​. Virtual methods declared in ​​TObject​​ reside at negative offsets from the vmt pointer.

Virtual methods

The main purpose of the vmt is to hold pointers to a class’s virtual methods. Instead of calling a method directly, the compiler will generate an indirect method call. A program will use a known index value to find the address of a method.

For example, if a program has a ​​TList​​ variable, it may hold a reference to a ​​TList​​ object, but it may also hold a reference to a​​TObjectList​​, as shown in Listing 1. When the program calls the​​Notify​​ method via that variable, the compiler cannot know in advance whether to call the base ​​TList​.Notify​​ method or to call the overridden method in ​​TObjectList​​ because the compiler does not know what the run-time type of the object is. Instead, the compiler generates code to look at the second entry in the vmt, which is the entry reserved for the ​​Notify​​ method. That method will be at that entry no matter how far down the inheritance chain you go from the​​TList​​ base.

The first field of any object instance contains the object’s run-time type as a class reference, which as I noted above is just a pointer to the class’s vmt. See Figure 1. To call a virtual method, then, a program first dereferences the object reference to get the vmt pointer, and then it dereferences that pointer at the offset corresponding to the desired method to determine the address of that method. At that point the program is ready to jump to that address, just as it would have done all along if the method were not virtual. If the method had not been virtual, then its address would have been compiled directly into the code at the place it was called. The program would not have had to look up the address in the method table.




Listing 1

Code to instantiate two ​​TObjectList​​s


​var
  ObjList1, ObjList2: TList;
begin
  ObjList1 := TObjectList.Create(True);
  ObjList2 := TObjectList.Create(True);
end;​




Figure 1

Illustration of the object and vmt layouts resulting from the code of Listing 1. Note that both ​​TObjectList​​ instances point to thesame vmt.


 


Non-method contents of the vmt

In addition to the pointers to virtual methods, the vmt contains other class-specific information at offsets even farther negative than the​​TObject​​ methods, as shown in Table 1.




Table 1

Non-method vmt offsets, in bytes, according to the Delphi 2005 implementation


Name

Offset

​vmtSelfPtr​

–76

​vmtIntfTable​

–72

​vmtAutoTable​

–68

​vmtInitTable​

–64

​vmtTypeInfo​

–60

​vmtFieldTable​

–56

​vmtMethodTable​

–52

​vmtDynamicTable​

–48

​vmtClassName​

–44

​vmtInstanceSize​

–40

​vmtParent​

–36

​vmtParent​

The table entry at this offset holds a pointer to the vmt of the class’s parent. For ​​TObject​​, which has no parent, this field contains ​​nil​​.

​vmtInstanceSize​

This entry holds the size, in bytes, of an instance of the class. This field gets used by ​​TObject​.NewInstance​​, which allocates memory for a new instance of a class.

​vmtClassName​

At this offset is a ​​PShortString​​ value with the name of the class. The ​​TObject​.ClassName​​ method returns this string.

​vmtDynamicTable​

Here resides a pointer to a list of pointers to the class’s dynamic methods (as opposed to its virtual methods, which are in the vmtitself). The list also contains pointers to all a class’s message handlers, which means the ​​Dispatch​​ method relies on this list, too.

​vmtMethodTable​

This entry holds a pointer to method-name information, which the​​TObject​.MethodName​​ and ​​MethodAddress​​ methods use to fetch their results.

​vmtFieldTable​

This entry is to field addresses as ​​vmtMethodTable​​ is to method addresses.

​vmtTypeInfo​

At this offset, the table holds a pointer to the class’s run-time type information. Recall that to have rtti, a class must have been compiled in the ​​$M+​​ compiler state or descend from a class compiled in that state (such as ​​TPersistent​​). Other classes will just contain ​​nil​​ in this field.

​vmtInitTable​

​TObject​.CleanupInstance​​ uses the data structure pointed to at this offset to know which of an object’s fields need to be cleaned up specially while the object is being destroyed. Those fields are of the same types as ​​the ones that get cleaned up when a dynamic array gets freed​​.

​vmtAutoTable​

At this offset is a pointer to a class’s automation table, which holds a list of method entries, including their names, dispids, and parameter lists. The ​​TAutoObject​​ class uses this table to implement the ​​IDispatch​​ interface. The compiler generates the list based on the methods declared in the class’s ​​automated​​ section.

​vmtIntfTable​

The value at this offset provides the result for the ​​TObject​.GetInterfaceTable​​ method, which is used in two places.

  1. The first is in the implementation of GetInterfaceEntry, which is used most often to implement IUnknown​.QueryInterface. The method searches the interface table of the class and of any parent classes, if necessary, for an entry with a matching guid.
  2. GetInterfaceTable also occurs in the implementation ofInitInstance. That method sets all a new object’s fields to all-bits-zero, but then it uses the interface table to initialize any hidden interface-method-table pointers to appropriate values. I describe those hidden pointers in a separate article.

​vmtSelfPtr​

This entry marks the end of the vmt. It holds a pointer back to the beginning of the table. For example, at the ​​vmtSelfPtr​​ offset of​​TButton​​’s vmt will be the value ​​TButton​​.


Deprecated vmt-offset constants

When using some of the vmt-offset constants, such as ​​vmtDestroy​​ or​​vmtDefaultHandler​​, the compiler may issue a warning that the constants are deprecated. In that case, use the ​​vmtoffset​​ assembler keyword with the name of the method you need.