Windows平台下基于OLEDB模板的数据库编程技术汇总

本文详细介绍了Windows平台下使用OLEDB模板进行数据库编程的方法,包括初始化、连接数据库、错误处理、CAccessor、CDynamicAccessor、CManualAccessor等的使用。文章还提出了一些常见问题的解决方案,如数据类型的转换问题,并提供了二进制BLOB数据的读写示例。此外,作者还分享了自己改写CDynamicAccessor以支持不同数据类型的绑定的代码,以提高灵活性和兼容性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Windows 平台下基于 OLEDB 模板数据库编程稳定性强,兼容性好、代码量小、适用性广,绿色,可用于各种规模应用的开发。
但是网上包括 MSDN 上可供查阅的资料极少,笔者在踩了太多坑后将其基本编程技术汇总,学弟学妹们通过此文可快速上手。
文中代码基于 Windows、C++/MFC,稍加改动可用于无 MFC 场景(OLEDB模板不需要MFC支持)。

笔者邮箱:njhuangf@163.com

1 初始化、连接数据库、错误信息的获取

1.1 数据库种类的选择和其驱动程序
以下情况无需安装对应驱动
1、开发 32 位应用使用 Access(mdb)、SQL Server、Oracle
2、开发 64 位应用使用 SQL Server
以下情况需在 Windows 中安装对应 OLEDB/ODBC 驱动
1、开发 64 位应用使用 Access(mdb)
2、开发 32/64 位应用使用 Access(accdb)
3、开发 64 位应用使用 Oracle
4、开发 32/64 位应用使用 MySQL、SQLite 等

1.2 初始化

// 在 CXXXApp::InitInstance()中增加以下代码
::AfxOleInit(); // 这个是单线程的,有时程序需要用COM多线程初始化,已知对 OLEDB 无影响,具体请自行查阅相关资料。

// 在 CXXXApp::ExitInstance()中增加以下代码
::AfxOleTerm(FALSE);

1.3 连接数据库
以下两个变量定义为全局变量或CXXXApp的成员变量,其中 m_Session 应在程序全局可访问。后文假设 m_Session 定义为 CXXXApp 的成员变量。

CSession	m_Session;		// 数据源会话对象(每次操作数据库均会使用)
CDataSource	m_Source;		// 数据源对象(只在连接数据库时使用,但退出前不要关闭)

以下连接数据库操作在程序启动时执行一次,例如可在 CXXXApp::InitInstance() 中 ::AfxOleInit() 后执行,后面每次操作数据库时均会使用到其中的 m_Session。

// OLE DB 连接字符串规范同 .net 的 ADO
CString sConnection = L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\\\...\\mydb.mdb;"

if (FAILED(m_Source.OpenFromInitializationString(CComBSTR(sConnection))) ||
    FAILED(m_Session.Open(m_Source)))
{	
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码, GetDBErrInfo() 定义参见下节
	return FALSE;
}

1.4 运行过程中错误信息的获取
每次数据库操作通过 if (FAILED(…)) 发现错误后立即调用以下函数可获取错误信息,可不判断 HRESULT 返回值。

CString GetDBErrInfo()
{
	CDBErrorInfo dbErrInf;
	ULONG nError = 0;
	WCHAR sError[255] = { 0 };
	CString sErrMsg;

	dbErrInf.GetErrorRecords(&nError);

	if (nError > 0)
	{
		CComBSTR bsErrInf;
		ERRORINFO errinfo;
		dbErrInf.GetBasicErrorInfo(0, &errinfo);
		dbErrInf.GetAllErrorInfo(0, ::GetUserDefaultLCID(), &bsErrInf);

		::swprintf_s(sError, L" (0x%X)", errinfo.hrError);
		sErrMsg = (LPCTSTR)bsErrInf;
		sErrMsg += sError;
	}

	return sErrMsg;
}

2 简单常用的存取访问器 CAccessor

使用 VS 向导即可以生成基于 CAccessor 的数据库访问代码,是 OLEDB 最常用模板之一。
CAccessor 可以根据传递的数据类型自动绑定,例如在数据库中某一列的数据类型是日期/时间型,传递 WCHAR[n]、DATE、DBTIMESTAMP 等 C++ 数据类型均可实现绑定。
使用 CAccessor 时事先要知道数据库准确结构或 SQL 语句返回的列及其数据类型,且每次访问数据库均需要定义一个结构,所以在一些稍复杂的应用中 CAccessor 就显得不够灵活。
2.1 无参数读

// 定义数据结构
struct CMyBind
{
	int		m_nCol0; 
	DATE	m_tCol1;
	WCHAR	m_sCol2[255];

	BEGIN_COLUMN_MAP(CMyBind)
		COLUMN_ENTRY(1, m_nCol0)
		COLUMN_ENTRY(2, m_tCol1)
		COLUMN_ENTRY(3, m_sCol2)
	END_COLUMN_MAP()
};

// 定义存取器,后面的两个 > 之间要加一个空格,否则编译不通过
CCommand<CAccessor<CMyBind> > rs;

// 执行
if (FAILED(rs.Open(theApp.m_Session, L”SELECT Col0,Col1,Col2 FROM MYTABLE”)))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

// 读取返回的结果集
while (rs.MoveNext() == S_OK)
{	
	int				nCol0 = rs.m_nCol0; 
	COleDateTime	tCol1 = rs.m_tCol1;
	CString			nCol2 = rs.m_nCol2;

	// TODO:自己的业务处理代码 …
}

2.2 带参数读

// 定义数据结构
struct CMyBind
{
	int		m_nCol0; 
	DATE	m_tCol1;
	WCHAR	m_sCol2[255];

	BEGIN_COLUMN_MAP(CMyBind)
		COLUMN_ENTRY(1, m_tCol1)
		COLUMN_ENTRY(2, m_sCol2)
	END_COLUMN_MAP()

	BEGIN_PARAM_MAP(CMyBind)
		COLUMN_ENTRY(1, m_sCol0)
	END_PARAM_MAP()
};

// 定义存取器,后面的两个 > 之间要加一个空格,否则编译不通过
CCommand<CAccessor<CMyBind> > rs;

rs.m_nCol0 = 1; // 传递参数

// 执行
if (FAILED(rs.Open(theApp.m_Session, L"SELECT Col1,Col2 FROM MYTABLE WHERE Col0=?")))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

// 读取返回的结果集
while (rs.MoveNext() == S_OK)
{
	COleDateTime	tCol1 = rs.m_tCol1;
	CString			nCol2 = rs.m_nCol2;

	// TODO:自己的业务处理代码 …
}

2.3 读 BLOB 二进制数据

// 定义数据结构
struct CMyBind
{
	ISequentialStream*	m_pISequentialStream;
	WCHAR				m_sRecordMemo[255];	
	DATE				m_tRecordDate;

	BEGIN_COLUMN_MAP(CMyBind)
		BLOB_ENTRY(1, IID_ISequentialStream, STGM_READ, m_pISequentialStream)
		COLUMN_ENTRY(2, m_sRecordMemo)
		COLUMN_ENTRY(3, m_tRecordDate)
	END_COLUMN_MAP()
};

// 定义存取器,后面的两个 > 之间要加一个空格,否则编译不通过
CCommand<CAccessor<CMyBind> > rs;

// 执行
CString sSQLCmd = L"SELECT Photo,RecordMemo,RecordDate FROM TBPHOTO WHERE ID=1";
if (FAILED(rs.Open(theApp.m_Session, sSQLCmd)))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return 0;
}

// 读取返回的结果集
while (rs.MoveNext() == S_OK)
{
	COleDateTime 	tRecordDate	= rs.m_tRecordDate;
	CString			sRecordMemo	= rs.m_sRecordMemo;

	CComPtr<IStream> pIStream = NULL; 

	if (rs.m_pISequentialStream != NULL)
	{
		ULONG cb = 0;
		::CreateStreamOnHGlobal(NULL, TRUE, &pIStream);
			
		do
		{
			BYTE btBuffer[4001] = { 0 };
			rs.m_pISequentialStream->Read(btBuffer, 4000, &cb);
	
			if (cb > 0)
				pIStream->Write(btBuffer, cb, &cb);
	
		} while (cb > 0);
	}
	
	// 读取到的二进制数据已保存在 pIStream 中 
	// TODO:自己的业务处理代码 …
}

2.4 仅参数写

// 定义数据结构
struct CMyBind
{
	int		m_nCol0; 
	DATE	m_tCol1;
	WCHAR	m_sCol2[255];

BEGIN_PARAM_MAP(CMyBind)
	COLUMN_ENTRY(1, m_sCol0)
	COLUMN_ENTRY(2, m_nCol1)
	COLUMN_ENTRY(3, m_sCol2)
END_PARAM_MAP()
};

// 定义存取器,后面的两个 > 之间要加一个空格,否则编译不通过
CCommand<CAccessor<CMyBind> > rs;

// 传递参数
m_nCol0 = 1;
m_nCol1 = COleDateTime::GetCurrentTime();
::wcscpy_s(rs.m_sCol2, L"A");

// 执行
if (FAILED(rs.Open(theApp.m_Session, L"UPDATE MYTABLE SET Col0=?,Col1=? WHERE Col2=?")))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

3 执行无参数、无返回结果集操作时推荐使用 CManualAccessor

CManualAccessor 手工绑定数据的代码很冗长(MSDN中有示例),有参数或有返回结果集时更推荐使用前文 CAccessor 和后文CDynamicAccessor 系列。

在上一节(仅参数写)中的代码使用 CManualAccessor 改写如下(代码量减少):

// 定义存取器
CCommand<CManualAccessor> rs;

// SQL中 NOW() 是 Access 支持的函数,其它数据库可能需要改为其它形式,例如:’2020-10-10 12:00:00’
if (FAILED(rs.Open(theApp.m_Session, L"UPDATE MYTABLE SET Col0=1,Col1=NOW() WHERE Col2=’A’")))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

4 更实用的 CDynamicAccessor 系列

CDynamicAccessor 系列包括 CDynamicAccessor、CDynamicStringAccessor、CDynamicParamAccessor,后两者继承自前者。

PS. 有相关文档表示在运行性能方面:
数据库访问方式:OLEDB SDK > OLEDB模板 > ADO > ODBC
OLEDB模板:CAccessor> CManualAccessor > CDynamicAccessor 系列
但实际这些运行性能的差异相比不同数据库性能的差异,是完全可以忽略不计的。

4.1 CDynamicAccessor 无参数读

// 定义存取器
CCommand<CDynamicAccessor> rs;

// 执行
if (FAILED(rs.Open(theApp.m_Session, L”SELECT Col0,Col1,Col2 FROM MYTABLE”)))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

// 读取返回的结果集
while (rs.MoveNext() == S_OK)
{	
	int				nCol0 = *(int*)rs.GetValue(1); 		// 强制类型转换 !!!
	COleDateTime	tCol1 = *(DATE*)rs.GetValue(2); 	// 强制类型转换 !!!
	CString			nCol2 = (WCHAR*)rs.GetValue(3); 	// 强制类型转换 !!!

	// TODO:自己的业务处理代码 …
}

使用 CDynamicAccessor 存在三个问题:

  1. 在某些情况下,无法准确判断返回结果集中的部分数据类型,可能导致强制类型转换出错,例如:SELECT SUM(…) FROM … GROUP BY …,第一列有时返回是整型,有时返回是浮点型,类似情况还较多。
  2. 无法方便的读取返回结果集中的长文本数据或 BLOB(需要进行地址转换,代码略)。
  3. 如果有参数则需要手工绑定参数,代码冗长。

4.2 CDynamicStringAccessor 无参数读
为了解决 CDynamicAccessor 存在的第1、2个问题,即类型转换困难的问题,可以使用 CDynamicStringAccessor,其返回的结果集中的数据已全部转换成字符串,包括长文本和二进制BLOB数据。
如果某一列是二进制BLOB,其返回的字符串类似“5F3A…”格式,是二进制转换成的字符串,需自行转换成最终的二进制数据。

// 定义存取器
CCommand<CDynamicStringAccessor> rs;

// 执行
if (FAILED(rs.Open(theApp.m_Session, L”SELECT Col0,Col1,Col2 FROM MYTABLE”)))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

// 读取返回的结果集
while (rs.MoveNext() == S_OK)
{	
	Int nCol0 = ::ttoi(rs.GetString(1)); 
	COleDateTime tCol1;
 	tCol1.ParseDateTime(rs.GetString(2));
	CString nCol2 = rs.GetString(3);

	// TODO:自己的业务处理代码 …
}

4.3 CDynamicParamAccessor 带参数读写
为了解决CDynamicAccessor存在的第3个问题,即绑定参数不方便的问题,可以使用其另一个扩展CDynamicParamAccessor。
笔者已知多数情况下,CDynamicParamAccessor 均应使用其成员函数 SetParamString 设置参数,而不是使用其成员函数 SetParam,原因是驱动并不能根据 SQL 中参数名称(通常是 “?”)判断数据类型,就统一按字符串绑定了。

// 准备存取器
CCommand<CDynamicParamAccessor> rs;

HRESULT hResult = S_OK;

if (FAILED(hResult = rs.Create(m_Session, L"SELECT Col0,Col1 FROM TB WHERE Col2=?")) ||
	FAILED(hResult = rs.Prepare()) ||
	FAILED(hResult = rs.BindParameters(&rs.m_hParameterAccessor, rs.m_spCommand)))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

// 绑定参数
if (!rs.SetParamString(1, L”A”))
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

// 存取器属性,不需要修改或删除可去除
CDBPropSet dbset(DBPROPSET_ROWSET);								// 行属性
dbset.AddProperty(DBPROP_IRowsetChange, true);					// 支持 IRowsetChange 接口
dbset.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT);	// 结果集允许修改; 

// 打开存取器
if (FAILED(hResult = rs.Open(&dbset))) // 不需要修改或删除时可去除 &dbset
{
	::AfxMessageBox(GetDBErrInfo()); // TODO:修改为自已的错误处理代码
	return FALSE;
}

while (rs.MoveNext() == S_OK)
{
	int nCol0 = *(int*)rs.GetValue(1); 		// 强制类型转换 !!!

	// TODO:自己的业务处理代码 …
	// 此处执行 rs.Delete() 可删除当前记录
	// SetValue(…) 后执行 rs.SetData() 可修改当前记录
}

4.4 同时解决 CDynamicAccessor 存在三个问题
CDynamicStringAccessor 和 CDynamicParamAccessor 均不能同时解决前文所述的 CDynamicAccessor 存在的三个问题。
为此可以自行合并两个类型为一个类,此处命名为CDynamicStringParamAccessor。方法如下:
1、CDynamicStringParamAccessor继承自CDynamicStringAccessor
2、再添加 CDynamicParamAccessor 的全部源码(在Visual C++ 包含目录中搜索)
即可综合两个类,读取数据时用 GetString,设置参数用 SetParamString,即可以同时解决 CDynamicAccessor 存在的三个问题。

5 二进制 BLOB 数据的写

笔者未找到可以用 UPDATE 或 INSERT 语句直接写入 BLOB 数据库的方法,只能用 SELECT 语句定位到 BLOB 所在的记录,再 rs.SetData()。所以写入前需保证该条记录是存在的,在新建一条含 BLOB 数据的记录时,应预先用 INSERT 插入该条记录。
然后再应用以下代码,以下代码是工业强度的,按注释说明直接用即可。

/// <summary>通过 OLEDB 模板保存二进制数据到数据库的 BLOB 字段,代码量减少至 MSDN 上示例代码的几十分之一,SDK、WTL、MFC 通用</summary>
/// <param name="session">传入的 session 应已经正常连接数据库</param>
/// <param name="cmdBlob">外部声明的目的是在出现错误时,外部代码能通过其获取错误,否则也可改为内部声明</param>
/// <param name="sSelectSQL">查询二进制数据的 SQL 语句,需满足:返回一个 BLOB 查询字段且返回唯一记录。例如:SELECT Photo FROM TB_PHOTO WHERE ID=1</param>
/// <param name="pIStream">已写入了二进制(BLOB) 数据的 IStream 接口。</param>
/// <returns>标准 HRESULT</returns>
HRESULT SaveBLOB(CSession& session, CCommand<CManualAccessor>& cmdBlob, LPCTSTR sSelectSQL, IStream* pIStream)
{
	_ASSERT(session.m_spOpenRowset != NULL);	// 传入的 session 应已经正常连接数据库
	_ASSERT(sSelectSQL != NULL);				// 例如:SELECT Photo FROM TB_PHOTO WHERE ID=1
	_ASSERT(pIStream != NULL);					// 已写入了二进制(BLOB) 数据的 IStream 接口。

	HRESULT hResult = 0;

	// 行属性
	CDBPropSet propSet(DBPROPSET_ROWSET);							// 这是行属性
	propSet.AddProperty(DBPROP_IRowsetChange, true);				// 设置支持 IRowsetChange 接口,否则后面 CManualAccessor 的 m_spRowsetChange 接口为空
	propSet.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE);	// 设置结果集允许修改

	// 借用 CManualAccessor 特性简化代码,否则额外需要很长的代码以获取一些接口
	if (FAILED(hResult = cmdBlob.Open(session, sSelectSQL, &propSet)))
		return hResult;

	// 借用 OLEDB 模板函数填充绑定结构 DBBINDING
	DBOBJECT dbObject = { STGM_READ, __uuidof(ISequentialStream) };
	DBBINDING dbBinding = { 0 };
	CAccessorBase::Bind(&dbBinding, 1, DBTYPE_IUNKNOWN, sizeof(IUnknown*), 0, 0, DBPARAMIO_NOTPARAM, 0, 0, 0, &dbObject);

	// 获取 IAccessor 接口并根据 DBBINDING 自行创建 HACCESSOR (CManualAccessor 中未直接提供该 IAccessor 接口)
	CComPtr<IAccessor> pIAccessor = NULL;
	if (FAILED(hResult = cmdBlob.m_spRowsetChange->QueryInterface(IID_IAccessor, (void**)&pIAccessor)))
		return hResult;

	HACCESSOR hAccessor = DB_NULL_HACCESSOR;
	if (FAILED(hResult = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, &dbBinding, sizeof(ISequentialStream*), &hAccessor, NULL)))
		return hResult;

	// cmdBlob.CreateAccessor(1, &dbBinding, sizeof(ISequentialStream*));

	// 借用 OLEDB 模板函数,效果与 cmdBlob.GetNextResult() 相同
	if (S_OK != (hResult = cmdBlob.MoveNext()))
		return hResult;

	// 复位 IStream 至开始位置
	if (FAILED(hResult = pIStream->Seek({ 0 }, 0, NULL)))
		return hResult;

	// IStream 继承自 ISequentialStream, 因此可以直接从 IStream 获取 ISequentialStream 接口
	ISequentialStream* pISequentialStream = NULL;//(不能用 CComPtr,这里的 ISequentialStream 接口似乎会自动释放 ...)
	if (FAILED(hResult = pIStream->QueryInterface(IID_ISequentialStream, (void**)&pISequentialStream)))
		return hResult;

	// CManualAccessor 自带的 SetData 不能实现二进制写入,需直接调用 IRowsetChange 的 SetData 方法
	return cmdBlob.m_spRowsetChange->SetData(cmdBlob.m_hRow, hAccessor, &pISequentialStream);
}

6 有条件再自行解决数据类型的问题

基于前文所述内容,已经完全可以基于 OLEDB 模板完成各类各种规模的开发任务。
但是前文所述 CDynamicAccessor 的相关内容,各种数据均是按字符串进行转换的,如果原始数据不是字符串,还需要进行各种转换,这带来了一些麻烦,例如在不同类型数据库中,日期/时间的字符串表示格式是不同的,在进行转换时需要考虑数据库的类型。如果能直接按特定数据类型进行绑定,则省去这一步骤。

解决方法如下:

  1. 改写 CDynamicStringAccessor 中的 BindColumns函数,改写其中统一按字符串绑定列的部分代码,先判断数据类型再按特定数据类型绑定(参见第 7 节完整代码)。
  2. 改写 CDynamicParamAccessor 中的 BindParameters 函数,改写其中统一按字符串绑定参数的部分代码。首先由外部指定各个参数数据类型(无法直接判断参数数据类型),再按指定的数据类型绑定,该实现代码与业务关联较大,以下仅给出示例。
...

// if this is a string, recalculate column size in bytes
DBLENGTH colLength = spParamInfo[l].ulParamSize;
if (spParamInfo[l].wType == DBTYPE_STR)
	colLength += 1;
if (spParamInfo[l].wType == DBTYPE_WSTR)
	colLength = colLength*2 + 2;

// 以上是 CDynamicParameterAccessor 成员函数 BindParameters 的原有代码
// *************************************************************************

// 示例:修改第一列数据类型为双精度浮点型
spParamInfo[0].wType		= DBTYPE_R8;
spParamInfo[0].ulParamSize	= 8;

// *************************************************************************
// 以下是 CDynamicParameterAccessor 成员函数 BindParameters 的原有代码

// Calculate the column data offset
nDataOffset = AlignAndIncrementOffset( nOffset, colLength, GetAlignment( spParamInfo[l].wType ) );

...

以上改写完成的两个类可再合并成新的 CXXXXAccessor 使用(方法参见本文 4,4 节)

7 改写 CDynamicAccessor 按不同数据类型绑定

在 CDynamicAccessor 基础上改写,重点改写了其 BindColumns 成员函数。
统一按三种数据类型进行绑定:长整型、双精度浮点型、字符串。其中日期按浮点型处理。

7.1 头文件 AutoAccessor.h

#pragma once

class CAutoAccessor : public CDynamicAccessor
{
public:
	explicit CAutoAccessor(_In_ DBLENGTH nBlobSize = 8000) : CDynamicAccessor(DBBLOBHANDLING_DEFAULT, nBlobSize) { };

public:
	HRESULT BindColumns(_Inout_ IUnknown* pUnk) throw();
	WCHAR*	GetStrVal(_In_ DBORDINAL nColumn) const throw();
	WCHAR*	GetStrVal(_In_z_ LPCTSTR pColumnName) const throw();
	HRESULT SetStrVal(_In_ DBORDINAL nColumn, _In_z_ WCHAR* data) throw();
	HRESULT SetStrVal(_In_z_ LPCTSTR pColumnName, _In_z_ WCHAR* data) throw();
	double	GetDouble(DBORDINAL nColumn) const throw();
	double	GetDouble(LPCTSTR pColumnName) const throw();
	HRESULT SetDouble(_In_ DBORDINAL nColumn, _In_z_ double fVal) throw();
	HRESULT SetDouble(_In_z_ LPCTSTR pColumnName, _In_z_ double fVal) throw();
	int		GetIntVal(_In_ DBORDINAL nColumn)						{ return (int)GetDouble(nColumn); };
	int		GetIntVal(_In_z_ LPCTSTR pColumnName)					{ return (int)GetDouble(pColumnName); };
	HRESULT SetIntVal(_In_ DBORDINAL nColumn, _In_z_ int nVal)		{ return SetDouble(nColumn, nVal); };
	HRESULT SetIntVal(_In_z_ LPCTSTR pColumnName, _In_z_ int nVal)	{ return SetDouble(pColumnName, nVal); };

private:
	void _SetLength(_In_ DBORDINAL nColumn, _In_ DBLENGTH nLength) throw();
	HRESULT _SetString(_In_ DBORDINAL nColumn, _In_z_ WCHAR* data) throw();
};```

7.2 实现文件 AutoAccessor.cpp

#include "stdafx.h"
#include "AutoAccessor.h"

HRESULT CAutoAccessor::BindColumns(_Inout_ IUnknown* pUnk) throw()
{
	ATLENSURE_RETURN(pUnk != NULL);

	HRESULT hResult;

	// 应未执行过 AddBindEntry
	ASSERT(m_pColumnInfo == NULL);
	m_bOverride = false;

	// 获取 IColumnsInfo 接口,并获取列信息
	CComPtr<IColumnsInfo> spColumnsInfo;
	if (FAILED(hResult = pUnk->QueryInterface(&spColumnsInfo)) ||
		FAILED(hResult = spColumnsInfo->GetColumnInfo(&m_nColumns, &m_pColumnInfo, &m_pStringsBuffer)))
		return hResult;

	DBBINDING* pBinding = new DBBINDING[m_nColumns];
	CAutoVectorPtr<DBBINDING> spBinding(pBinding); // 函数退出时删除 pBinding

	DBBYTEOFFSET nOffset = 0;

	ATLASSUME(m_pfClientOwnedMemRef == NULL);
	m_pfClientOwnedMemRef = new bool[m_nColumns];

	for (ULONG i = 0; i < m_nColumns; i++)
	{
		m_pfClientOwnedMemRef[i] = false;

		// If it's a IPersist* object or the column size is large enough for us to treat it as
		// a BLOB then we will request references (in client owned memory) to a string
		if (m_pColumnInfo[i].ulColumnSize > m_nBlobSize || m_pColumnInfo[i].wType == DBTYPE_IUNKNOWN)
		{
			m_pColumnInfo[i].wType = DBTYPE_WSTR | DBTYPE_BYREF;
			m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR*);
			m_pfClientOwnedMemRef[i] = true;
		}
		else // We're treating everything as a string so add 1 for the NULL byte.
		{
			switch (m_pColumnInfo[i].wType)
			{
				// 整型
			case DBTYPE_I1:				m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_I2:				m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_I4:				m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_I8:				m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_UI1:			m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_UI2:			m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_UI4:			m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_UI8:			m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
			case DBTYPE_BOOL:			m_pColumnInfo[i].wType = DBTYPE_I4;		m_pColumnInfo[i].ulColumnSize = 4;	break;
				// 浮点值
			case DBTYPE_R4:				m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;	// 单精度浮点值
			case DBTYPE_R8:				m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;	// 双精度浮点值
			case DBTYPE_CY:				m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
			case DBTYPE_DECIMAL:		m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
			case DBTYPE_NUMERIC:		m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
			case DBTYPE_VARNUMERIC:		m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
				// 日期/时间
			case DBTYPE_DATE:			m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
			case DBTYPE_DBDATE:			m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
			case DBTYPE_DBTIME:			m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
			case DBTYPE_DBTIMESTAMP:	m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
			case DBTYPE_FILETIME:		m_pColumnInfo[i].wType = DBTYPE_R8;		m_pColumnInfo[i].ulColumnSize = 8;	break;
				// 字符串
			case DBTYPE_BSTR:			m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + m_pColumnInfo[i].ulColumnSize);		break;
			case DBTYPE_STR:			m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + m_pColumnInfo[i].ulColumnSize);		break;
			case DBTYPE_WSTR:			m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + m_pColumnInfo[i].ulColumnSize);		break;
			case DBTYPE_BYTES:			m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + m_pColumnInfo[i].ulColumnSize * 2);	break;
			case DBTYPE_VARIANT:		m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 20);									break;
			case DBTYPE_IDISPATCH:		m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 32);									break;
			case DBTYPE_GUID:			m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 38);									break;
			case DBTYPE_IUNKNOWN:		m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 32);									break;
			case DBTYPE_ARRAY:			m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 32);									break;
			case DBTYPE_VECTOR:			m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 32);									break;
			case DBTYPE_PROPVARIANT:	m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 32);									break;
			default: ATLASSERT(FALSE);	m_pColumnInfo[i].wType = DBTYPE_WSTR;	m_pColumnInfo[i].ulColumnSize = sizeof(WCHAR) * (1 + 32);									break;	// unhandled column type
			}
		}

		DBBYTEOFFSET nOffData = AlignAndIncrementOffset(nOffset, m_pColumnInfo[i].ulColumnSize, GetAlignment(m_pColumnInfo[i].wType));	// Calculate the column data offset
		DBBYTEOFFSET nOffLength = AlignAndIncrementOffset(nOffset, sizeof(DBLENGTH), __alignof(DBLENGTH));		// Calculate the column length offset
		DBBYTEOFFSET nOffStatus = AlignAndIncrementOffset(nOffset, sizeof(DBSTATUS), __alignof(DBSTATUS));		// Calculate the column status offset

		BindEx(pBinding + i,
			m_pColumnInfo[i].iOrdinal,
			m_pColumnInfo[i].wType,
			m_pColumnInfo[i].ulColumnSize,
			m_pColumnInfo[i].bPrecision,
			m_pColumnInfo[i].bScale,
			DBPARAMIO_NOTPARAM,
			nOffData,
			nOffLength,
			nOffStatus,
			NULL,
			DBMEMOWNER_CLIENTOWNED);

		// Note that, as we're not using this for anything else, we're using the pTypeInfo element to store the offset to our data.
		m_pColumnInfo[i].pTypeInfo = (ITypeInfo*)(ULONG_PTR)nOffData;
	}

	// Allocate the accessor memory if we haven't done so yet
	if (m_pAccessorInfo == NULL)
	{
		if (FAILED(hResult = AllocateAccessorMemory(1)))	// We only have one accessor
		{
			delete[] m_pfClientOwnedMemRef;
			m_pfClientOwnedMemRef = NULL;
			return hResult;
		}

		m_pAccessorInfo->bAutoAccessor = true;
	}

	// 获取 IAccessor 接口
	CComPtr<IAccessor> spAccessor;
	if (FAILED(hResult = pUnk->QueryInterface(&spAccessor)))
		return hResult;

	// Allocate enough memory for the data buffer and tell the rowset,the rowset will free the memory in its destructor.
	m_pBuffer = new BYTE[nOffset];
	::memset(m_pBuffer, 0, nOffset);

	if (FAILED(hResult = BindEntries(pBinding, m_nColumns, &m_pAccessorInfo->hAccessor, nOffset, spAccessor)))
	{
		delete[] m_pfClientOwnedMemRef;
		m_pfClientOwnedMemRef = NULL;
	}

	return hResult;
}


WCHAR* CAutoAccessor::GetStrVal(_In_ DBORDINAL nColumn) const throw()
{
	if (!TranslateColumnNo(nColumn))
		return NULL;

	ASSERT((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) == DBTYPE_WSTR);
	if ((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) != DBTYPE_WSTR)
		return NULL;

	if (m_pColumnInfo[nColumn].wType & DBTYPE_BYREF)
		return *(WCHAR**)_GetDataPtr(nColumn);

	return (WCHAR*)_GetDataPtr(nColumn);
}

WCHAR* CAutoAccessor::GetStrVal(_In_z_ LPCTSTR pColumnName) const throw()
{
	DBORDINAL nColumn = 0;
	if (!GetInternalColumnNo(pColumnName, &nColumn))
		return NULL;    // Not Found

	ASSERT((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) == DBTYPE_WSTR);
	if ((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) != DBTYPE_WSTR)
		return NULL;

	if (m_pColumnInfo[nColumn].wType & DBTYPE_BYREF)
		return *(WCHAR**)_GetDataPtr(nColumn);

	return (WCHAR*)_GetDataPtr(nColumn);
}

HRESULT CAutoAccessor::SetStrVal(_In_ DBORDINAL nColumn, _In_z_ WCHAR* data) throw()
{
	if (!TranslateColumnNo(nColumn))
		return DB_S_ERRORSOCCURRED;

	ASSERT((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) == DBTYPE_WSTR);
	if ((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) != DBTYPE_WSTR)
		return DB_S_COLUMNTYPEMISMATCH;

	return _SetString(nColumn, data);
}

HRESULT CAutoAccessor::SetStrVal(_In_z_ LPCTSTR pColumnName, _In_z_ WCHAR* data) throw()
{
	DBORDINAL nColumn;
	if (!GetInternalColumnNo(pColumnName, &nColumn))
		return DB_S_ERRORSOCCURRED;

	ASSERT((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) == DBTYPE_WSTR);
	if ((m_pColumnInfo[nColumn].wType & DBTYPE_WSTR) != DBTYPE_WSTR)
		return DB_S_COLUMNTYPEMISMATCH;

	return _SetString(nColumn, data);
}

double CAutoAccessor::GetDouble(DBORDINAL nColumn) const throw()
{
	if (!TranslateColumnNo(nColumn))
		return 0;    // Not Found

	ASSERT(m_pColumnInfo[nColumn].wType == DBTYPE_R8 || m_pColumnInfo[nColumn].wType == DBTYPE_I4);

	if (m_pColumnInfo[nColumn].wType == DBTYPE_R8)
		return *(double*)_GetDataPtr(nColumn);

	if (m_pColumnInfo[nColumn].wType == DBTYPE_I4)
		return *(int*)_GetDataPtr(nColumn);

	return 0;
}

double CAutoAccessor::GetDouble(LPCTSTR pColumnName) const throw()
{
	DBORDINAL nColumn = 0;
	if (!GetInternalColumnNo(pColumnName, &nColumn))
		return 0;    // Not Found

	ASSERT(m_pColumnInfo[nColumn].wType == DBTYPE_R8 || m_pColumnInfo[nColumn].wType == DBTYPE_I4);

	if (m_pColumnInfo[nColumn].wType == DBTYPE_R8)
		return *(double*)_GetDataPtr(nColumn);

	if (m_pColumnInfo[nColumn].wType == DBTYPE_I4)
		return *(int*)_GetDataPtr(nColumn);

	return 0;
}

HRESULT CAutoAccessor::SetDouble(_In_ DBORDINAL nColumn, _In_z_ double fVal) throw()
{
	if (!TranslateColumnNo(nColumn))
		return DB_S_ERRORSOCCURRED;

	ASSERT(m_pColumnInfo[nColumn].wType == DBTYPE_R8 || m_pColumnInfo[nColumn].wType == DBTYPE_I4);
	if (m_pColumnInfo[nColumn].wType != DBTYPE_R8 && m_pColumnInfo[nColumn].wType != DBTYPE_I4)
		return DB_S_COLUMNTYPEMISMATCH;

	if (m_pColumnInfo[nColumn].wType == DBTYPE_R8)
		*(double*)_GetDataPtr(nColumn) = fVal;

	if (m_pColumnInfo[nColumn].wType == DBTYPE_I4)
		*(int*)_GetDataPtr(nColumn) = (int)fVal;

	return S_OK;
}

HRESULT CAutoAccessor::SetDouble(_In_z_ LPCTSTR pColumnName, _In_z_ double fVal) throw()
{
	DBORDINAL nColumn = 0;
	if (!GetInternalColumnNo(pColumnName, &nColumn))
		return DB_S_ERRORSOCCURRED;

	ASSERT(m_pColumnInfo[nColumn].wType == DBTYPE_R8 || m_pColumnInfo[nColumn].wType == DBTYPE_I4);
	if (m_pColumnInfo[nColumn].wType != DBTYPE_R8 && m_pColumnInfo[nColumn].wType != DBTYPE_I4)
		return DB_S_COLUMNTYPEMISMATCH;

	if (m_pColumnInfo[nColumn].wType == DBTYPE_R8)
		*(double*)_GetDataPtr(nColumn) = fVal;

	if (m_pColumnInfo[nColumn].wType == DBTYPE_I4)
		*(int*)_GetDataPtr(nColumn) = (int)fVal;

	return S_OK;
}

void CAutoAccessor::_SetLength(_In_ DBORDINAL nColumn, _In_ DBLENGTH nLength) throw()
{
	DBBYTEOFFSET nOffset = (DBBYTEOFFSET)(ULONG_PTR)m_pColumnInfo[nColumn].pTypeInfo;
	IncrementAndAlignOffset(nOffset, m_pColumnInfo[nColumn].ulColumnSize, __alignof(DBLENGTH));
	*(DBLENGTH*)(m_pBuffer + nOffset) = nLength;
}

HRESULT CAutoAccessor::_SetString(_In_ DBORDINAL nColumn, _In_z_ WCHAR* data) throw()
{
	DBLENGTH stringLen = (DBLENGTH)strlenT<WCHAR>(data);

	if (m_pColumnInfo[nColumn].wType & DBTYPE_BYREF)
	{
		WCHAR** pBuffer = (WCHAR**)_GetDataPtr(nColumn);

		//in case of overflow throw exception
		if (stringLen + 1 > (size_t(-1) / sizeof(WCHAR)))
			return E_FAIL;	// arithmetic overflow

		WCHAR* pNewBuffer = (WCHAR*)AtlCoTaskMemRecalloc(*pBuffer, (stringLen + 1), sizeof(WCHAR));
		if (pNewBuffer == NULL)
			return E_OUTOFMEMORY;

		*pBuffer = pNewBuffer;
		strcpyT<WCHAR>(pNewBuffer, stringLen + 1, data);
		_SetLength(nColumn, stringLen * sizeof(WCHAR));
	}
	else
	{
		WCHAR* pBuffer = (WCHAR*)_GetDataPtr(nColumn);

		if (stringLen >= m_pColumnInfo[nColumn].ulColumnSize / sizeof(WCHAR))
		{
			strcpyT<WCHAR>(pBuffer, m_pColumnInfo[nColumn].ulColumnSize / sizeof(WCHAR) - 1, data);
			pBuffer[m_pColumnInfo[nColumn].ulColumnSize / sizeof(WCHAR) - 1] = 0;
			_SetLength(nColumn, m_pColumnInfo[nColumn].ulColumnSize - sizeof(WCHAR));
			return DBSTATUS_S_TRUNCATED;
		}
		else
		{
			strcpyT<WCHAR>(pBuffer, m_pColumnInfo[nColumn].ulColumnSize / sizeof(WCHAR), data);
			_SetLength(nColumn, stringLen * sizeof(WCHAR));
		}
	}

	return S_OK;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值