Перенос C-функции со статическим параметром обратного вызова в C++- функцию, которая принимает закрытый член в качестве обратного вызова
Я хочу обернуть эту C-функцию,
int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);
в функции C++, как,
namespace
IronMaiden
{
int
BookOfSouls::MyWrapper( DB* db
, std::string& sql
, ?? callback function ?? int (*callback)(void*,int,char**,char**) ?? )
{
.
.
.
sqlite3_exec( db->get(), sql.c_str(), ?? callback ??, nullptr, nullptr);
.
.
.
}
}
Я могу передать любую статическую функцию sqlite3_exec()
, но я хочу передать приватную функцию-член из BookOfSouls
объект в качестве функции обратного вызова, и я хочу получить доступ к частным данным объекта из этой функции.
2 ответа
Итак, после некоторых испытаний и неудач я получил способ ТОЧНО достичь того, что я хочу сделать.
Мое решение не простое... мои страхи сбываются...
Потратив несколько часов, чтобы ползти по SO и Интернету (как в темном, холодном и жутком подземелье, с моим светлым факелом и музыкой Angelscourge), я пришел к выводу, что нет такой вещи, как прямой путь!
/*...I can't blame a C lib to don't support some C++ feature...*/
Итак, мне нужно было найти другую стратегию... так что я интенсивно использовал свой мозг (иначе хедбэнг)... там выскочило решение:
/* The solution uses `libsigc++` AND a static function. */
Статическая функция находится в пространстве имен оболочки. Эта статическая функция сделает одно, испустит сигнал. так что если я возьму тот же пример, что и в вопросе,
namespace
IronMaiden
{
sigc::signal<int,void*,int,char**,char**> signalCB; // `extern` is a friend
.
.
.
static int
callback( void *PersonalUse
, int argc
, char **argv
, char **azColName )
{
return IronMaiden::signalCB.emit( PersonalUse, argc, argv, azColName);
}
.
.
.
int
BookOfSouls::MyWrapper( DB* db
, std::string& sql
, const sigc::slot<int,void*,int,char**,char**>& slotCB )
{
.
.
.
sigc::connection signalConnection = IronMaiden::signalCB.connect( slotCB );
sqlite3_exec( db->get(), sql.c_str(), callback, nullptr, nullptr);
signalConnection.disconnect();
.
.
.
}
}
Чтобы подключить функцию (приватный член), скажем YearOfTheGoat::ImTheCallbackFunction()
, из YearOfTheGoat
объект,
YearOfTheGoat::ImSomeFunctionFromWhereIWantToUseMyWrapper()
{
IronMaiden::MyWrapper( DBobj, transaction
, sigc::mem_fun(*this, &YearOfTheGoat::ImTheCallbackFunction) );
}
так что теперь каждый раз, когда sqlite3_exec()
звонки callback()
, что издаст сигнал, затем вызовет ImTheCallbackFunction()
некоторые важные заметки:
- в этом примере параметры и тип возвращаемого значения
ImTheCallbackFunction()
должны быть идентичны темcallback()
, - будьте осторожны, чтобы отключиться или сигнал останется подключенным.
- У меня пока нет проблем с этим решением. Но вы должны понимать, что я написал этот ответ сразу после того, как написал код в своей программе. Так что я не собираюсь утверждать, что это ЛУЧШЕЕ решение. Но это работает!
- заметка о
sqlite3_exec
Кажется, что функция cb вызывается только в том случае, если есть, что возвращать. - не путайся между
BookOfSouls
а такжеYearOfTheGoat
, Первое НЕ МАТЕРИАЛЬНО. Во-вторых, я использовал объект. (NB сBookOfSouls
не является нематериальным, движущимсяcallback
а такжеsigc::signal
внутриBookOfSouls
неплохая идея).
комментарии очень приветствуются
Ключ является void*
аргумент:
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
Обратный вызов является бесплатной функцией, но вы можете предоставить произвольный первый аргумент в. Поэтому, если вы хотите передать функцию-член, мы можем воспользоваться тем фактом, что лямбда без захвата может быть преобразована в указатель на функцию (благодаря некоторому колдовству):
template <typename F, int (F::*mem)(int, char**, char**)>
void set_callback(F* f) {
sqlite3_exec(db->get(), sql.c_str(),
+[](void* arg, int i, char** p, char**q){
return (static_cast<F*>(arg)->*mem)(i, p, q);
},
f,
nullptr, nullptr);
}
Обратите внимание, что функция-член также должна быть аргументом шаблона, чтобы лямбда не захватывала. Было бы проще, если бы вам просто потребовалось конкретное имя функции, например sqlCallback()
тогда ваша функция обратного вызова будет:
+[](void* arg, int i, char** p, char** q) {
return static_cast<F*>(arg)->sqlCallback(i, p, q);
}