www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Problems with function as parameter

reply Josh <moonburntm gmail.com> writes:
I'm trying to write a callback function for SDL_mixer through 
Derelict, but this is the first time I've tried to use a function 
as a parameter, and so I think I'm just making a minor mistake 
somewhere.

The C SDL docs say:
****************************************************
// make a channelDone function
void channelDone(int channel)
{
     printf("channel %d finished playing.\n", channel);
}
...
// set the callback for when a channel stops playing
Mix_ChannelFinished(channelDone);
****************************************************

And my D code is:
****************************************************
void unmuteAfterPlaySound()
{
	Mix_ChannelFinished(channelDone);
}

void channelDone(int channel)
{
	unmuteMusic();
}
****************************************************

But DMD seems to be trying to run channelDone() i.e. with no 
parameters and parentheses omitted, and is giving this error:

Error: function Mixer.channelDone (int channel) is not callable 
using argument types ()
Error: function pointer Mix_ChannelFinished (extern (C) void 
function(int channel)) is not callable using argument types 
(_error_)

Is anyone able to see what I've done wrong? Any help would be 
appreciated, thanks.
Sep 21
parent reply Matt Jones <matthew.brennan.jones gmail.com> writes:
On Thursday, 21 September 2017 at 20:21:58 UTC, Josh wrote:
 I'm trying to write a callback function for SDL_mixer through 
 Derelict, but this is the first time I've tried to use a 
 function as a parameter, and so I think I'm just making a minor 
 mistake somewhere.

 [...]
Make it a C function, not a D function: extern (C) void channelDone(int channel) { unmuteMusic(); } and use & to reference the function instead of calling it: Mix_ChannelFinished(&channelDone);
Sep 21
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Thursday, 21 September 2017 at 22:05:22 UTC, Matt Jones wrote:
 On Thursday, 21 September 2017 at 20:21:58 UTC, Josh wrote:
 I'm trying to write a callback function for SDL_mixer through 
 Derelict, but this is the first time I've tried to use a 
 function as a parameter, and so I think I'm just making a 
 minor mistake somewhere.

 [...]
Make it a C function, not a D function: extern (C) void channelDone(int channel) { unmuteMusic(); } and use & to reference the function instead of calling it: Mix_ChannelFinished(&channelDone);
It should be extern(C) and, ideally, nothrow. The binding should be enforcing nothrow, but currently doesn't. I'll change that now.
Sep 21
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Friday, 22 September 2017 at 02:18:34 UTC, Mike Parker wrote:

 and use & to reference the function instead of calling it:

 Mix_ChannelFinished(&channelDone);
To expand on this, D allows functions to be called without parentheses. `channelDone` is actually a function call, whereas in C, it's treated as a function pointer. In D, you have to take the address of a function just as you would any other type, hence `&channelDone`.
Sep 21
prev sibling parent reply Josh <moonburntm gmail.com> writes:
On Friday, 22 September 2017 at 02:18:34 UTC, Mike Parker wrote:
 On Thursday, 21 September 2017 at 22:05:22 UTC, Matt Jones 
 wrote:
 On Thursday, 21 September 2017 at 20:21:58 UTC, Josh wrote:
 I'm trying to write a callback function for SDL_mixer through 
 Derelict, but this is the first time I've tried to use a 
 function as a parameter, and so I think I'm just making a 
 minor mistake somewhere.

 [...]
Make it a C function, not a D function: extern (C) void channelDone(int channel) { unmuteMusic(); } and use & to reference the function instead of calling it: Mix_ChannelFinished(&channelDone);
It should be extern(C) and, ideally, nothrow. The binding should be enforcing nothrow, but currently doesn't. I'll change that now.
Why should the binding force nothrow? I don't understand why you HAVE to not throw exceptions. Is it because of the C -> D aspect?
Sep 21
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Friday, 22 September 2017 at 02:32:56 UTC, Josh wrote:

 Why should the binding force nothrow? I don't understand why 
 you HAVE to not throw exceptions. Is it because of the C -> D 
 aspect?
Yes, it's because D exceptions are not guaranteed to propagate through the C callstack. It works on some compiler/platforms, but not others. I wrote an article at Gamedev.net (which apparently no longer picks up the site's stylesheets) which shows one way of dealing with it. I've modified my approach since then (and should write a better article about it), but the basic idea still holds.
Sep 21
prev sibling parent Mike Parker <aldacron gmail.com> writes:
On Friday, 22 September 2017 at 02:32:56 UTC, Josh wrote:

 Why should the binding force nothrow? I don't understand why 
 you HAVE to not throw exceptions. Is it because of the C -> D 
 aspect?
Yes, it's because D exceptions are not guaranteed to propagate through the C callstack. It works on some compiler/platforms, but not others. I wrote an article at Gamedev.net (which apparently no longer picks up the site's stylesheets) which shows one way of dealing with it. I've modified my approach since then (and should write a better article about it), but the basic idea still holds. https://www.gamedev.net/articles/programming/general-and-gameplay-programming/d-exceptions-and-c-callbacks-r3323/
Sep 21
prev sibling parent reply Josh <moonburntm gmail.com> writes:
On Thursday, 21 September 2017 at 22:05:22 UTC, Matt Jones wrote:
 On Thursday, 21 September 2017 at 20:21:58 UTC, Josh wrote:
 I'm trying to write a callback function for SDL_mixer through 
 Derelict, but this is the first time I've tried to use a 
 function as a parameter, and so I think I'm just making a 
 minor mistake somewhere.

 [...]
Make it a C function, not a D function: extern (C) void channelDone(int channel) { unmuteMusic(); } and use & to reference the function instead of calling it: Mix_ChannelFinished(&channelDone);
Thanks for the help, but when I try that, I get: src\mixer.d(80,22): Error: function pointer Mix_ChannelFinished (extern (C) void function(int channel)) is not callable using argument types (extern (C) void delegate(int channel)) Code: void unmuteAfterPlaySound() { Mix_ChannelFinished(&channelDone); } extern (C) void channelDone(int channel) { unmuteMusic(); }
Sep 21
parent reply Mike Parker <aldacron gmail.com> writes:
On Friday, 22 September 2017 at 02:22:46 UTC, Josh wrote:

 src\mixer.d(80,22): Error: function pointer Mix_ChannelFinished 
 (extern (C) void function(int channel)) is not callable using 
 argument types (extern (C) void delegate(int channel))

 Code:
 void unmuteAfterPlaySound()
 {
 	Mix_ChannelFinished(&channelDone);
 }

 extern (C) void channelDone(int channel)
 {
 	unmuteMusic();
 }
The error message indicates that `channelDone` is a member of a class or a struct. A pointer to a member function is a delegate (or closure), not a function pointer. Free functions, static nested functions, and static member functions all produce function pointer. Non-static member functions and non-static nested functions all produce delegates/closures. See the docs: https://dlang.org/spec/function.html#closures If you need to manipulate instance members from a C callback, you'll need a way to implement a mechanism to work out which instance you need.
Sep 21
parent reply Josh <moonburntm gmail.com> writes:
On Friday, 22 September 2017 at 03:26:36 UTC, Mike Parker wrote:
 On Friday, 22 September 2017 at 02:22:46 UTC, Josh wrote:

 src\mixer.d(80,22): Error: function pointer 
 Mix_ChannelFinished (extern (C) void function(int channel)) is 
 not callable using argument types (extern (C) void 
 delegate(int channel))

 Code:
 void unmuteAfterPlaySound()
 {
 	Mix_ChannelFinished(&channelDone);
 }

 extern (C) void channelDone(int channel)
 {
 	unmuteMusic();
 }
The error message indicates that `channelDone` is a member of a class or a struct. A pointer to a member function is a delegate (or closure), not a function pointer. Free functions, static nested functions, and static member functions all produce function pointer. Non-static member functions and non-static nested functions all produce delegates/closures. See the docs: https://dlang.org/spec/function.html#closures If you need to manipulate instance members from a C callback, you'll need a way to implement a mechanism to work out which instance you need.
Perfect, that's the info I needed. As these functions were in a class, setting channelDone and unmuteMusic to static worked. As an aside, in that doc it says "The .funcptr property of a delegate will return the function pointer value as a function type". So I also tried Mix_ChannelFinished((&channelDone).funcptr); and this compiled, but caused a segfault when the callback ran. What would have caused this? Is it because it's a C function?
Sep 21
parent user1234 <user1234 12.hu> writes:
On Friday, 22 September 2017 at 04:32:08 UTC, Josh wrote:
 As an aside, in that doc it says "The .funcptr property of a 
 delegate will return the function pointer value as a function 
 type". So I also tried 
 Mix_ChannelFinished((&channelDone).funcptr); and this compiled, 
 but caused a segfault when
 the callback ran. What would have caused this? Is it because 
 it's a C function?
No it's because in this function are used variables that are specific to the class instance (the this). If there weren't this would work, even if it's not a good idea to do that: struct Foo { int a; void needThisYeahReally(){a = 0;} void needThisButWorkWithout(){} } void main() { Foo foo; { auto dg = &foo.needThisButWorkWithout; dg.funcptr(); } { auto dg = &foo.needThisYeahReally; // dg.funcptr(); // segfault because of access to this.a } } Using a pointer to a static member function was the right thing to do.
Sep 22