How To: OOP in assembly language
This post is a response to forum question posted by modchip:
"Recently, I've seen a lot of stuff about doing OOP in assembly language. Most of the stuff I've seen almost fried my brain. So... does anybody have some simple (by simple, I mean very simple) examples to get n00bs like me started? What are the basic requirements? What are its advantages? I would definitely want to learn more about this."
Definition:
Objects in assembly is implemented only by using structure (which serves as the class definition), defined to have its first member as a pointer to the class's virtual table (vtable), something like this:
Animal struc
vtable dd 0
m_kind db 32 dup (0)
Animal ends
Instance methods on the other hand are written as normal procedure having the “instance of the class“ as the first argument, known as the `this’ pointer, see these two methods for the Animal class I’ve written:
GetKind@Animal proc $this:dword
mov eax, $this ; setup the $this argument to EAX
add eax, offset Animal.m_kind ; Add the offset of member variable m_kind
ret ; Return it
GetKind@Animal endp
SetKind@Animal proc uses esi edi $this:dword, lpValue:dword
invoke GetKind@Animal, $this ; Get the address of the member variable
invoke szCopy, lpValue, eax ; Set it with different value
xor eax, eax
ret
SetKind@Animal endp
As you can see, there’s nothing fancy about writing methods, all you need is a naming convention that will help you work with unique namings. I use $this as the name of the `this’ pointer, and for method name, I use method name + ‘@’ character + name of the class who owns the method, thus GetKind@Animal and SetKind@Animal. For overloaded methods, I just append '@' + size of arguments. Something like, if I overloaded GetKind@Animal with argument for catching the output, I'll be writing it as: GetKind@Animal@4 where @4 means 1 DWORD argument pass the $this pointer.
Inside each methods, all you need is knowledge of working with assembly structures to get around with the $this pointer. In my example, I simply added the offset of the member I’m interested in, with the $this pointer:
mov eax, $this ; setup the $this argument to EAX
add eax, offset Animal.m_kind ; Add the offset of member variable m_kindmy_class struc
vtable dd 0
my_class ends
Note: you only need vtable if there are virtual members defined (members that can be overridden by derived classes). I'll get to that on my next post.
This structure also holds all instance variables for that class.
Let’s assume we have a class called Animal with instance variable m_kind as 32 bytes, then our structure will be written like this:
That’s how I set register to point to the correct member variable. The code above, EAX now holds the effective address of Instance.m_kind. Then we can return it, or perform whatever logic we desire to that member variable.
Instanciation:
Creating an instance of the class is simply declaring a variable of type structure we’ve defined earlier:
.data
AnimalInstance Animal <>
Then call the constructor as "the first thing" before using the instance (if you want). C++ calls the constructor automagically for you (even creating default constructor for you), but in assembly, it’s up to you whether you want to call the constructor or not (you can actually implement constructorless objects).
Ok, assuming we've declared a default constructor ctor@Animal, calling it is similar to calling assembly procedure, passing the instance as the first argument:
push offset AnimalInstance
call ctor@Animal
Similar to calling the constructor above, calling the method is no different. Let’s assume we want to set the animal’s kind to “Bird” by calling the methof SetKind@Animal, you can do it this way:
.data
AnimalInstance Animal <>
animalInstanceKind db "Bird", 0
.code
push offset animalInstanceKind
push offset AnimalInstance
call SetKind@Animal
I suggest to define a prototype for the methods, so we can use the MASM’s invoke:
SetKind@Animal proto :dword, :dword
.code
invoke SetKind@Animal, addr AnimalInstance, addr animalInstanceKind
Benefits:
I can't think of any good benefits, really. Implementing OOP in a non-OOP compiler is simply a kind of workaround which introduces much work and maintenance than working with procedural which is what's available in assembly language.
Benefits probably will count if OOP is available built-in in an assembler. HLA is one of the 'compiler' that provides this "built-in" functionality; and also, there's ObjAsm32 too, implementing OOP using set of MASM macros (emulating OOP keywords).
Closing:
That’s basically pretty much of it, practice and enjoy OOP in assembly language.
Thanks for reading. I’ll cover Overloading, Inheritance, Static and Virtual members on my next post.
Until the next time, happy coding.