天天看點

通過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<int, OCIStmt *> m_outStmt;
	map<int, DBOutBuf *> 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被重新指向新的位址,不了解。