Thread overview
Can't assign extern(C) function pointer to D variable?
Nov 22, 2022
XavierAP
Nov 22, 2022
Hipreme
Nov 22, 2022
XavierAP
Nov 22, 2022
ag0aep6g
November 22, 2022

I was surprised when it didn't compile, though I immediately found it understandable...
Already read through https://dlang.org/spec/interfaceToC.html
and https://wiki.dlang.org/Bind_D_to_C

Is it really the case (that an extern(C) function pointer cannot be assigned to a D variable)? Or is it a matter of annotating with the right attributes? If so, how?

Otherwise I'm interested in the best or most concise workaround. Is there a better one? I came up with a template solution:

https://github.com/XavierAP/game-king/blob/master/source/scope_cleanup.d

The problem I had was that this ScopeCleanup struct could not be constructed passing a pointer to a function imported from C (from the SDL library; you can browse around the same repo to see its usage; it's just a toy project that's barely started).

November 22, 2022

On Tuesday, 22 November 2022 at 21:11:37 UTC, XavierAP wrote:

>

I was surprised when it didn't compile, though I immediately found it understandable...
Already read through https://dlang.org/spec/interfaceToC.html
and https://wiki.dlang.org/Bind_D_to_C

[...]

You need to create an alias containing your callback type.


alias DCallback = extern(C) void function();
DCallback cb;
cb = yourCFunction;
November 22, 2022
On 22.11.22 22:11, XavierAP wrote:
> I was surprised when it didn't compile, though I immediately found it understandable...
> Already read through https://dlang.org/spec/interfaceToC.html
> and https://wiki.dlang.org/Bind_D_to_C
> 
> Is it really the case (that an extern(C) function pointer cannot be assigned to a D variable)? Or is it a matter of annotating with the right attributes? If so, how?

Works for me:

import core.stdc.stdio: puts;
auto p1 = &puts;
extern (C) int function(const char* s) p2 = &puts;

If you're trying to assign an `extern (C)` function pointer to an `extern (D)` one (the default), that cannot work. The compiler would emit code using D's calling convention, but the called function would assume C's calling convention.
November 22, 2022

On Tuesday, 22 November 2022 at 21:32:43 UTC, Hipreme wrote:

>

You need to create an alias containing your callback type.

Thanks both!! I have all the pieces of the puzzle. I'm actually staying with the wrapping template solution. (Because the strongly typed one turns out too convoluted, and because it allows the remaining ScopeCleanup struct to be more general purpose, for non-C functions, and for functions that don't return void but an error code which I want to discard.)

The first problem was indeed that a C function pointer "is not" a D one. So annotating the variable with extern(C) can indeed solve it. I had actually tried this, but it was not compiling for another reason.

The next reason (as you see in the GitHub link) is that the variable in question is a (constructor) parameter. D can't seem to compile extern(C) inlined somewhere else.

Indeed aliasing takes care of this second problem:

alias CFunction = extern(C) void function();

/// RAII object that does nothing but calling, when destructed, the function passed at construction.
struct ScopeCleanup
{
	@disable this();
	this(CFunction cleanup) { this.cleanup = cleanup; }
	~this() { cleanup(); }

	CFunction cleanup;
}

Now this module compiles. BUT the code trying to call this constructor doesn't compile, when called with C function such as SDL_Quit imported from the SDL lib, or IMG_Quit imported from the SDL_image lib.

From the compiler error I learn that the imported function is not only extern(C) but also nothrow @nogc. Fair enough, I add it to the alias. BUT still no good, because (as I learn from the same compiler error) this binding imports these functions as

extern(C) void function() nothrow @nogc*

with this final "*" this turns out, from the D point of view, a "pointer to a function pointer" XD so it has to be called/de-referenced in this way (in destructor):

alias CFunctionPtr = extern(C) void function() nothrow @nogc*;

/// RAII object that does nothing but calling, when destructed, the function passed at construction.
struct ScopeCleanup
{
	@disable this();
	this(CFunctionPtr cleanup) { this.cleanup = cleanup; }
	~this() { (*cleanup)(); }

	CFunctionPtr cleanup;
}

Thanks guys for the learning, I'm staying with the template solution (thanks D), but let me know if you have more insights.