Contents Tools Components
Art of Programming Links Contact me

Example of OOP (Win32 Asm)

Polymorphic Object Layout

Let's start with the description of how the object instance looks like in memory and how all parts interact with each other.

The object itself is a structure with the first member as a pointer to the Virtual Methods Table (VMT).

VMT is also a structure with the pointers to virtual functions.


All pointers are DWORD values as dictated by Win32 platform.

Object instance also can include any member variables which will follow the VMT address (VMT Offset on Pic. 1).

Here I should mention that not all methods of the class should be mapped into VMT, but only the 'virtual' methods which can override the class behaviour. Virtual function call is slower than normal static call linked by LINKER.


Multiple Objects

The VMT is nothing more than a storage of pointers to virtual functions.

Since the class behaviour is the same for all the instances of objects of that class - we do not need to keep the VMT for each object. It will be the waste of room.

Instead, (Pic. 2) we will have the single instance of VMT and all object instances will have the same VMT Offset value.


Inheritance

When the object gets derived from a base class - it can do the following: (Pic. 3)

  • Append more data at the end of the base class structure
  • Append more virtual methods at the end of base class VMT

At this point I have to mention a couple of important things.

Both object structure and VMT structure must be altered only by APPENDING additional values AFTER the base class members. Easiest way to do it is to use nested structures.

Also, even if the derived class has the same VMT as a base class - the VMT should be declared and defined anyway. Every class must have its own VMT for good maintainability of the project. If some classes will share VMT implementation and you have to modify just one of them you will be in trouble doing so, because modifying the shared VMT will modify behaviour of all classes attached to it.


Polymorphism

To override the virtual method we simply have to replace the VMT entry at the moment of its initialization: (Pic. 4).

The VMT initialization is best to be done at compile time, and not at run-time, to avoid performance related problems. Implementation of all parts will be discussed later on.


Construction/Destruction

Now it is time to talk about how we can initialize/release the object. VMT of every class will always contain both constructor and destructor functions as first two virtual entries: (Pic. 5).

Every class has both of them - again - it may seem like an overhead, but in the long run you will see its benefits.

Implementation of the constructor at the very least can provide the clearing of the member variables and pointers, etc.


The fixed location of constructor and destructor virtual pointers allows certain coding benefits:

  • Macros can be developed to perform seamless initialization/release of objects.
  • It will be possible to call virtual methods inside the constructor code, because the VMT is attached by a macro BEFORE the constructor is called. Take that, C++!

The body of the constructor of a derived class should look like this:

  • Call the base implementation of constructor
  • Perform additional initialization, required for the object

The body of the destructor of a derived class should look like this:

  • Perform the full release code, required for the object
  • Call the base implementation of destructor

This coding convention will allow the chain of construction/destruction to be called by a single call to virtual entry in VMT of a class derived a few times.


Implementation

Now, enough with the theory!
Let's get our hands dirty with code!

  • To avoid a mess and promote good coding practice we will use two files:
    • .Asm - all class code implementations go here
    • .Inc - all class definitions go here
    To use the class or inherit from it we just have to include its .Inc file and we are done.
  • One of the terms in OOP - so called 'this' pointer. Every time the class method is called that pointer is passed as a parameter, so the code 'knows' which instance of the class it supposed to use. We have to assume some register for that purpose. It can be anything, but in my opinion the best choice is EBX. EAX is busy with return values, ESI, EDI can be used as string pointers. ECX is a counter... Anyhow, the macros you will see in this example are using EBX as 'this' pointer.
  • To make code easier to read we will derive all identifiers from a class name. Say, we need to implement class xxiHeap. Then its constructor will be named xxiHeap_Constructor and its VMT structure will be named xxiHeap_VtblDef. The prefixes/postfixes can be different, but class name should be clearly visible. Makes perfect code even for large applications!
  • If you ever coded the multi-module application - you know, that in order to use a function, you must declare it Public in the .Asm file where the function body is located and Extrn in all other files. To avoid the hassle I designed a tricky macro:

    Further, I will use this macro in the .Inc file to expose the class methods:

    Finally, before it is included into our .Asm implementation file - I will define the class_ImplHere value and my macro will declare all methods as Public. In all other files they will be declared as Extrn. The cool thing about it is that you need only to declare your class method once!

  • Here is how we can define the class structure and its VMT:

    Both definitions should be located inside .Inc file.

  • OK... Now about VMT - we talked a little while ago about the objects of the same class sharing the single VMT instance. The best way to go about it is to declare and initialize it in .DATA section of the .Asm file for that class. Well, of course, we can allocate it and assign the virtual methods manually (by the code), but that will be larger and slower. Win32 Flat Model has plenty of room, so we will go most logical way. Here it is:

    As you can see - the single instance of VMT will be called class_VtblImpl and also must be exposed to other modules from .Inc file. Macro of the similar nature as ExposeMethod can be used for that.

  • Finally, we are now completed all parts! Both files for our module are ready. It is time now to make it work. The following questions should be answered:
    • How to declare an object instance?
    • How to construct/destruct the object?
    • How to call object methods (virtual and static)?

    In the next step we will see how macros can be used for that purpose. Macros are good anytime, but here - when we need details - macros will help us to produce very clean code... Read on, please!

  • Object instance can be declared in two ways:
    • As a structure. It can be located inside .DATA section, as a local variable or as part of another object:

      In all of these cases the address of an object can be taken with LEA instruction. Obviously, any file using the class should include the .Inc file for that class.

    • As a pointer to structure (I will explain that case little below)
  • To initialize the object - corresponding macro will do the following:
    • Load the address of the object into EBX ('this' pointer)
    • Attach the VMT to the object
    • Call the object constructor from its virtual table

  • To release the object - corresponding macro will do the following:
    • Load the address of the object into EBX ('this' pointer)
    • Load the VMT offset from the object
    • Call the object destructor from its virtual table

    Cool! This time we do not need to pass a class name. Both macros are using EAX, but still there are registers left to pass some parameters...
  • Well, I promised to talk about dynamically allocated objects. This time our initialization macro will allocate room for the object and then perform the same operations as before:

    This macro assumes that Allocate is a name of your allocator function which takes block size in EAX and returns the pointer to the block in the same EAX. You have to adjust the macro if this assumption is wrong. Hint: if you will make your allocator return EBX - you can make macro faster - no need for loading 'this'. Of course, Allocate should be exposed as global function to use anytime.
  • To release a pointer to object we have to call its destructor first and then deallocate memory:

    Again, here I am making an assumption about FreeMem.
  • Whoa! I think I am done here... I am pleased to say that all we have learned here can be coded pretty fast if you will use the utility from Tools.