sqlite3_exec()函数引发的血案

我们首先来看看sqlite3_exec()函数的原型:

SQLITE_API int SQLITE_STDCALL 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 */
);

第一个参数是打开sqlite3数据库句柄,第二个参数是你要执行的sql语句,第三个参数是每次查询所调用的回调函数,第四个参数是我们传入的参数,假如说我们在回调函数中找到了我们需要的数据,那么我们就可以通过这个指针来反馈给我们的调用者,第五个参数是调用sqlite3_exec发生错误时候的描述。

int (*callback)(void*,int,char**,char**);

我们这次碰到的问题就是在第三个参数和第四个参数之间的传递问题,我设计了一个查询函数,他将会调用sqlite3_exec()来查询,并且在callback函数当中通过void *这个指针来反馈我们需要的信息。

OK,知道了这些我们就来写一个实例来查询,为了简化我们的程序,不必要的错误判断已经略过

void query()
{
    sqlite3 *pDB=NULL;
    char *result;/*反馈信息*/
    char *cErrMsg;
    int nRes=sqlite3_open("/home/xxx/u.db",&pDB);
    const char *sqlstr="SELECT * FROM 'info'";
    int res=sqlite3_exec(pDB,sqlstr,CB,result,&cErrMsg);
    printf("%s\n",result);
}

下面使我们的回调函数实现:

int CB(void *param,int argc,char **value,char **ColName)
{
    for(int i=0;i<argc;i++)
    {
        if(!strcmp(ColName[i],"url"))
        {
            param=value[i];
        }
    }
    return 0;
}

调用的时候,printf出来的结果是错误的。。。
这是为什么呢?
回到之前的代码,我们传给回调函数的是一个char *指针,而value[i]也是一个char *指针,指向的是我们需要的值,那么我们这样操作应该是没问题的啊?我们会想,是不是因为函数销毁了局部变量呢?
我们改成如下函数:

int CB(void *param,int argc,char **value,char **ColName)
{
    for (int i = 0; i < argc; i++)
    {
	if (!strcmp(azColName[i], "url"))
	{
		char *tmp = (char *)malloc(sizeof(char)*strlen(value[i]));
		tmp = value[i];
		param = tmp;
	}
    }
    return 0;
}

运行之后仍然是错误的,那是为什么呢?
百思不得其解之后,在stackoverflow上面找到一篇被删除的问题:

You are writing the pointer to the newly allocated memory into res, but that variable is a local variable inside select_callback, so sql_exec will not know about it. The same applies to the param parameter: it is just a copy of sqlite3_exec's fourth parameter.

To ensure that your changes to the string are seen, you have to pass a pointer to the string itself (which is a pointer in C, or could be a string object in C++), similar to the error message. For C:

char *result_str = ...;
rc = sqlite3_exec(..., &result_str, ...);
...
int callback(void *param, ...)
{
    char **result_str = (char **)param;
    *result_str = (char *)realloc(*result_str, ...);
    strcpy(*result_str, ...);
}

大概的问题主要是这样的,刚开始我们直接传的是result,但是result是NULL的,而且result代表的是result这个char指针指向的第一个元素位置,这里明显为0x00000000,而我们传给callback函数的就是这个0x00000000地址,因此我们在callback函数里面result=value[i]的含义就是将value[i]的值复制给0x0000000这个不存在的地址,所以肯定不行啦。
我们对之前的程序作如下改动:

int CB(void *param,int argc,char **value,char **ColName)
{
    for(int i=0;i<argc;i++)
    {
        if(!strcmp(ColName[i],"url"))
        {
            char **tmp=(char **)param;
            *param=value[i];
        }
    }
    return 0;
}
void query()
{
    sqlite3 *pDB=NULL;
    char *result;/*反馈信息*/
    char *cErrMsg;
    int nRes=sqlite3_open("/home/xxx/u.db",&pDB);
    const char *sqlstr="SELECT * FROM 'info'";
    int res=sqlite3_exec(pDB,sqlstr,CB,&result,&cErrMsg);
    printf("%s\n",result);
}

在新的程序里面,我们传入的是result的地址,即char result;
然后在callback函数里面我们则强制转换param为char
,然后再对其进行解引用操作就可以将内容传出去了。