2010-03-10 3 views
9

Итак, я использую FMOD api, и это действительно C api.Функция вызова функции API C в код функции члена C++

Не то чтобы это плохо или что-то еще. Его просто не очень хорошо взаимодействует с кодом на C++.

Например, с помощью

FMOD_Channel_SetCallback(channel, callbackFunc) ; 

Он хочет функцию C-стиль для callbackFunc, но я хочу, чтобы передать его функции-члена класса.

В итоге я использовал трюк Win32 для этого, делая функцию-член статической. Затем он работает как обратный вызов в FMOD.

Теперь мне нужно взломать мой код, чтобы сделать некоторые из членов статическими, просто для учета COD.

Интересно, возможно ли это в FMOD или если есть работа, связанная с обратным вызовом на конкретную функцию экземпляра объекта C++ (а не статическую функцию). Это было бы намного более гладко.

ответ

11

Вы не можете напрямую передать функцию-член. Функция-член имеет неявный параметр this, а функции C - нет.

Вам нужно будет создать батут (не уверен, что подпись обратного вызова, так что просто делайте что-то случайное здесь).

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

Одна проблема заключается в том, откуда это указатель объекта. Надеемся, что fmod предоставит вам общее значение контекста, которое будет предоставлено вам при обратном вызове (вы можете передать указатель объекта).

Если нет, вам просто нужно сделать его глобальным для доступа к нему.

+1

+1 Да, вам нужно сделать батут, но они такие суровые (если вы хотите избежать глобалов и всего этого)! :-(Если API был спроектирован правильно, в первую очередь .... –

+1

Вам также нужно беспокоиться о том, что делать, если функции члена C++ выбрасываются. И откуда взялся этот термин «батут»? – 2010-03-10 20:43:54

+1

@NeilButterworth - I не помню, где я впервые услышал это как «батут», но одна ссылка для моего использования - Wikipedia (http://en.wikipedia.org/wiki/Trampoline_%28computers%29) 'При взаимодействии фрагментов кода с несовместимые соглашения о вызовах, батут используется для преобразования конвенции вызывающего абонента в конвенцию вызываемого. ' –

1

Использование только указателя функции (и дополнительного указателя объекта) для обратного вызова C является сломанным дизайном, по моему скромному мнению.

Если функция была, а не FMOD_Channel_SetCallback(channel, callbackFunc, callbackObj), то ваш статический метод просто принимает экземпляр объекта, а затем вызывает callbackObj->func() (который, очевидно, может быть нестационарным).

+1

Предложите это людям FMOD! Пожалуйста! – bobobobo

+1

@ bobobobo: См. Ответ Дениса. Я думаю, что API уже поддерживает это (очевидно, он изучил FMOD больше, чем я: -P). Я сохраню свой ответ в качестве предостерегающей истории для других разработчиков API, но, по крайней мере, у вас есть решение. –

0

вам нужно использовать батут и хранить указатель на объект, который вы хотите получить функцию-член призвал в глобальной или статической переменной, т.е.

Object *x; 
void callback_trampoline() { x->foobar(); } 
... 
FMOD_Channel_SetCallback(CHANNEL, callback_trampoline); 
4

Я предполагаю, что это должно работать так:
Вы можете назначить некоторые пользовательские данные на канал, вызвав FMOD_Channel_SetUserData. Эти данные пользователя должны быть указателем на ваш объект C++, который обрабатывает события. Затем вы должны написать обратный вызов C-стиля, который извлекает этот объект, вызывая FMOD_Channel_GetUserData, а затем вызывает ваш метод экземпляра C++ для этого объекта.

+0

+1 О, победа! Итак, API не так сломан, как я думал. –

2

Существует не переносное и довольно хакерское решение, которое имеет то преимущество, по крайней мере, быть потокобезопасным, что методы «батут» не являются.

Вы можете генерировать фактический машинный код функции на лету. Основная идея заключается в том, что у вас есть шаблон для вашей функции обратного вызова, который принимает указатель на объект и указатель на функцию-член и дает вам блок памяти кучи, который вы можете передать библиотеке в качестве функции обратного вызова C, которая будет, когда вызвано, развернуться и вызвать функцию-член на этом объекте.

Это беспорядочно, и вам нужно будет обеспечить реализацию для любой новой платформы (в любое время, когда соглашение о вызове будет изменено), но оно работает, является потокобезопасным. (Конечно, вам также придется следить за DEP). Другое поточно-безопасное решение - прибегнуть к локальному хранилищу потоков (при условии, что вы знаете, что обратный звонок будет происходить в том же потоке, что и сделанный вами звонок).

См. http://www.codeproject.com/KB/cpp/GenericThunks.aspx для примера того, как вы могли бы генерировать трюки.