www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Trick for passing void* arrays around with typing

foo(cast(void*)[Object1, Object2]);

foo(cast(bool delegate(void*))(Tuple!(X,Y) objects)) { });

One can pass arbitrary data as an array(the cast is ugly though)

Then the tuple is cast back to void* but one can access objects 
correctly with typing.

This works well when interacting with C/C++ void* data passing 
but can also work with passing heterogeneous objects in general.




import std.stdio, std.typecons;

class X { int x = 1; }
class Y { int y = 2; }

// a ** is required for the cast.
void foo(void** data)
{
     auto dd = cast(Tuple!(X,Y)*)(data);
     auto d = *dd;
    writeln(d[0].x);
    writeln(d[1].y);
}


// This would be a C callback that is hidden from us but the 
callback isn't.
alias Q = void delegate(void*);
void bar(Q d, void* x)
{
     d(x);
}



void main()
{
     X x = new X;
     Y y = new Y;
     foo(cast(void**)[x, y]);
     bar(cast(Q)(Tuple!(X,Y)* dd)
         {
             auto d = *dd;
             writeln("> ", d[0].x);
             writeln("> ", d[1].y);
         }, cast(void*)[x, y]);
}


What would be nice is if one didn't actually have to jump through 
all the hoops:


void foo(void* as Tuple!(X,Y) d) // Redundant here but only 
because we have access to foo
{
    writeln(d[0].x);
    writeln(d[1].y);
}

// This would be a C callback that is hidden from us but the 
callback isn't.
alias Q = void delegate(void*);
void bar(Q d, void* x)
{
     d(x);
}



void main()
{
     X x = new X;
     Y y = new Y;
     foo([x, y]);
     bar((as Tuple!(X,Y) d)
         {
             writeln("> ", d[0].x);
             writeln("> ", d[1].y);
         }, as [x, y]);
}


here I'm using as to signify a sort of implicit cast.

The main thing to note is that for the callback, D gets the type 
as it is defined  even though the cast changes the overall 
delegate to what it should be:


bar(cast(Q)(Tuple!(X,Y)* dd)
    {
         auto d = *dd;
         writeln("> ", d[0].x);
         writeln("> ", d[1].y);
     }, cast(void*)[x, y]);
}


I don't actually use * in my code I think it is necessary to get 
the right values due to how they are passed. I think it has to do 
with register vs stack passing. So it might be fragile

What I have is

bar(cast(Q)(Tuple!(X,Y) d)
    {
         writeln("> ", d[0].x);
         writeln("> ", d[1].y);
     }, cast(void*)[x, y]);
}

which works, at least for x64.

The cool thing is the "casting" all happens internally and so 
there is no direct need for casting. Obviously though there is no 
real casting going on(one could swap x and y in the argument) and 
so it is dangerous... but D could make it 100% type safe with the 
appropriate syntax. (only for delegates and callbacks) although 
maybe one could use it for normal functions too:

foo([x,y] as Tuple!(X,Y))

e.g., normally we would have to do

bar((dd)
    {
         auto d = cast(Tuple!(X,Y))dd; // Doesn't work though 
because tuple is not a reference, so must use the double pointer 
hacks.
         writeln("> ", d[0].x);
         writeln("> ", d[1].y);
     }, cast(void*)[x, y]);
}


When working with quite a number of C callbacks and passing data, 
this is a nice pattern to use to avoid the ugly temp variables 
and casting.
Jun 21 2019