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.

Reply via email to