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 存在三个问题:
- 在某些情况下,无法准确判断返回结果集中的部分数据类型,可能导致强制类型转换出错,例如:SELECT SUM(…) FROM … GROUP BY …,第一列有时返回是整型,有时返回是浮点型,类似情况还较多。
- 无法方便的读取返回结果集中的长文本数据或 BLOB(需要进行地址转换,代码略)。
- 如果有参数则需要手工绑定参数,代码冗长。
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 的相关内容,各种数据均是按字符串进行转换的,如果原始数据不是字符串,还需要进行各种转换,这带来了一些麻烦,例如在不同类型数据库中,日期/时间的字符串表示格式是不同的,在进行转换时需要考虑数据库的类型。如果能直接按特定数据类型进行绑定,则省去这一步骤。
解决方法如下:
- 改写 CDynamicStringAccessor 中的 BindColumns函数,改写其中统一按字符串绑定列的部分代码,先判断数据类型再按特定数据类型绑定(参见第 7 节完整代码)。
- 改写 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;
}