如何在SQLExecDirect/SQLExecute前后确定绑定输入参数的类型、内存位置与值?
ODBC API拦截技术
直接拦截SQLBindParameter、SQLExecDirect、SQLExecute这几个ODBC核心API调用。当程序调用SQLBindParameter时,把参数的类型、内存地址、缓冲区长度这些信息存下来;等到执行SQL的API被调用时,再根据之前存的内存地址去读取当前参数的实际值。
实现上,Windows平台可以用Detours库做钩子,Linux下用LD_PRELOAD机制拦截动态库中的ODBC函数就行。需要注意的是,不同参数类型(比如字符串、整数、日期)的内存读取方式不一样,得根据绑定的cType(比如SQL_C_CHAR、SQL_C_LONG)来做对应解析。自定义ODBC封装层
如果是你自己开发的程序,完全可以把ODBC的API包一层。在封装的SQLBindParameter里,把每个参数的元数据(类型、内存位置、长度指示器)存在一个列表里;等到调用SQLExecDirect或者SQLExecute之前,遍历这个列表,逐个读取参数的当前值。
举个简单的伪代码例子:// 定义结构体存参数信息 typedef struct { SQLSMALLINT cType; // C语言类型 SQLSMALLINT sqlType; // SQL类型 SQLPOINTER paramValuePtr; // 参数内存地址 SQLLEN *strLen_or_IndPtr; // 长度/指示器指针 } BoundParam; static BoundParam boundParams[100]; // 假设最多绑定100个参数 static int paramCount = 0; // 封装SQLBindParameter SQLRETURN my_SQLBindParameter(SQLHSTMT hstmt, SQLUSMALLINT paramNumber, ...) { SQLRETURN ret = SQLBindParameter(hstmt, paramNumber, ...); if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { // 把参数信息存到数组里 boundParams[paramCount++] = {cType, sqlType, paramValuePtr, strLen_or_IndPtr}; } return ret; } // 封装SQLExecDirect SQLRETURN my_SQLExecDirect(SQLHSTMT hstmt, SQLCHAR *szSqlStr, SQLINTEGER cbSqlStr) { // 遍历参数列表,读取并输出值 for (int i = 0; i < paramCount; i++) { BoundParam *p = &boundParams[i]; if (p->cType == SQL_C_CHAR) { printf("参数%d:类型SQL_C_CHAR,值:%s,内存地址:%p\n", i+1, (char*)p->paramValuePtr, p->paramValuePtr); } else if (p->cType == SQL_C_LONG) { printf("参数%d:类型SQL_C_LONG,值:%ld,内存地址:%p\n", i+1, *(long*)p->paramValuePtr, p->paramValuePtr); } // 其他类型(比如日期、浮点数)同理处理 } // 调用原始API执行SQL return SQLExecDirect(hstmt, szSqlStr, cbSqlStr); }利用ODBC驱动的内置追踪
不少ODBC驱动自带参数日志功能,比如SQL Server的ODBC驱动。你可以在ODBC数据源管理器里找到对应的数据源,开启追踪选项,指定日志文件路径。程序运行后,日志里会记录绑定的参数类型和实际值,不过有些驱动可能只记录类型,需要看具体驱动的配置项。这种方式不用改代码,适合快速排查问题。调试器手动排查
如果只是临时调试,直接用调试器就行。在SQLExecDirect或SQLExecute处打个断点,程序停住后,找到之前SQLBindParameter调用时保存的paramValuePtr(参数内存地址),在调试器的内存窗口里输入这个地址,结合绑定的类型来查看值。比如Visual Studio里直接在内存窗口输入地址,字符串类型就看ASCII内容,整数类型就看对应的数值。
内容的提问来源于stack exchange,提问作者Eugène Bindels

