Using structs to mimic the C++ object structure

With D's low-level control, we can access any kind of data structure and paint nice types over it. To demonstrate this, we'll access a C++ object from D. The same technique can also be used for memory-mapped hardware.

Note

You need to take caution that this is not portable. It may not work even across different versions of the same C++ compiler. If you use this trick, make sure it is in an environment where you know the binary layout.

How to do it…

Let's mimic the C++ object structure by executing the following steps:

  1. Investigate the C++ ABI on your system. Typically, but not necessarily, a C++ object consists of a pointer to the virtual function table, the members of the parent class (recursively), and the members of the child class. If the C++ object has no virtual functions, it does not have a virtual table; the layout is then compatible with a C struct with the same data members.
  2. Create an extern(C++) interface for the virtual table, like we did for the regular interaction between D and C++. The interface should inherit from the parent class' virtual function table interface.
  3. Make a struct to represent the C++ object. First, put a void* member that represents the virtual table.
  4. Write a property that returns the this pointer casted to the virtual interface type.
  5. Use alias this with the property we wrote in step 4.
  6. List the data members in the same order in which they appear with compatible types as the C++ object.
  7. Write a function prototype, which is a placeholder for the C++ object's constructors, and use pragma(mangle) to assign it a matching name. You may have to figure out the C++ mangles experimentally; compile the C++ application and inspect its object file.
  8. Write D constructors that forward to the C++ constructors.
  9. Rewrite C++ destructors—calling it directly tends to attempt deallocating the object, which will result in a crash.
  10. List non-virtual functions normally.
  11. Use the object and hope it doesn't crash! Here, I used dmd 2.065 and g++ 4.4.4 on 32-bit Linux.

The following is the C++ code to define the object:

class Class {
    public:
        Class(int n);
        virtual void add(int a);
        void print();
      int number;
};
void Class::print() { printf("Number = %d
", number); }
Class::Class(int n) { printf("constructed
"); number = number; }
void Class::add(int a) { number += a; }

The following is the D code to access the C++ object:

struct Class {
    extern(C++) interface Vtable {
          void add(int a);
    }
     void* vtbl;
     @property Vtable getVirtuals() {  return cast(Vtable) &this; }
     alias getVirtuals this;
     extern(C++) void print();
     int number;
    pragma(mangle, "_ZN5ClassC2Ei")
    extern(C++) void ctor(int a);
    this(int a) { ctor(a); }
}
void main() {
    Class c = Class(0);
    c.num = 10;
    c.add(10);
    c.print();
}

It will print the following output:

constructed
Number = 20

How it works…

Data is data as far as the computer hardware is concerned. If we can map a type to the same bits in memory and use it in the appropriate places, it will work, irrespective of whether that type declaration is written in C++, D, or any other language.

In addition to D's built-in C++ support and other similar features, such as constructors and destructors, we can use a few other features to make this smoother to use.

The first one is alias this. When a member that is is requested is not in the object itself, or when an implicit conversion is requested, it attempts substituting the alias this value instead. This allows method forwarding and controlled implicit conversion, which implements a kind of subtyping—this is very similar to class inheritance.

The second is pragma(mangle). This changes the name of the function in the object file. If we match calling convention, arguments, return value, and name, we can call the code. The computer doesn't care about anything beyond that! It would also be possible to write code that automatically mangles D functions with C++ names and passes the return value of that function to pragma(mangle). The function would be evaluated at compile time to build the name string. We'll look at these techniques later in the book. Here, we got the mangled name experimentally (in this case, it is correct for my version of g++ on Linux; your results may vary).

We also used a nested interface for the virtual function table. This simply keeps the outer namespace clean; it has no functionality difference.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.111.208