Binding D to C Part Four

posted in D Bits
Published August 05, 2012
Advertisement
This is the fourth part of a series on creating bindings to C libraries for the D Programming Language.

In part one, I introduced the difference between dynamic and static bindings and some of the things to consider when choosing which kind to implement. In part two, I talked about the different linkage attributes to be aware of when declaring external C functions in D. In part three, I showed how to translate C types to D. Here in part four, I'll wrap up the dscussion of type translation with a look at structs.

A D Struct is a C Struct


For the large majority of cases, a C struct can be directly translated to D with little or no modification. The only major difference in the declarations is when C's typedef keyword is involved. The following example shows two cases, with and without typedef. Notice that there is no trailing semi-colon at the end of the D structs.


// In C
struct foo_s
{
int x, y;
};

typedef struct
{
float x;
float y;
} bar_t;

// In D
struct foo_s
{
int x, y;
}

struct bar_t
{
float x;
float y;
}


Most cases of struct declarations are covered by those two examples. Sometimes, a slight deviation may be encountered. Such as a struct with two names, one in the struct namespace and one outside of it (the typedef). In that case, the typedefed name should always be used.


// In C
typedef struct foo_s
{
int x;
struct foo_s *next;
} foo_t;

// In D
struct foo_t
{
int x;
foo_t *next;
}


Another common case is that of what is often called an opaque struct (in C++, more commonly referred to as a forward reference). The translation from C to D is similar to that above.


// In C
typedef struct foo_s foo_t;

// In D
struct foo_t;


Member Gotchas


When translating the types of struct members, the same rules as outlined in Part 3 should be followed. But there are a few gotchas to be aware of.

The first gotcha is relatively minor, but annoying. I've previously mentioned in this series that I believe it's best to follow the C library interface as closely as possible when naming types and functions in a binding. This makes translating code using the library much simpler. Unfortunately, there are cases where a struct might have a field which happens to use a D keyword for its name. The solution, of course, is to rename it. I've encountered this a few times with Derelict. My solution is to prepend an underscore to the field name. For publicly available bindings, this should be prominantly documented.


// In C
typedef struct
{
// oops! module is a D keyword.
int module;
} foo_t;

// In D
struct foo_t
{
int _module;
}


The next struct gotcha is that of versioned struct members. Though rare in my experience, some C libraries wrap the members of some structs in #define blocks. I find this practice rather annoying (libpng, I'm looking at you), because it can cause problems not only with language bindings but also with binary compatibility issues when using C as well. Thankfully, translating this idiom to D is simple. Using it, on the other hand, can get a bit hairy.

Here's an example.


// In C
typedef struct
{
float x;
float y;
#ifdef MYLIB_GO_3D
float z;
#endif
} foo_t;

// In D
struct foo_t
{
float x;
float y;
// Using any version identifier you want -- this is one case where I advocate breaking
// from the C library. I prefer to use an identifier that makes sense in the context of the binding.
version(Go3D) float z;
}


Then, to make use of the versioned member, the '-version=Go3D' is passed on the command line when compiling. And this is where the headache begins.

If the binding is compiled as a library, then any D application linking to that library will also need to be compiled with any version identifiers the library was compiled with, else the versioned members won't be visible. Furthermore, the C library needs to be compiled with the equivalent defines. So to use foo_t.z from the example above, the C library must be compiled with -DMYLIB_GO_3D, the D binding with -version=Go3D, and the D app with -version=Go3D. And when making a binding like Derelict that loads shared libraries dynamically, there's no way to ensure that end users will have a properly compiled copy of the C shared library on their system unless it is shipped with the app. Not a big deal on Windows, but rather uncommon on Linux. Also, if the binding is intended for public consumption, the versioned sections need to be documented.

Read more about D's version conditions in the D Programming Language documentation.

The final struct member gotcha, and a potentially serious one, is bitfields. The first issue here is that D does not have bitfields. In D2, we have a library solution in std.bitmanip, but for a C binding it's not a silver-bullet solution because of the second issue. And the second issue is that the C standard leaves the ordering of bitfields undefined.

Consider the following example from C.


typedef struct
{
int x : 2;
int y : 4;
int z: 8;
} foo_t;


There are no guarantees here about the ordering of the fields or where or even if the compiler inserts padding. It can vary from compiler to compiler and platform to platform. This means that any potential solution in D needs to be handcrafted to be compatibile with a specific C compiler version in order to guarantee that it works as expected.

Using std.bitmanip.bitfields might be the first approach considered.


// D translation using std.bitmanip.bitfields
struct foo_t
{
mixin(bitfields!(
int, "x", 2,
int, "y", 4,
int, "z", 8,
int, "", 2)); // padding
}


Bitfields implemented this way must total to a multiple of 8 bits. In the example above, the last field, with an empty name, is 2 bits of padding. The fields will be allocated starting from the least significant bit. As long as the C compiler compiles the C version of foo_t starting from the least significant bit and with no padding in between the fields, then this approach might possibly work. I've never tested it.

The only other alternative that I'm aware of is to use a single member, then implement properties that use bit shift operations to pull out the appropriate value.


struct foo_t
{
int flags;
int x() @property { ... }
int y() @property { ... }
int z() @property { ... }
}


The question is, what to put in place of the ... in each property? That depends upon whether the C compiler started from the least-significant or most-significant bit and whether or not there is any padding in between the fields. In otherwords, the same difficulty faced with the std.bitmanip.bitfields approach.

In Derelict, I've only encountered bitfields in a C library one time, in SDL 1.2. My solution was to take a pass. I use a single 'flags' field, but provide no properties to access it. Given that Derelict is intended to be used on multiple platforms with C libraries compiled by multiple compilers, no single solution was going to work in all cases. I decided to leave it up to the user. Anyone needing to access those flags could figure out how to do it themselves. I think that's the best policy for any binding that isn't going to be proprietary. Proprietary bindings, on the other hand, can be targeted at specific C compilers on specific platforms.

Conclusion


I believe that's all I wanted to say about structs. In Part 5, which I'm quite certain will be the final installment, I'll talk about how to declare functions for both dynamic and static bindings and some of the issues that need to be considered when doing so. I'll also tie off any loose ends I think of.
0 likes 4 comments

Comments

AlessandroStamatto
Great Tutorial Serie!

The bitmap was the only awkward part, I felt the solution was a bit hackish...

Is it possible to use the property solution and do a generic implementation
detecting endianess in any way and then using something like static if, mixins,
or something like that to define the right property functions?
August 05, 2012 08:07 PM
Aldacron
For bitfields, there is no real way to detect endianess. Just being on a big-endian platform, for example, does not mean the bitfields will be packed in big-endian order. It's entirely compiler-dependent. See this [url="http://stackoverflow.com/questions/1490092/c-c-force-bit-field-order-and-alignment"]post at StackOverflow[/url].

The short of it is that there is no reliable, general-purpose way to determine the ordering of bitfields in a given C library from the D side. The information needed to do so just isn't there.
August 05, 2012 10:29 PM
jpf91
The only 'portable' way I know would be to write accessor functions in c, then add bindings to those.

C:
[CODE]
int get_x(foo_t foo)
{
return foo.x;
}
[/CODE]

D:
[CODE]
extern(C) int get_x(foo_t);

struct foo_t
{
int flags; //to make sure the struct has the correct size
int x() @property
{
return get_x(this);
}
int y() @property { ... }
int z() @property { ... }
}
[/CODE]
August 06, 2012 05:02 PM
Aldacron
That still leaves you with the same problem... the C library you're binding with has to be compiled with the same compiler as the accessors, else all bets are off. The problem is solvable without resorting to C (though that does make it simpler). As long as you have full control over how the C libraries are being compiled. If, for example, you are using a static binding *and* statically linking with the C libs, you're golden. But if you are depending on shared libs, the only way you have control is if you ship specifically compiled versions with your app. Which sort of defeats the purpose of shared libraries on platforms like Linux.
August 07, 2012 11:07 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement