views:

468

answers:

5

So, I'm using the FMOD api and it really is a C api.

Not that that's bad or anything. Its just it doesn't interface well with C++ code.

For example, using

FMOD_Channel_SetCallback( channel, callbackFunc ) ;

It wants a C-style function for callbackFunc, but I want to pass it a member function of a class.

I ended up using the Win32 trick for this, making the member function static. It then works as a callback into FMOD.

Now I have to hack apart my code to make some of the members static, just to account for FMOD's C-ness.

I wonder if its possible in FMOD or if there's a work around to link up the callback to a specific C++ object's instance member function (not a static function). It would be much smoother.

+2  A: 

Using only a function pointer (and no additional separate object pointer) for a C callback is a broken design, in my humble opinion.

If the function were, instead, FMOD_Channel_SetCallback(channel, callbackFunc, callbackObj), then your static method just takes an instance of the object, then calls callbackObj->func() (which obviously can be non-static).

Chris Jester-Young
Suggest this to the FMOD people! Please!
bobobobo
@bobobobo: See Denis's answer. I think the API already supports this (obviously he's researched FMOD more than I have :-P). I'll keep my answer up as a cautionary tale for other API designers, but, at least you do have a solution.
Chris Jester-Young
+4  A: 

You cannot directly pass a member function. A member function has the implicit parameter this and C functions don't.

You'll need to create a trampoline (not sure the signature of the callback, so just doing something random here).

extern "C" int fmod_callback( ... args ...)
{
    return object->member();
}

One issue is where does that object pointer come from. Hopefully, fmod gives you a generic context value that will be provided to you when your callback is made (you can then pass in the object pointer).

If not, you'll just need to make it a global to access it.

R Samuel Klatchko
+1 Yes, you do have to make a trampoline, but they're so gnarly (if you want to avoid globals, and all that)! :-( If the API were designed correctly in the first place....
Chris Jester-Young
You also need to worry about what to do if the C++ member functions throws. And where did this "trampoline" term come from?
anon
@NeilButterworth - I don't recall where I first heard this described as a "trampoline", but one reference for my usage is Wikipedia (http://en.wikipedia.org/wiki/Trampoline_%28computers%29) `When interfacing pieces of code with incompatible calling conventions, a trampoline is used to convert the caller's convention into the callee's convention.`
R Samuel Klatchko
@R Samuel Klatchko Thanks for the link. So basically another word for a "thunk" I think.
anon
@NeilButterworth - definitely - I consider a "trampoline" and a "thunk" synonymous.
R Samuel Klatchko
A: 

Hi,

you need to use a trampoline and store the pointer to the object you want to get the member function called on in a global or static variable, i.e.

Object *x;
void callback_trampoline() { x->foobar(); }
...
FMOD_Channel_SetCallback(CHANNEL, callback_trampoline);
antti.huima
+3  A: 

I guess it supposed to work like this:
You can assign some user data to channel by calling FMOD_Channel_SetUserData. This user data should be a pointer to your C++ object that handles events. Then you should write C-style callback that extracts that object by calling FMOD_Channel_GetUserData and then calls your C++ instance method on that object.

Denis Krjuchkov
+1 Oh, win! So, the API isn't as broken as I thought.
Chris Jester-Young
+1  A: 

There is a non-portable, and pretty hackish solution that has the advantage of at least being thread-safe, which the "trampoline" methods are not.

You can generate the actual function machine code on the fly. The basic idea is that you have a template for your call-back function that takes an object pointer and a member-function pointer and gives you a block of heap memory that you can pass to the library as a C call-back function, that will, when called, turn around and call the member function on that object.

It's messy, and you'll have to provide an implementation for any new platform (any time the calling convention changes), but it works, is thread-safe. (Of course you'll also have to watch out for DEP). The other thread-safe solution is to resort to thread-local storage (assuming that you know the call-back will happen on the same thread as the call you made).

See http://www.codeproject.com/KB/cpp/GenericThunks.aspx for an example of how you could go about generating thunks.

Eclipse