Thread overview
June 26
I have a two questions about calling C functions from D.

(1) When passing a D callback to a C function, is there a way to write the code without having to prefix the callback declaration with "extern(C)"?

It's not a big deal adding the prefix to the D function declaration. It just seems odd to me to prefix D code with "extern(C)". For example, the following code works:

  extern(C) void cfunc(void function(int));
  extern(C) void dcallback(int x) {...}		<-- Why extern(C)?
  cfunc(&dcallback);

Can this be rewritten, dropping the prefix from the second line? If not, it would be helpful to know why "extern(C)" is needed here too.

(2) Is there a way to restrict the invocation of a linked C function to one specific D function?

If the C header is defined in one of the core.stdc libraries, the import statement can either be global or inside a specific D function -- both work. In contrast, when the C function prototype is written directly into the D program (as above), the linker complains unless this declaration is made global. If it's possible to restrict the scope of the C function to just one D function, I'll take advantage.

(I'm using dmd, if that makes a difference.)

Thanks
June 26
On Friday, 26 June 2020 at 00:30:22 UTC, Denis wrote:
> I have a two questions about calling C functions from D.
>
> (1) When passing a D callback to a C function, is there a way to write the code without having to prefix the callback declaration with "extern(C)"?
>
> It's not a big deal adding the prefix to the D function declaration. It just seems odd to me to prefix D code with "extern(C)". For example, the following code works:
>
>   extern(C) void cfunc(void function(int));
>   extern(C) void dcallback(int x) {...}		<-- Why extern(C)?
>   cfunc(&dcallback);
>
> Can this be rewritten, dropping the prefix from the second line? If not, it would be helpful to know why "extern(C)" is needed here too.

No, it cannot be dropped. `extern(C)` is required because C and D are using different calling conventions (D functions are also mangled). For example, D (at least DMD and LDC) are passing the arguments to the function in reverse.

> (2) Is there a way to restrict the invocation of a linked C function to one specific D function?
>
> If the C header is defined in one of the core.stdc libraries, the import statement can either be global or inside a specific D function -- both work. In contrast, when the C function prototype is written directly into the D program (as above), the linker complains unless this declaration is made global. If it's possible to restrict the scope of the C function to just one D function, I'll take advantage.

For functions nested in a D language construct (class, struct, function) the compiler will always use the D mangling, instead of the C mangling. In theory it would be possible to workaround that by forcing the mangled name using `pragma(mangle)`, but for some reason the compiler doesn't allow `pragma(mangle)` inside a function body, on a nested function declaration.

You can wrap up everything in a struct, as follows:

struct printf
{
    pragma(mangle, "printf") extern (C) private static int printf(in char*, ...);

    static int opCall(Args...)(Args args)
    {
        return printf(args);
    }
}

void main()
{
    printf("asd\n".ptr);
}

The `printf` function can be called from anywhere within the module, but not outside the module.

--
/Jacob Carlborg
June 26
On Friday, 26 June 2020 at 08:15:27 UTC, Jacob Carlborg wrote:
> On Friday, 26 June 2020 at 00:30:22 UTC, Denis wrote:

>>   extern(C) void cfunc(void function(int));
>>   extern(C) void dcallback(int x) {...}		<-- Why extern(C)?
>>   cfunc(&dcallback);
>>
>> Can this be rewritten, dropping the prefix from the second line? If not, it would be helpful to know why "extern(C)" is needed here too.
>
> No, it cannot be dropped. `extern(C)` is required because C and D are using different calling conventions (D functions are also mangled). For example, D (at least DMD and LDC) are passing the arguments to the function in reverse.

OK, now this makes sense.

I tested calling the same callback function directly from D: it compiled and worked correctly. So at least prefixing the callback function with `extern(C)` doesn't prevent the rest of the D program from calling it too.

>> (2) Is there a way to restrict the invocation of a linked C function to one specific D function?

[...]

> For functions nested in a D language construct (class, struct, function) the compiler will always use the D mangling, instead of the C mangling. In theory it would be possible to workaround that by forcing the mangled name using `pragma(mangle)`, but for some reason the compiler doesn't allow `pragma(mangle)` inside a function body, on a nested function declaration.
>
> You can wrap up everything in a struct, as follows:

I see.

Thank you very much for these explanations and code -- the insights are very helpful.

Denis
June 26
On 2020-06-26 18:54, Denis wrote:

> OK, now this makes sense.
> 
> I tested calling the same callback function directly from D: it compiled and worked correctly. So at least prefixing the callback function with `extern(C)` doesn't prevent the rest of the D program from calling it too.

No, of course not. How would you otherwise call your `cfunc` function from your original example ;)

-- 
/Jacob Carlborg
June 29
On 6/26/20 4:15 AM, Jacob Carlborg wrote:
> On Friday, 26 June 2020 at 00:30:22 UTC, Denis wrote:
>> I have a two questions about calling C functions from D.
>>
>> (1) When passing a D callback to a C function, is there a way to write the code without having to prefix the callback declaration with "extern(C)"?
>>
>> It's not a big deal adding the prefix to the D function declaration. It just seems odd to me to prefix D code with "extern(C)". For example, the following code works:
>>
>>   extern(C) void cfunc(void function(int));
>>   extern(C) void dcallback(int x) {...}        <-- Why extern(C)?
>>   cfunc(&dcallback);
>>
>> Can this be rewritten, dropping the prefix from the second line? If not, it would be helpful to know why "extern(C)" is needed here too.
> 
> No, it cannot be dropped. `extern(C)` is required because C and D are using different calling conventions (D functions are also mangled). For example, D (at least DMD and LDC) are passing the arguments to the function in reverse.

Are you sure? On the ABI page [1] , it says "The extern (C) and extern (D) calling convention matches the C calling convention used by the supported C compiler on the host system."

I'm pretty sure you can use function pointers to D functions for C callbacks, and it should work.

-Steve

[1] https://dlang.org/spec/abi.html#function_calling_conventions
June 29
On Monday, 29 June 2020 at 16:34:33 UTC, Steven Schveighoffer wrote:

> Are you sure? On the ABI page [1] , it says "The extern (C) and extern (D) calling convention matches the C calling convention used by the supported C compiler on the host system."

In that case the documentation is wrong. Here's an example showing the differences:

$ cat foo.c
#include <stdio.h>

void foo(int a, int b)
{
    printf("a=%d b=%d\n", a, b);
}
$ clang -c foo.c
$ cat main.d
pragma(mangle, "foo") extern (D) void foo_extern_d(int, int);
pragma(mangle, "foo") extern (C) void foo_extern_c(int, int);

void main()
{
    foo_extern_d(1, 2);
    foo_extern_c(1, 2);
}
$ dmd main.d foo.o
$ ./main
a=2 b=1
a=1 b=2

This is on macOS.

--
/Jacob Carlborg
June 29
On 6/29/20 1:50 PM, Jacob Carlborg wrote:
> On Monday, 29 June 2020 at 16:34:33 UTC, Steven Schveighoffer wrote:
> 
>> Are you sure? On the ABI page [1] , it says "The extern (C) and extern (D) calling convention matches the C calling convention used by the supported C compiler on the host system."
> 
> In that case the documentation is wrong. Here's an example showing the differences:

Yep, for sure. I'll file an issue. Anyone know why the calling convention would differ?

-Steve
June 30
On Monday, 29 June 2020 at 19:55:59 UTC, Steven Schveighoffer wrote:
> Yep, for sure. I'll file an issue. Anyone know why the calling convention would differ?

It's easier to enforce left to right evaluation order this way: arguments are pushed to stack as they are evaluated, which is pascal calling convention.
June 30
On 6/30/20 3:00 AM, Kagamin wrote:
> On Monday, 29 June 2020 at 19:55:59 UTC, Steven Schveighoffer wrote:
>> Yep, for sure. I'll file an issue. Anyone know why the calling convention would differ?
> 
> It's easier to enforce left to right evaluation order this way: arguments are pushed to stack as they are evaluated, which is pascal calling convention.

Easier, or more efficient? The cost seems high if it doesn't provide any efficiency benefits (i.e. one cannot use extern(D) functions for C callbacks).

In any case, I filed an issue: https://issues.dlang.org/show_bug.cgi?id=20993

-Steve
June 30
On Tuesday, 30 June 2020 at 12:22:15 UTC, Steven Schveighoffer wrote:

> (i.e. one cannot use extern(D) functions for C callbacks).

I don't think that's a big issue. Honestly, I don't think it's an issue at all.

BTW, the order of arguments is not the only thing. Variadic functions in D and C are completely different. I don't think it's possible to implement a C style variadic function with D linkage (the language doesn't provide a syntax for it).

There's also D specific types which C cannot handle (like arrays and delegates).

I'm sure there are other differences in the ABIs.

--
/Jacob Carlborg