Passing Structs to function like in C

cy via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Fri Aug 12 22:06:06 PDT 2016


On Friday, 12 August 2016 at 15:21:22 UTC, D.Rex wrote:
> I was wondering how this is achieved in D, or if D has an 
> alternative implementation of this.

It isn't, because C interfaces that require you to pass in 
structures are inherently bad design, and usually both unstable 
and extremely C specific. Nobody wants to bother with that, and 
get stuck maintaining it to boot. You have to re-define the 
struct in D's language in order to comprehend it, but then you 
look in libuv and you see things like:

struct uv_shutdown_s {
   UV_REQ_FIELDS
   uv_stream_t* handle;
   uv_shutdown_cb cb;
   UV_SHUTDOWN_PRIVATE_FIELDS
};

and you let out a slow, agonized groan of disgust, because now 
you have to go searching through the CPP parser output manually 
to determine the nature of the struct. Once you've done that, 
your code is unstable and likely to stop working with even minor 
version changes, because the C programmers used those #define 
statements on the assumption that what substitutes in there won't 
always remain the same. And since C doesn't check the size of a 
structure, you usually get no error when they add another byte in 
the middle, only silent failures and breakage.

But... assuming you're forced to do this, you'd run the header 
file through cpp with all the relevant flags. There is no parser 
for C that works in the general case (and C programmers just LOVE 
using all the horrible edge cases), but you can just manually go 
through and look at the structures you want to use. For instance:

struct uv_shutdown_s {
   void* data; uv_req_type type; void* active_queue[2]; void* 
reserved[4];
   uv_stream_t* handle;
   uv_shutdown_cb cb;

};

cpp doesn't fill in typedefs, so you have to manually go and look 
at what those are (and hope that they don't change depending on 
compilation parameters). Oh, and I should mention cpp erases all 
newlines, so any substitution is all going to be crammed on a 
single line. But once you see that, then you make a uv.d file 
containing:

...

alias ShutdownCallback = void function(shutdown*, int);

struct Shutdown {
   void* data;
   Req type;
   void*[2] active_queue;
   void*[4] reserved;
   Stream* handle;
   ShutdownCallback cb;
}

as well as the same thing done for "req" (uv_req_t) and "stream" 
(uv_stream_t). Then you can just...

extern (C) int uv_shutdown(Shutdown*, Stream*, ShutdownCallback);
extern (C) void* malloc(size_t);
void foo(Stream* something, int delegate() handle) {
   Shutdown* req = cast(Shutdown*)malloc(Shutdown.sizeof);
   req.data = handle; // ?
   uv_shutdown(req, something, unpack_delegate);
}


...and it'll work, until they change something, or you try to 
switch to a 64 bit machine, or something.

...and if instead, libuv used an opaque structure for Shutdown 
and Stream, and functions to access its data, you could do this:

struct Shutdown;
struct Stream;
extern (C) int uv_shutdown(Shutdown*, Stream*, ShutdownCallback);
extern (C) Shutdown* uv_new_shutdown();
void foo(Stream* something, int delegate() handle) {
   Shutdown* req = uv_new_shutdown();
   req.data = handle;
   uv_shutdown(req,stream,unpack_delegate);
}

Much simpler, no? Plus it's stable even when they change the 
structure of their implementation.

When I have to write a C library, I almost always write a C 
interface that doesn't suck, in order to do the things I want 
with that library. Then at least when they change their internal 
structure, my C can recompile, and present a stable and simple 
ABI to the D side of things.

This is actually a problem for every language that's not C, not 
just D. Passing structs as arguments to your interface just 
always makes things harder for language supporters.


More information about the Digitalmars-d-learn mailing list