Thread overview
Using delegates for C callbacks.
Feb 01, 2008
Leandro Lucarella
Feb 01, 2008
BCS
Feb 01, 2008
Kirk McDonald
Feb 02, 2008
Leandro Lucarella
Feb 02, 2008
Leandro Lucarella
Feb 02, 2008
Leandro Lucarella
February 01, 2008
Hi! I'm doing some code to interface with C and I need to use D delegates as C callbacks.

I've tested a lot of posibilities to do this, asking on IRC channels, but nothing seems to work entirely.

This is the closer I got:

 1  import std.stdio;
 2
 3  extern (C) void f(void function(void*) cb, void* arg)
 4  {
 5  	cb(arg);
 6  }
 7
 8  extern (C) static void thunk(alias Fn)(void* arg)
 9  {
10  	Fn(arg);
11  }
12
13  void fcb(void* arg)
14  {
15  	writefln("fcb: ", *cast (int*) arg);
16  }
17
18  class C
19  {
20  	int x = 1;
21  	void dcb(void* arg)
22  	{
24  		writefln("dcb: ", *cast (int*) arg, " = ", x);
25  	}
26  }
27
28  void main()
29  {
30  	int x = 1;
31  	C c = new C;
32  	thunk!(fcb)(&x);
33  	//thunk!(c.dcb)(&x);
34  	f(&thunk!(fcb), &x);
35  	//f(&thunk!(c.dcb), &x);
36  }

The thunk for the plain function adaptation works fine, but not the delegate. If I uncomment the code at line 33 I get:

thunk.d:10: Error: need 'this' to access member dcb

I have a void* pointer to use, I can "inject" the this pointer, but I don't know how. Doing something like:

 8  extern (C) static void thunk(alias Fn)(void* arg)
 9  {
10  	Fn.ptr = arg;
11  	Fn();
12  }

Doesn't work, it says:
thunk.d:10: Error: no property 'ptr' for type 'void'
thunk.d:10: Error: constant dcb().ptr is not an lvalue
thunk.d:10: Error: cannot implicitly convert expression (arg) of type void* to int

Which I don't understand (specially the part of "no property 'ptr' for type 'void'", why dcb is void? I don't think I understand very well the semantics of an alias template parameter =S


Is there any recomended solution for this? I think it (or should be) a
fairly common problem (at least when making D bindings for C libraries).

-- 
Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/
----------------------------------------------------------------------------
GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145  104C 949E BFB6 5F5A 8D05)
----------------------------------------------------------------------------
FINALMENTE EL CABALLITO FABIAN VA A PASAR UNA BUENA NAVIDAD
	-- Crónica TV
February 01, 2008
Reply to Leandro,

I have not tested this and don't have time to do so now

const auto sig = [ /* the binary form of nop; nop; nop; */ ];

R Thunker(T, A...)(A a)  // general thunk with tags
{
  T delegate(A) dg;
  dg.fn = 0xdeadbeef;
  dg.ptr = 0xabadf00d;
  return dg(a);
  asm {nop; nop; nop; nop; }
}


R function(A) Thuked(R, A...)(R delegate(A) dg)   // copy general thunk and replace tags with real stuff.
{
   byte* start = cast(byte*)(&R Thunker!(T, A));  // get thunk

   int stop = lengthTo(start, sig);  // find end

   start = start[0..stop].dup;  // copy

   stop = lengthTo(start, 0xdeadbeaf);  // relpace    (cast(void**)(start+stop))[0] = dg.fn;

   stop = lengthTo(start, 0xabadf00d);  // relpace
   (cast(void**)(start+stop))[0] = dg.ptr;

   return cast(typeof(ret)) start.ptr;   // return
}


note: the dg.ptr and dg.fn may be incorrect but IIRC there is a way to do that.


February 01, 2008
Leandro Lucarella wrote:
> Hi! I'm doing some code to interface with C and I need to use D delegates
> as C callbacks.
> 
> I've tested a lot of posibilities to do this, asking on IRC channels, but
> nothing seems to work entirely.
[snip]
> 
> The thunk for the plain function adaptation works fine, but not the
> delegate. If I uncomment the code at line 33 I get:
> 
> thunk.d:10: Error: need 'this' to access member dcb
> 
> I have a void* pointer to use, I can "inject" the this pointer, but I
> don't know how. Doing something like:
> 
>  8  extern (C) static void thunk(alias Fn)(void* arg)
>  9  {
> 10  	Fn.ptr = arg;
> 11  	Fn();
> 12  }
> 
> Doesn't work, it says:
> thunk.d:10: Error: no property 'ptr' for type 'void'
> thunk.d:10: Error: constant dcb().ptr is not an lvalue
> thunk.d:10: Error: cannot implicitly convert expression (arg) of type void* to int
> 
> Which I don't understand (specially the part of "no property 'ptr' for
> type 'void'", why dcb is void? I don't think I understand very well the
> semantics of an alias template parameter =S
> 
> 
> Is there any recomended solution for this? I think it (or should be) a
> fairly common problem (at least when making D bindings for C libraries).
> 

Try something like this:

extern(C) void f(void function(void*) fn, void* closure) {
    fn(closure);
}

class C {
    void foo(int i) {}
}

struct Closure {
    C self;
    int arg;
}

// It is possible to do some template trickery in order to
// generalize this thunk for any function signature and any
// class, but it is simpler to get the point across with a
// concrete example.
extern(C) void thunk(alias Fn)(void* _closure) {
    void delegate(int) dg;
    Closure* closure = cast(Closure*)_closure;
    dg.funcptr = &Fn;
    dg.ptr = cast(void*)(closure.self);
    dg(closure.arg);
}

void main() {
    C c = new C;
    auto closure = new Closure;
    closure.self = c;
    closure.arg = 20;
    // Note that we're passing the function C.foo and not
    // the method c.foo.
    thunk!(C.foo)(closure);
    f(&thunk!(C.foo), closure);
}

-- 
Kirk McDonald
http://kirkmcdonald.blogspot.com
Pyd: Connecting D and Python
http://pyd.dsource.org
February 02, 2008
Kirk McDonald, el  1 de febrero a las 14:23 me escribiste: [snip]
> Try something like this:
> 
> extern(C) void f(void function(void*) fn, void* closure) {
>     fn(closure);
> }
> 
> class C {
>     void foo(int i) {}
> }
> 
> struct Closure {
>     C self;
>     int arg;
> }
> 
> // It is possible to do some template trickery in order to
> // generalize this thunk for any function signature and any
> // class, but it is simpler to get the point across with a
> // concrete example.
> extern(C) void thunk(alias Fn)(void* _closure) {
>     void delegate(int) dg;
>     Closure* closure = cast(Closure*)_closure;
>     dg.funcptr = &Fn;
>     dg.ptr = cast(void*)(closure.self);
>     dg(closure.arg);
> }
> 
> void main() {
>     C c = new C;
>     auto closure = new Closure;
>     closure.self = c;
>     closure.arg = 20;
>     // Note that we're passing the function C.foo and not
>     // the method c.foo.
>     thunk!(C.foo)(closure);
>     f(&thunk!(C.foo), closure);
> }

Thanks, Kirk! The trick about passing the C.foo function instead of the c.foo method was defenely the trick. I adapted your example to what I needed, which is simpler because I don't need the Closure wrapper, so the code is more general without extra complexity:

import std.stdio;

extern(C) void f(void function(void*) fn, void* closure) {
        fn(closure);
}

class C {
        int x;
        void foo() {
                writefln("foo: ", x);
        }
}

// It is possible to do some template trickery in order to
// generalize this thunk for any function signature and any
// class, but it is simpler to get the point across with a
// concrete example.
extern(C) void thunk(alias Fn)(void* closure) {
        void delegate() dg;
        dg.funcptr = &Fn;
        dg.ptr = closure;
        dg();
}

void main() {
        C c = new C;
        // Note that we're passing the function C.foo and
        // not the method c.foo.
        c.x = 1;
        thunk!(C.foo)(cast (void*) c);
        c.x = 2;
        f(&thunk!(C.foo), cast (void*) c);
}

PS: Thanks BCS for the answer. I didn't try it either because I didn't understand it and found it too twisted, I was looking for something simpler :)

-- 
Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/
----------------------------------------------------------------------------
GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145  104C 949E BFB6 5F5A 8D05)
----------------------------------------------------------------------------
Dentro de 30 años Argentina va a ser un gran supermercado con 15
changuitos, porque esa va a ser la cantidad de gente que va a poder
comprar algo.
	-- Sidharta Wiki
February 02, 2008
Leandro Lucarella, el  1 de febrero a las 22:09 me escribiste:
> Thanks, Kirk! The trick about passing the C.foo function instead of the c.foo method was defenely the trick. I adapted your example to what I needed, which is simpler because I don't need the Closure wrapper, so the code is more general without extra complexity:

Well, I had some problems with that code, and wasn't general enough. What I really wanted was to be able to do something like this:


import std.stdio;

extern(C) void f(void function(void*) fn, void* closure) {
        fn(closure);
}

class C {
        int x;
        void foo() {
                writefln("C.foo: ", x);
        }
}

void thunk(void delegate() dg) {
        alias extern (C) void function(void*) fp;
        f(cast (fp) dg.funcptr, dg.ptr);
}

void main() {
        void foo() {
                writefln("foo");
        }
        C c = new C;
        c.x = 1;
        thunk(&c.foo);
        thunk(&foo);
}



This compiles... and *runs*! At least with GDC (DMD complains about "no
property 'funcptr' for type 'void delegate()'", I can see a lot of
problems with that code but that :S) on Linux.

Is this too wrong? I guess the casting from dg.funcptr to an extern (C) function is not (I don't know if D calling convention is warrantied to be the same as C, and I don't know if is warrantied that the first argument to a delegate is the context pointer), but I really want the generality and simplicity of this code, it makes no sense to need code more complex than that to do what I want to do.

PS: Should I move this to digitalmars.D?

-- 
Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/
----------------------------------------------------------------------------
GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145  104C 949E BFB6 5F5A 8D05)
----------------------------------------------------------------------------
EXTRAÑA RELACION ENTRE UN JUBILADO Y UN JABALI
	-- Crónica TV
February 02, 2008
Leandro Lucarella, el  2 de febrero a las 02:17 me escribiste:
> Is this too wrong? I guess the casting from dg.funcptr to an extern (C) function is not (I don't know if D calling convention is warrantied to be the same as C, and I don't know if is warrantied that the first argument to a delegate is the context pointer), but I really want the generality and simplicity of this code, it makes no sense to need code more complex than that to do what I want to do.

Well, it was that wrong, it only worked with delegates without arguments. I saw that the D calling conventions are defined in the D ABI specification so they aren't always the same as the C calling conventions, I guess.

I finally decided to go with this:


import std.stdio;

extern(C) void c_f(void function(int, void*) fn, int data, void* closure) {
        fn(data, closure);
}

class C {
        int x;
        void foo(int data) {
                writefln("C.foo: x=", x, ", data=", data);
        }
}

struct Delegate
{
        void delegate(int) dg;
}

extern (C) void thunk(int revents, void* data)
{
        auto d = cast (Delegate*) data;
        d.dg(revents);
}

void d_f(void delegate(int) dg, int data)
{
        auto d = new Delegate;
        d.dg = dg;
        c_f(&thunk, data, d);
}

void main() {
        void foo(int data) {
                writefln("foo: data=", data);
        }
        C c = new C;
        c.x = 1;
        d_f(&foo, 10);
        d_f(&c.foo, 5);
}


I'm not crazy about the heap allocation, but at least is simple, safe and general. And the templated thunk version wont work either if I want the API usage to be simple (with templates, users will be forced to pass the function pointer, which can be calculated at compile-time, separated from the context pointer, which is always a runtime value).

-- 
Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/
----------------------------------------------------------------------------
GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145  104C 949E BFB6 5F5A 8D05)
----------------------------------------------------------------------------
JUGAR COMPULSIVAMENTE ES PERJUDICIAL PARA LA SALUD.
	-- Casino de Mar del Plata