Using C Classes in Managed Code

The RECT structure discussed so far comes from the C-based Win32 API. If you work with C++-based APIs, structures and classes can obviously be more complex. Nevertheless, everything I have discussed for C structures applies to C++ classes equally. Like C structures, C++ classes define the binary layout of instances. For access to data members of C++ classes, ldind and stind instructions are used in the same way as they're used for C structures.

However, C++ gives the programmer further features to define types; C++ classes can also have functions, including special member functions and virtual functions. The details of calling member functions across interoperability boundaries are discussed in Chapter 9. In this chapter, I will discuss how managed code can be used to determine the target of a virtual function call (the address of the most derived virtual function) at runtime.

When a native class contains or inherits virtual functions, it will have one or more vtable pointers in the instance data. (The number of vtable pointers depends on the base classes.) Since the (native) C++ compiler is aware of these vtable pointers and the layout of the vtables, it can generate native code that uses the vtable pointer to pick the right element from the vtable. Using the ldind instruction, the C++/CLI compiler can generate managed code that achieves the same. To understand how this is possible, assume a native class Base that defines two virtual functions f1 and f2:

class Base {

public: virtual void f1(); virtual void f2();

Given a function GetObject that returns a Base*, the virtual function f2 could be called with the following code:

To determine the address of the (potentially overloaded) virtual function f2, the C++/CLI compiler generates IL code that first pushes the pBase pointer on the stack:

ldloc pBase

Since the vtable pointer is the first element of the class Base, it can be loaded on the stack with the ldind.i4 instruction (assuming the code targets a 32-bit processor):

ldind.i4

Since f2 is the second virtual function of Base, the second element in the vtable is needed. To determine the address of this second element, the offset of 4 is added to the vtable pointer on the stack:

ldc.i4.4 add

Since the address to the correct function pointer in the vtable is now on the stack, the pointer to the most derived implementation of f2 can be determined with the ldind.i4 instruction:

ldind.i4

For the actual virtual function call, a special IL instruction is used (this will be discussed in Chapter 9).

Was this article helpful?

0 0

Post a comment