digitalmars.D.learn - Using delegates for C callbacks.
- Leandro Lucarella (66/66) Feb 01 2008 Hi! I'm doing some code to interface with C and I need to use D delegate...
- BCS (25/25) Feb 01 2008 Reply to Leandro,
- Kirk McDonald (39/72) Feb 01 2008 Try something like this:
- Leandro Lucarella (47/84) Feb 01 2008 Thanks, Kirk! The trick about passing the C.foo function instead of the
- Leandro Lucarella (43/47) Feb 01 2008 Well, I had some problems with that code, and wasn't general enough. Wha...
- Leandro Lucarella (52/58) Feb 01 2008 Well, it was that wrong, it only worked with delegates without arguments...
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
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
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
Feb 01 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
Feb 01 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
Feb 01 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
Feb 01 2008