如何为SQLite3的C API的sqlite3_exec实现回调包装函数?
问题原因
你遇到的invalid use of non-static member function错误,本质是C和C++的函数模型不兼容:
- SQLite的C API要求的回调是普通C函数指针,而C++类的非静态成员函数会隐含一个
this指针作为第一个参数,签名和C函数指针完全不匹配,所以不能直接传递。 - 代码里两处错误都是这个原因:一是把
Product_Queries的非静态成员函数传给Callback的构造函数(普通函数指针参数),二是把Callback的非静态成员函数传给sqlite3_exec的回调参数。
解决办法
我们用静态函数做中间层,配合sqlite3_exec的第四个参数(用来传数据的void*),把类实例和业务数据传递进去,实现通用逻辑的包装。下面是具体修改后的代码:
1. 重构Callback类
把包装函数改成静态成员,通过sqlite3_exec的data参数拿到Callback实例,先执行通用逻辑,再调用业务回调:
#include <sqlite3.h> #include <iostream> #include <vector> class Callback { public: // 定义业务回调的函数指针类型 using BusinessCallback = int(*)(void* data, int argc, char** argv, char** azColName); Callback(BusinessCallback callback, void* data) : m_callback(callback), m_data(data) {} // 静态包装函数,完全符合sqlite3_exec要求的签名 static int CallbackWrapper(void* callbackInstance, int argc, char** argv, char** azColName) { // 这里写通用逻辑,比如日志、参数校验 std::cout << "[通用逻辑] 处理结果,共" << argc << "列数据" << std::endl; // 把传入的指针转成Callback实例 Callback* cb = static_cast<Callback*>(callbackInstance); // 调用实际的业务回调 return cb->m_callback(cb->m_data, argc, argv, azColName); } private: BusinessCallback m_callback; void* m_data; };
2. 处理业务回调
如果你的业务回调不需要访问Product_Queries的成员变量,直接改成静态成员函数最省事:
class Product_Queries { public: // 改成静态成员函数,和BusinessCallback签名匹配 static int GetProductIds_Callback(void* data, int argc, char** argv, char** azColName){ std::vector<int>* ids = static_cast<std::vector<int>*>(data); if (argv[0]) { // 防空指针 ids->push_back(std::stoi(argv[0])); } return 0; } };
如果业务回调需要访问Product_Queries的成员,就用一个结构体把实例和业务数据打包,再写个静态转接函数:
// 打包业务所需的所有数据 struct ProductCallbackData { Product_Queries* instance; std::vector<int>* productIds; }; class Product_Queries { public: // 保留非静态成员函数,方便访问类内成员 int GetProductIds_Callback(void* data, int argc, char** argv, char** azColName){ ProductCallbackData* cbData = static_cast<ProductCallbackData*>(data); // 这里可以访问cbData->instance的成员,比如cbData->instance->m_someMember if (argv[0]) { cbData->productIds->push_back(std::stoi(argv[0])); } return 0; } }; // 静态转接函数,把C回调转成类成员函数调用 static int ProductCallbackAdapter(void* data, int argc, char** argv, char** azColName) { ProductCallbackData* cbData = static_cast<ProductCallbackData*>(data); return cbData->instance->GetProductIds_Callback(data, argc, argv, azColName); }
3. 修改main函数调用
对应静态业务回调的版本
int main(int argc, char* argv[]) { sqlite3* db; int rc = sqlite3_open("database.db", &db); if (rc != SQLITE_OK) { std::cerr << "数据库打开失败:" << sqlite3_errmsg(db) << std::endl; return rc; } std::vector<int> productIds; // 创建Callback实例,传入静态业务回调和容器指针 Callback callback(Product_Queries::GetProductIds_Callback, &productIds); char* errMsg = nullptr; // 第四个参数传Callback实例指针,供静态包装函数使用 rc = sqlite3_exec(db, "SELECT id FROM Products", Callback::CallbackWrapper, &callback, &errMsg); if (rc != SQLITE_OK) { std::cerr << "查询出错:" << errMsg << std::endl; sqlite3_free(errMsg); } // 打印结果 std::cout << "拿到的产品ID:"; for (int id : productIds) { std::cout << id << " "; } std::cout << std::endl; sqlite3_close(db); return 0; }
对应非静态业务回调的版本
int main(int argc, char* argv[]) { sqlite3* db; int rc = sqlite3_open("database.db", &db); if (rc != SQLITE_OK) { std::cerr << "数据库打开失败:" << sqlite3_errmsg(db) << std::endl; return rc; } Product_Queries product_queries; std::vector<int> productIds; ProductCallbackData cbData = {&product_queries, &productIds}; // 创建Callback实例,传入转接函数和打包的数据 Callback callback(ProductCallbackAdapter, &cbData); char* errMsg = nullptr; rc = sqlite3_exec(db, "SELECT id FROM Products", Callback::CallbackWrapper, &callback, &errMsg); if (rc != SQLITE_OK) { std::cerr << "查询出错:" << errMsg << std::endl; sqlite3_free(errMsg); } std::cout << "拿到的产品ID:"; for (int id : productIds) { std::cout << id << " "; } std::cout << std::endl; sqlite3_close(db); return 0; }
核心要点
- 传给sqlite3的回调必须是静态函数或普通C函数,不能直接用类的非静态成员函数。
- 用
sqlite3_exec的第四个参数传递类实例或业务数据,实现C回调到C++类成员的桥接。 - 包装类的静态函数统一处理通用逻辑,避免每个业务回调重复写相同代码。
内容的提问来源于stack exchange,提问作者g81q5nyd
相关产品推荐
相关产品推荐

