Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / ASM
Article

How to invoke C++ member operations from inline-assembler code segments

Rate me:
Please Sign up or sign in to vote.
4.74/5 (15 votes)
19 Sep 20057 min read 50.3K   443   42   1
Calling C++ operations from assembler code using member function pointers.

Introduction

Back in the late '80s, when I first started programming on Commodores famous Amiga PC, there was no alternative than using Assembler for optimizing your code to squeeze out any resources your hardware had. Although things have changed and compiler vendors have done a great job on code optimization, there are still some cases where you can do a better job than compilers do (presuming you know a lot about Assembler programming and your processor's architecture). By the way, if I talk about code optimizing I only mean optimizing the machine code for a given algorithm, not the algorithm itself. In most cases, it is more accurate to do optimizing on the algorithm. If you compare the computational complexity between the bubblesort (n2) and heapsort (n * log n) algorithms, you will see that code optimization of the bubblesort algorithm will not prevent the heapsort algorithm being faster for a definite n1 > n.

Because this article is not intended to be an introduction on code optimization, let us just assume that you have a piece of Assembler code in your C++ project (whether or not this is due to optimization purposes) and you want to invoke a member function of a given object within this Assembler code fragment. Due to the different concepts between the Assembler (procedural paradigm) and the C++ (object-oriented paradigm) programming language, I will first give you a brief overview of how C++ concepts like virtual function calls are implemented in Assembler. Afterwards, we will see how C++ member function pointers can be used to invoke member functions from Assembler code sections.

Calling non-virtual and virtual functions

Although the syntax between non-virtual and virtual function calls does not differ in C++, the Assembler code generated by the compiler differs a lot. The reason is that virtual function calls are dynamic calls. This means that the actual callee is determined during runtime. That's why this is also called late binding. Virtual functions are essential for the realization of polymorphism which is one of the key paradigms of object-oriented languages. Let's take a look at the following class hierarchy:

class ServiceA
{
public:
  void sub(int a, int b) {
    printf("ServiceA: %d - %d = %d\n", a, b, a-b);
  }
  virtual void add(int a, int b)  {
    printf("ServiceA: %d + %d = %d\n", a, b, a+b);
  }
  virtual void mul(int a, int b)  {
    printf("ServiceA: %d * %d = %d\n", a, b, a*b);
  } 
};
class ServiceB : public ServiceA
{
public:
  void sub(int a, int b) {
    printf("ServiceB: %d - %d = %d\n", a, b, a-b);
  }
  virtual void add(int a, int b)  {
    printf("ServiceB: %d + %d = %d\n", a, b, a+b);
  }
  virtual void mul(int a, int b)  {
    printf("ServiceB: %d * %d = %d\n", a, b, a*b);
  } 
};

The class ServiceA declares two virtual functions add() and mul() which are overwritten by the subclass ServiceB. When you call one of these operations with a pointer of type ServiceA on an instance of type ServiceB then the correct operation of class ServiceB will be invoked. This behavior is exactly what we know as object-oriented polymorphism and differs from calls to non-virtual functions.

1  ServiceA serviceA; ServiceB serviceB;
2  ServiceA *pSA = &serviceB;
3
4  pSA->sub(20, 5); //static call
5  pSA->add(10, 50); //dynamic call
6  pSA->mul(2, 2); //dynamic call

By scrutinizing the Assembler code in line 4 and 5, you can compare the differences between virtual and non-virtual function calls. Non-virtual function calls like the one in line 4 are handled during compile time. When the compiler gets line 4 as an input, it knows the type of the pointer and the type of the function. It determines the address of the non-virtual function sub() and generates the Assembler statement for calling this function. The Assembler code for line 4 looks more or less like this:

ASM
push 5;
push 20;
mov ecx, pSA;
call 0x40000; This is a pseudo-address where the member function 
              ServiceA::sub is located.

As we already know: virtual function calls are dynamic calls. This means that the address of the function is calculated during runtime. But what's the magic behind this? To be able to call the correct function depending on the object type, the compiler generates a specific function lookup table for virtual functions which is also called a vtable. Every object has a pointer to its vtable where the compiler stores function pointers to the correct functions. It is important that the offsets of the different virtual functions are the same. Only the function pointers differ from object type to object type. The tables below describe the structure and content of the vtable for different object types:

vtable of instances of type ServiceA

OffsetC++function pointer
0x00add()0x40010
0x04mul()0x40070

vtable of instances of type ServiceB

OffsetC++function pointer
0x00add()0x40230
0x04mul()0x402C0

By the way, you can significantly reduce the memory footprint of your application if you avoid using virtual functions on classes with a few bytes of memory usage from which lots of instances are created. Let's consider an example: an instance of a class might use 4 bytes of memory for its attributes. If the class has virtual functions the compiler will generate a vtable for that class (compiler optimization could prevent that in some circumstances, but that's not the deal). Because every instance of that class would have a pointer to this vtable, you would double the memory usage for each instance (I think some compilers only use 2 bytes as an offset to the vtable which would result in an increase of "just" 50%). Therefore consider to steer clear of virtual functions where possible.

Calling a virtual function instructs the compiler to generate code for looking up the function pointer in the vtable and to call this function. The Assembler code for line 5 looks more or less like this:

ASM
push 50;
push 10;
mov ecx, pSA;
mov edx, [ecx];//first 4 bytes are the pointer to the vtable
call [edx];//call first element from the vtable (offset 0)

The Assembler code for line 6 looks accordingly:

ASM
push 2;
push 2;
mov ecx, pSA;
mov edx, [ecx];//first 4 bytes are the pointer to the vtable
call [edx + 4];//call second element from the vtable (offset 4)

The operation mul() is stored in the second position of the vtable. That's why the call takes an offset of 4 bytes (call [edx + 4]).

thiscall calling convention

The default calling convention for calling member functions in C++ is called thiscall. The characteristics about this calling convention is similar to the standard calling convention. This means, that arguments are passed from right to left on the stack. The implicit this pointer is placed in ECX. Finally, the stack is cleaned up by the called function which does return values in EAX if needed. Thus, we first have to push 5, then 20 on the stack and load the pointer in ECX; for C++, a statement like pSA->sub(20, 5) (a good introduction on calling conventions is Calling Conventions Demystified on Code Project).

Using C++ member function pointers

The problem with the examples above is that you can't use them in your inline Assembler code fragments. In case of non-virtual function calls, you need to determine the address of the member functions. But you will fail to write Assembler code like the following example:

ASM
push 5;
push 20;
mov ecx, pSA;
call &ServiceA::sub; // Error! You can not determine
                     // the address of a member function
                     // like this

In case of virtual function calls we saw that the compiler creates code for accessing the vtable of the instance. In our examples, the pointer to the vtable was located in the first 4 bytes. But this memory layout is not defined by the ANSI C++ specification. Therefore, you can't rely on that. Another big problem is, that you can not determine the position of a specific virtual function within the vtable. So it is better to use C++ member function pointers to invoke member functions from C++ classes within Assembler code fragments. If you are not familiar with member function pointers, the article "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston is a very good starting point for the topic. Even if you know what member function pointers are, there are some confusing aspects about them, so I recommend to read this article.

As Don Clugston pointed out, there is a diversity of implementation between different compilers when it comes to member function pointers. Therefore, talking about details on the level of Assembler statements will definitely lead us to wrong results. But to understand how member functions work and how we can use them in our inline Assembler code sections, we can make our assumptions about them.

Member function pointers do not just point to some member function. As we already know this won't work for virtual functions. They rather point to another function which is implicitly created by the compiler.

1  typedef void (ServiceA::*TypeAPtr)(int, int);
2
3  int main(int argc, char* argv[])
4  {
5    ServiceA serviceA; ServiceB serviceB;
6    ServiceA *pSA = &serviceB;
7    TypeAPtr _add = &ServiceA::add;
8    (pSA->*_add)(10,20);
9  }

I think the most curious part of this code fragment is line 7. According to the C++ syntax, this statement looks like: assign the address of the virtual member function called add() of class ServiceA to the member function pointer _add. But as this function is a virtual one, you can not determine the exact member function without having an instance of ServiceA or any subclass of it. It gets even more curious because some compilers even let you omit the & operator. But omitting the & operator is non-standard and you should avoid it.

The Assembler code for the assignment in line 7 is straightforward. It just copies the address of the implicit function which calls the virtual member function add() on types of ServiceA into the member function pointer. Together with the member function call from line 8, the Assembler code looks like this:

ASM
mov [_add], address of implicit function; //line 7
mov ecx, pSA; //line 8
call [_add];

This is exactly what we can use in our inline Assembler sections.

int main(int argc, char* argv[])
{
  ServiceA serviceA; ServiceB serviceB;
  ServiceA *pSA = &serviceB;
  TypeAPtr _add = &ServiceA::add;

  pSA->add(10,50);
  pSA->mul(102,50);
  pSA->sub(20,5);

  (pSA->*_add)(10,20);
  _asm
  {
    lea ecx, serviceB;
    push 60;
    push 40;
    call _add;
  }
  return 0;
}

Conclusion

This article describes a way to call member functions from C++ classes within inline Assembler code sections. As far as I know this is the only way you can do this, despite the fact if it is useful or not. Hope, you can still use this.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Chief Technology Officer W3L
Germany Germany
-Since 1th August 2007: Chief Technology Officer of W3L
-2002/08/01-2007/07/31: PhD student
-1997/10/15-2002/07/31: Studied Electrical Engineering and Computer Science

Comments and Discussions

 
Questionwhat about constructor? destructor Pin
Debra_supper14-Feb-11 7:17
Debra_supper14-Feb-11 7:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.