通过OCI库执行带输出参数和游标的存储过程造成程序崩溃的查错过程

手中有一套OCI操作的类似于JDBC的封装,要添加存储过程中对输出参数的支持,通过查看手册,只要调用OCIBindByName或者ByPos进行绑定即可,改好后代码大概是这样的:

class OciStatement : public DBStatement
{
public :
	//...

	void regOutParam(int idx, int type, int max);

protected:

	void doExecute(int times);

protected:
	OCIEnv		*	m_hEnv	;
	OCIError	*	m_hError;
	OCISvcCtx	*	m_hSvc	;
	
	OCIStmt * m_stmtp;

	map m_outStmt;
	map m_outData;
};


void OciStatement::doExecute(int times) 
{
	sword swResult;
	swResult = OCIStmtExecute(m_hSvc, m_stmtp,  m_hError
			, times, 0, NULL, NULL, OCI_DEFAULT);
	if (!checkResult(swResult))
		makeException(m_hError, swResult, m_sql.c_str());
}
	
void OciStatement::regOutParam(int idx, int type, int max)
{
	sword swResult;
	OCIBind * bindp = NULL; 

	if (type == DBCT_RESULT)
	{
		OCIStmt * stmtp = NULL;
		swResult = OCIHandleAlloc(m_hEnv, (void **)&stmtp
				, OCI_HTYPE_STMT, 0, NULL);
		if (!checkResult(swResult))
		{
			makeException(m_hError, swResult, m_sql.c_str());
		}
		else
		{
			m_outStmt[idx] = stmtp;

			swResult = OCIBindByPos(m_stmtp
					, &bindp
					, m_hError
					, idx
					, (void *)&stmtp
					, (sb4) 0
					, SQLT_RSET
					, (dvoid *)0
					, (ub2 *)0
					, (ub2 *)0
					, (ub4)0
					, (ub4 *)0
					, (ub4)OCI_DEFAULT);
		}
	}
	else if (type == DBCT_INTEGER)
	{
		DBOutBuf * out = new DBOutBuf(max);
		m_outData[idx] = out;

		swResult = OCIBindByPos(m_stmtp 
				, &bindp 
				, m_hError 
				, idx 
				, (void *)out->buf
				, out->BUFFERLEN
				, SQLT_INT 
				, (dvoid *)&out->flag
				, (ub2 *)&out->datalen 
				, (ub2 *)0 
				, (ub4)0  
				, (ub4 *)0 
				, OCI_DEFAULT ); 
	}
	else
	{
		//...
	}
	if (!checkResult(swResult)) 
		makeException(m_hError, swResult, m_sql.c_str());
}

绑定其它类型的都没有问题,唯独只有当输出类型为游标对象时,程序直接崩溃,通过core分析,在执行OCIStmtExecute时,在oracle的库里崩溃了,访问非法内存。而直接使用OCI创建的测试程序则没有问题,仔细分析了才找到了问题,绑定其它类型时,传入的是内存地址,而绑定游标时,传入的是OCIStmt对象的指针的指针,如下:

OCIStmt * stmtp = NULL;
swResult = OCIHandleAlloc(m_hEnv, (void **)&stmtp
		, OCI_HTYPE_STMT, 0, NULL);
if (!checkResult(swResult))
{
	makeException(m_hError, swResult, m_sql.c_str());
}
else
{
	m_outStmt[idx] = stmtp;

	swResult = OCIBindByPos(m_stmtp
			, &bindp
			, m_hError
			, idx
			, (void *)&stmtp	//注意,这里传入的是OCIStmt**
                                //栈上的对象stmtp在函数结束后就没有了
                                //再执行doExecute中调用OCIStmtExecute就非法访问了
                                //做OCI的测试程序时bind和exec全在main中,就不会出错
			, (sb4) 0
			, SQLT_RSET
			, (dvoid *)0
			, (ub2 *)0
			, (ub2 *)0
			, (ub4)0
			, (ub4 *)0
			, (ub4)OCI_DEFAULT);
}

找到问题就好处理了,记录在这儿吧,对指针的使用真是需要小心再小心。

不知道为什么OCI库会采用这样的设计,使用中也未发现stmtp被重新指向新的地址,不理解。

你可能感兴趣的:(数据库,开发)