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.
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?