Thread overview
C style callbacks fix for member callbacks
May 20, 2018
MGW
May 20, 2018
ag0aep6g
May 21, 2018
ag0aep6g
May 19, 2018
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 20, 2018
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
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 21, 2018
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 21, 2018
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 21, 2018
> 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 21, 2018
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 26, 2018
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.