www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - C style callbacks fix for member callbacks

reply IntegratedDimensions <IntegratedDimensions gmail.com> writes:
I have a member callback that I want to use as a C callback.

This is impossible due to the `hidden this` passed as the "first" 
parameter.

The callback already makes it's last value a user pointer which I 
use as a "this".

If I make my method static extern(C) then there is no crash and 
everything works. The problem is that it is now a static function 
within the class which I want to avoid.

Because there is actually a "this pointer" I can make it a member 
function and everything works as long as the calling convention 
is right.

I was initially wrapping the callback in a delegate that made it 
all work out but I want to avoid that level of indirection since 
it should not be necessary.

I have tried manually reversing the arguments, using various 
calling conventions, etc but everything crashes except when I use 
extern(C) static without modifying the order.

extern(C) static foo(a,b,c,d)

puts the parameters on the stack as d,c,b,a

foo(a,b,c,d)

is d,c,b,a,this (? The ABI does not make it clear what order is 
pushed on the stack. It uses the terminology "passes" and I 
assume that the calling convention is C'ish although extern(C) 
matters).


works

extern(C) static foo(a,b,c,d)

does not work

static foo(d,c,b,a)

So something else is going on

"The last parameter is passed in EAX rather than being pushed on 
the stack if the following conditions are met:

     It fits in EAX.
     It is not a 3 byte struct.
     It is not a floating point type.
"

Can someone clarify the exact calling convention process for C vs 
D along with any potential solutions to the above problem(using 
static is not a solution).

Just to make sure we are on the same page:

class
{
    void extern(C) static foo(a,b,c,mythis);
}

works while

class
{
    void extern(C) foo(a,b,c);
}

fails.

class
{
    void foo(c,b,a);
}

also fails.
May 19 2018
next sibling parent reply MGW <mgw yandex.ru> writes:
On Saturday, 19 May 2018 at 23:52:58 UTC, IntegratedDimensions 
wrote:
 I have a member callback that I want to use as a C callback.
http://www.agner.org/optimize/calling_conventions.pdf https://www.youtube.com/watch?v=xhDS377mAc4
May 20 2018
parent reply IntegratedDimensions <IntegratedDimensions gmail.com> writes:
On Sunday, 20 May 2018 at 08:40:57 UTC, MGW wrote:
 On Saturday, 19 May 2018 at 23:52:58 UTC, IntegratedDimensions 
 wrote:
 I have a member callback that I want to use as a C callback.
http://www.agner.org/optimize/calling_conventions.pdf https://www.youtube.com/watch?v=xhDS377mAc4
Sorry, I can't understand Russian(wish I could!). It also does not seem applicable for my problem. Although It is a useful idea here(using D in C++). alias callback = extern(C) int function(const(void) a, void *b, uint c, void* context); Where context acts as this. I would like to assign a D method to this callback. class { callback c; /*extern(C) static*/ int foo(const(void) a, void *b, uint c, void* context); this() { c = cast(callback)&foo; } }
May 20 2018
parent reply ag0aep6g <anonymous example.com> writes:
On 05/20/2018 06:48 PM, IntegratedDimensions wrote:
 alias callback = extern(C) int function(const(void) a, void *b, uint c, 
 void* context);
(I'm assuming that `a` is supposed to be a `const(void)*`.)
 Where context acts as this.
 
 I would like to assign a D method to this callback.
 
 class
 {
     callback c;
     /*extern(C) static*/ int foo(const(void) a, void *b, uint c, void* 
 context);

     this() { c = cast(callback)&foo; }
 }
Unless I'm misunderstanding it, the spec seems to say that the `this` pointer is passed as if it was an additional parameter past the last one [1]. But that doesn't seem to be true in the implementation. At least on Linux x86-64, `this` seems to be a hidden first parameter. So when a method is called as a `callback`, `a` becomes `this`, `b` becomes the first explicit parameter, `c` the second, and `context` the third. So this works: ---- import std.stdio; alias Callback = extern(C) int function(const(void)* a, void* b, uint c, void* context); class C { int field = 43; extern(C) int foo(void* b, uint c, C this_) { const(void)* a = cast(void*) this; writeln(a, " ", b, " ", c, " ", this_.field); return 0; } } void main() { void* a = new int; void* b = new int; uint c = 42; auto obj = new C; Callback cb = cast(Callback) (&obj.foo).funcptr; cb(a, b, c, cast(void*) obj); writeln(a, " ", b, " ", c, " ", obj.field); /* For comparison. Should print the same. */ } ---- This is all very hacky, of course. And I don't really know what I'm doing there. So obviously, I don't recommend doing this. But other than hacking it like that, I don't think you can pass a method as a `callback` directly. [1] https://dlang.org/spec/abi.html#parameters
May 20 2018
parent reply IntegratedDimensions <IntegratedDimensions gmail.com> writes:
On Sunday, 20 May 2018 at 23:05:47 UTC, ag0aep6g wrote:
 On 05/20/2018 06:48 PM, IntegratedDimensions wrote:
 alias callback = extern(C) int function(const(void) a, void 
 *b, uint c, void* context);
(I'm assuming that `a` is supposed to be a `const(void)*`.)
 Where context acts as this.
 
 I would like to assign a D method to this callback.
 
 class
 {
     callback c;
     /*extern(C) static*/ int foo(const(void) a, void *b, uint 
 c, void* context);

     this() { c = cast(callback)&foo; }
 }
Unless I'm misunderstanding it, the spec seems to say that the `this` pointer is passed as if it was an additional parameter past the last one [1]. But that doesn't seem to be true in the implementation. At least on Linux x86-64, `this` seems to be a hidden first parameter. So when a method is called as a `callback`, `a` becomes `this`, `b` becomes the first explicit parameter, `c` the second, and `context` the third. So this works: ---- import std.stdio; alias Callback = extern(C) int function(const(void)* a, void* b, uint c, void* context); class C { int field = 43; extern(C) int foo(void* b, uint c, C this_) { const(void)* a = cast(void*) this; writeln(a, " ", b, " ", c, " ", this_.field); return 0; } } void main() { void* a = new int; void* b = new int; uint c = 42; auto obj = new C; Callback cb = cast(Callback) (&obj.foo).funcptr; cb(a, b, c, cast(void*) obj); writeln(a, " ", b, " ", c, " ", obj.field); /* For comparison. Should print the same. */ } ---- This is all very hacky, of course. And I don't really know what I'm doing there. So obviously, I don't recommend doing this. But other than hacking it like that, I don't think you can pass a method as a `callback` directly. [1] https://dlang.org/spec/abi.html#parameters
I tried this. Your code crashes in windows dmd x86 x64. It really shouldn't be hacky. The only difference is the "this" is implicit normally when in this case it is explicit and possibly in a different location than one expects.
May 20 2018
parent reply ag0aep6g <anonymous example.com> writes:
 I tried this. Your code crashes in windows dmd x86 x64.
Hm. Works for me in a virtual machine. But I'm not surprised that it's fragile. It might be completely wrong, and it just happens to look alright on my machine.
May 20 2018
parent IntegratedDimensions <IntegratedDimensions gmail.com> writes:
On Monday, 21 May 2018 at 02:23:27 UTC, ag0aep6g wrote:
 I tried this. Your code crashes in windows dmd x86 x64.
Hm. Works for me in a virtual machine. But I'm not surprised that it's fragile. It might be completely wrong, and it just happens to look alright on my machine.
https://run.dlang.io/is/CMNnJY Shows the static version produces the same code as the non-static. The code was probably compiled on linux. I can't tell though on my machine what is going on since I cannot disassemble properly and what I do see is far more complex.
May 21 2018
prev sibling parent IntegratedDimensions <IntegratedDimensions gmail.com> writes:
Investigating further, this does not seem to be pushed on the 
stack but set in EAX.

Hence no amount of parameter placement manipulation will work. It 
actually becomes an easy situation although this will be invalid 
as it will be be whatever value is in EAX used by the caller.

One cannot set this directly though but one does not have to use 
it. Therefor, simply using a member function here is the same as 
a static and no changes have to be made. Quite an easy fix. I do 
not know how safe it is. The docs say this is pushed and that is 
probably generally true.
May 25 2018