www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Using delegates for C callbacks.

reply Leandro Lucarella <llucax gmail.com> writes:
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
Feb 01 2008
next sibling parent BCS <ao pathlink.com> writes:
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.
Feb 01 2008
prev sibling parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
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.

 
 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
Feb 01 2008
parent reply Leandro Lucarella <llucax gmail.com> writes:
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
Feb 01 2008
parent reply Leandro Lucarella <llucax gmail.com> writes:
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
Feb 01 2008
parent Leandro Lucarella <llucax gmail.com> writes:
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
Feb 01 2008