首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
105 阅读
2
nvim番外之将配置的插件管理器更新为lazy
78 阅读
3
2018总结与2019规划
62 阅读
4
PDF标准详解(五)——图形状态
40 阅读
5
为 MariaDB 配置远程访问权限
33 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
linux
文本编辑器
Java
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
314
篇文章
累计收到
31
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
页面
归档
友情链接
关于
搜索到
84
篇与
的结果
2018-04-08
OLEDB 枚举数据源
在之前的程序中,可以看到有这样一个功能,弹出一个对话框让用户选择需要连接的数据源,并输入用户名和密码,最后连接;而且在一些数据库管理软件中也提供这种功能——能够自己枚举出系统中存在的数据源,同时还可以枚举出能够连接的SQL Server数据库的实例。其实这个功能是OLEDB提供的高级功能之一。枚举对象用于搜寻可用的数据源和其它的枚举对象(层次式),枚举出来的对象是一个树形结构。在程序中提供一个枚举对象就可以枚举里面的所有数据源,如果没有指定所使用的的上层枚举对象,则可以使用顶层枚举对象来枚举可用的OLEDB提供程序,其实我们使用枚举对象枚举数据源时它也是在注册表的对应位置进行搜索,所以我们可以直接利用操作注册表的方式来获取数据源对象,但是注册表中的信息过于复杂,而且系统对注册表的依赖比较严重,所以并不推荐使用这种方式。枚举对象的原型如下:CoType TEnumerator { [mandatory] IParseDisplayName; [mandatory] ISourcesRowset; [optional] IDBInitialize; [optional] IDBProperties; [optional] ISupportErrorInfo; }顶层枚举对象的获取和遍历要利用数据源枚举功能,第一个要获取的枚举对象就是顶层枚举对象。或者称之为根枚举器,根枚举器对象的CLSID是CLSID_OLEDB_ENUMNRATOR,顶层枚举对象可以使用标准的COM对象创建方式来创建,之后可以使用ISourceRowset对象的GetSourcesRowset,得到数据源组合成的结果集。接着可以根据行集中的行类型来判断是否是一个子枚举对象或者数据源对象。如果是子枚举对象,可以利用名字对象的方法创建一个新的子枚举对象,然后根据这个枚举对象来枚举其中的数据源对象。一般来说这颗数结构只有两层。OLEDB提供者结果集在上面我们说可以根据结果集中的行类型来判断是否是一个子枚举对象或者数据源对象,那么怎么获取这个行类型呢?这里需要了解返回的行集的结构。字段名称类型最大长度含义描述SOURCES_NAMEDBTYPE_WSTR128枚举对象或数据源名称SOURCES_PARSENAMEDBTYPE_WSTR128可以传递给IParseDisplayName接口并得到一个moniker对象的字符串(枚举对象或数据源的moniker)SOURCES_DESCRIPTIONDBTYPE_WSTR128枚举对象或数据源的描述SOURCES_TYPEDBTYPE_UI22(单位字节)枚举对象或实例的类型,有下列值:DBSOURCETYPE_BINDER (=4)- URLDBSOURCETYPE_DATASOURCE_MDP (=3) - OLAP提供者DBSOURCETYPE_DATASOURCE_TDP (=1) - 关系型或表格型数据源DBSOURCETYPE_ENUMERATOR (=2) - 子枚举对象SOURCES_ISPARENTDBTYPE_BOOL2(单位字节)是否是父枚举器在枚举时根据SOURCES_TYPE字段来判断是否是子枚举对象,如果是则使用第二列的数据获取子枚举器的对象。如果根据名称创建子枚举器这里需要使用IMoniker接口。名字对象(moniker)的创建方法,是一种标准的以名字解析方法创建一个COM对象及接口的方法。相比于直接使用CoCreateInstance来说是一种更加高级的方法。这是标准的COM 对象的创建方式,其原理就是通过一个全局唯一的名称在注册表中搜索得到对应的CLSID,然后根据ID调用CoCreateInstance来创建对象。具体搜索过程可以参考COM基础系列在数据源枚举的应用中,先从ISourcesRowset对象中Query出IParseDisplayName接口,再调用该接口的ParseDisplayName方法传入上述表格中SOURCES_PARSENAME的值,得到IMoniker接口,最后调用全局函数BindMinker传递IMoniker接口指针并指定需要创建的接口ID。具体例子最后是一个具体的例子这个例子中创建了一个MFC应用程序,最后效果类似于前面几个例子中的OLEDB的数据源选择对话框。在例子中最主要的代码有两段:IDBSourceDlg对话框的EnumDataSource方法,和IDBConnectDlg方法Initialize。这两个分别用来枚举系统中存在的数据源对象和数据源对象中对应的数据库实例。当用户根据界面的提示选择了对应的选项后点击测试连接按钮来尝试连接。这里展示的代码主要是3段,枚举数据源,枚举数据源中对应的数据库实例,以及根据选择的实例生成对应的数据源对象接口并测试连接。void IDBSourceDlg::EnumDataSource(ISourcesRowset *pISourceRowset) { COM_DECLARE_INTERFACE(IRowset); COM_DECLARE_INTERFACE(IAccessor); COM_DECLARE_INTERFACE(IMoniker); COM_DECLARE_INTERFACE(IParseDisplayName); HROW *rgRows = NULL; HACCESSOR hAccessor = NULL; ULONG cRows = 10; DWORD dwOffset = 0; PVOID pData = NULL; PVOID pCurrentData = NULL; DBCOUNTITEM cRowsObtained = 0; LPOLESTR lpParamName = OLESTR(""); //利用顶层枚举对象来枚举系统中存在的数据源 HRESULT hRes = pISourceRowset->GetSourcesRowset(NULL, IID_IRowset, 0, NULL, (IUnknown**)&pIRowset); ISourcesRowset *pSubSourceRowset = NULL; ULONG ulEaten = 0; if (FAILED(hRes)) { ComMessageBox(NULL, _T("OLEDB 错误"), MB_OK, __T("创建接口ISourcesRowset失败,错误码:%08x\n"), hRes); goto __CLEAR_UP; } DBBINDING rgBinding[3] = {0}; //这里只关心我们需要的列,不需要获取所有的列 for (int i = 0; i < 3; i++) { rgBinding[i].bPrecision = 0; rgBinding[i].bScale = 0; rgBinding[i].cbMaxLen = 128 * sizeof(WCHAR); rgBinding[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED; rgBinding[i].dwPart = DBPART_VALUE; rgBinding[i].eParamIO = DBPARAMIO_NOTPARAM; rgBinding[i].wType = DBTYPE_WSTR; rgBinding[i].obLength = 0; rgBinding[i].obStatus = 0; rgBinding[i].obValue = dwOffset; dwOffset += rgBinding[0].cbMaxLen; } rgBinding[0].iOrdinal = 3; //第三项是数据源或者枚举器的描述信息,用于显示 rgBinding[1].wType = DBTYPE_UI2; rgBinding[1].iOrdinal = 4; //第四列是枚举出来的类型信息,用于判断是否需要递归 rgBinding[2].iOrdinal = 1; //第一列是枚举出来的类型信息,用于获取子枚举器 hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor); if (FAILED(hRes)) { ComMessageBox(NULL, _T("OLEDB 错误"), MB_OK, __T("查询接口pIAccessor失败,错误码:%08x\n"), hRes); goto __CLEAR_UP; } hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 3, rgBinding, 0, &hAccessor, NULL); if (FAILED(hRes)) { ComMessageBox(NULL, _T("OLEDB 错误"), MB_OK, __T("创建访问器失败,错误码:%08x\n"), hRes); goto __CLEAR_UP; } pData = MALLOC(dwOffset * cRows); while (TRUE) { hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &cRowsObtained, &rgRows); if(S_OK != hRes && cRowsObtained == 0) { break; } ZeroMemory(pData, dwOffset * cRows); for (int i = 0; i < cRowsObtained; i++) { pCurrentData = (BYTE*)pData + dwOffset * i; pIRowset->GetData(rgRows[i], hAccessor, pCurrentData); DATASOURCE_ENUM_INFO dbei = {0}; //将枚举到的相关信息存储到对应的结构中 dbei.csSourceName = (LPCTSTR)((BYTE*)pCurrentData + rgBinding[2].obValue); dbei.csDisplayName = (LPCTSTR)((BYTE*)pCurrentData + rgBinding[0].obValue); dbei.dbTypeEnum = *(DBTYPEENUM*)((BYTE*)pCurrentData + rgBinding[1].obValue); m_DataSourceList.AddString(dbei.csDisplayName); //显示数据源信息 g_DataSources.push_back(dbei); } pIRowset->ReleaseRows(cRowsObtained, rgRows, NULL, NULL, NULL); } pIAccessor->ReleaseAccessor(hAccessor, NULL); __CLEAR_UP: FREE(pData); CoTaskMemFree(rgRows); COM_SAFE_RELEASE(pIRowset); COM_SAFE_RELEASE(pIAccessor); }void IDBConnectDlg::Initialize(const CStringW& csSelected) { BSTR lpOleName = NULL; ULONG uEaten = 0; for (vector<DATASOURCE_ENUM_INFO>::iterator it = g_DataSources.begin(); it != g_DataSources.end(); it++) { if (it->csDisplayName == csSelected) { lpOleName = it->csSourceName.AllocSysString(); } } COM_DECLARE_INTERFACE(ISourcesRowset); COM_DECLARE_INTERFACE(IParseDisplayName); COM_DECLARE_INTERFACE(IMoniker); CoCreateInstance(CLSID_OLEDB_ENUMERATOR, NULL, CLSCTX_INPROC_SERVER, IID_ISourcesRowset, (void**)&pISourcesRowset); pISourcesRowset->QueryInterface(IID_IParseDisplayName, (void**)&pIParseDisplayName); pIParseDisplayName->ParseDisplayName(NULL, lpOleName, &uEaten, &pIMoniker); if (lpOleName != NULL) { SysFreeString(lpOleName); } HRESULT hRes = BindMoniker(pIMoniker, 0, IID_ISourcesRowset, (void**)&m_pConnSourceRowset); COM_SAFE_RELEASE(pIMoniker); COM_SAFE_RELEASE(pIParseDisplayName); if (FAILED(hRes)) { COM_SAFE_RELEASE(m_pConnSourceRowset); return; } COM_DECLARE_INTERFACE(IRowset) hRes = m_pConnSourceRowset->GetSourcesRowset(NULL, IID_IRowset, 0, NULL, (IUnknown**)&pIRowset); if (FAILED(hRes)) { return; } DBBINDING rgBind[1] = {0}; rgBind[0].bPrecision = 0; rgBind[0].bScale = 0; rgBind[0].cbMaxLen = 128 * sizeof(WCHAR); rgBind[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED; rgBind[0].dwPart = DBPART_VALUE; rgBind[0].eParamIO = DBPARAMIO_NOTPARAM; rgBind[0].iOrdinal = 2; //绑定第二项,用于展示数据源 rgBind[0].obLength = 0; rgBind[0].obStatus = 0; rgBind[0].obValue = 0; rgBind[0].wType = DBTYPE_WSTR; HACCESSOR hAccessor = NULL; HROW *rghRows = NULL; PVOID pData = NULL; PVOID pCurrData = NULL; ULONG cRows = 10; COM_DECLARE_INTERFACE(IAccessor); hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor); if (FAILED(hRes)) { COM_SAFE_RELEASE(pIRowset); return; } hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, rgBind, 0, &hAccessor, NULL); DBCOUNTITEM cRowsObtained; if (FAILED(hRes)) { COM_SAFE_RELEASE(pIRowset); COM_SAFE_RELEASE(pIAccessor); return; } pData = MALLOC(rgBind[0].cbMaxLen * cRows); while (TRUE) { hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &cRowsObtained, &rghRows); if (S_OK != hRes && cRowsObtained == 0) { break; } for (int i = 0; i < cRowsObtained; i++) { pCurrData = (BYTE*)pData + rgBind[0].cbMaxLen * i; pIRowset->GetData(rghRows[i], hAccessor, pCurrData); m_ComboDataSource.AddString((LPOLESTR)pCurrData); } pIRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL); } FREE(pData); pIAccessor->ReleaseAccessor(hAccessor, NULL); }void IDBConnectDlg::OnBnClickedBtnConnectTest() { // TODO: 在此添加控件通知处理程序代码 CStringW csSelected = _T(""); ULONG chEaten = 0; m_ComboDataSource.GetWindowText(csSelected); COM_DECLARE_INTERFACE(IParseDisplayName); COM_DECLARE_INTERFACE(IMoniker); if (m_pConnSourceRowset == NULL) { MessageBox(_T("连接失败")); return; } HRESULT hRes = m_pConnSourceRowset->QueryInterface(IID_IParseDisplayName, (void**)&pIParseDisplayName); if (FAILED(hRes)) { return; } hRes = pIParseDisplayName->ParseDisplayName(NULL, csSelected.AllocSysString(), &chEaten, &pIMoniker); COM_SAFE_RELEASE(pIParseDisplayName); if (FAILED(hRes)) { MessageBox(_T("连接失败")); return; } COM_DECLARE_INTERFACE(IDBProperties); hRes = BindMoniker(pIMoniker, 0, IID_IDBProperties, (void**)&pIDBProperties); COM_SAFE_RELEASE(pIMoniker); if (FAILED(hRes)) { MessageBox(_T("连接失败")); return; } //获取用户输入 CStringW csDB = _T(""); CStringW csUser = _T(""); CStringW csPasswd = _T(""); GetDlgItemText(IDC_EDIT_USERNAME, csUser); GetDlgItemText(IDC_EDIT_PASSWORD, csPasswd); GetDlgItemText(IDC_EDIT_DATABASE, csDB); //设置链接属性 DBPROP connProp[5] = {0}; DBPROPSET connPropset[1] = {0}; connProp[0].colid = DB_NULLID; connProp[0].dwOptions = DBPROPOPTIONS_REQUIRED; connProp[0].dwPropertyID = DBPROP_INIT_DATASOURCE; connProp[0].vValue.vt = VT_BSTR; connProp[0].vValue.bstrVal = csSelected.AllocSysString(); connProp[1].colid = DB_NULLID; connProp[1].dwOptions = DBPROPOPTIONS_REQUIRED; connProp[1].dwPropertyID = DBPROP_INIT_CATALOG; connProp[1].vValue.vt = VT_BSTR; connProp[1].vValue.bstrVal = csDB.AllocSysString(); connProp[2].colid = DB_NULLID; connProp[2].dwOptions = DBPROPOPTIONS_REQUIRED; connProp[2].dwPropertyID = DBPROP_AUTH_USERID; connProp[2].vValue.vt = VT_BSTR; connProp[2].vValue.bstrVal = csUser.AllocSysString(); connProp[3].colid = DB_NULLID; connProp[3].dwOptions = DBPROPOPTIONS_REQUIRED; connProp[3].dwPropertyID = DBPROP_AUTH_PASSWORD; connProp[3].vValue.vt = VT_BSTR; connProp[3].vValue.bstrVal = csPasswd.AllocSysString(); connPropset[0].cProperties = 4; connPropset[0].guidPropertySet = DBPROPSET_DBINIT; connPropset[0].rgProperties = connProp; hRes = pIDBProperties->SetProperties(1, connPropset); if (FAILED(hRes)) { COM_SAFE_RELEASE(pIDBProperties); return; } COM_DECLARE_INTERFACE(IDBInitialize); hRes = pIDBProperties ->QueryInterface(IID_IDBInitialize, (void**)&pIDBInitialize); COM_SAFE_RELEASE(pIDBProperties); if (FAILED(hRes)) { return; } hRes = pIDBInitialize->Initialize(); if (FAILED(hRes)) { MessageBox(_T("连接失败")); }else { MessageBox(_T("连接成功")); } pIDBInitialize->Uninitialize(); COM_SAFE_RELEASE(pIDBInitialize); }最后,这次由于是一个MFC的程序,涉及到的代码文件比较多,因此就不像之前那样以代码片段的方式方上来了,这次我将其以项目的方式放到GitHub上供大家参考。项目地址
2018年04月08日
6 阅读
0 评论
0 点赞
2018-03-31
OLEDB 调用存储过程
除了常规调用sql语句和进行简单的插入删除操作外,OLEDB还提供了调用存储过程的功能,存储过程就好像是用SQL语句写成的一个函数,可以有参数,有返回值。存储过程除了像普通函数那样返回一般的值以外,还可以返回结果集,对于返回的内容可以使用输出参数的方式获取,但是如果返回的是结果集,一般不推荐使用输出参数来获取,一般采用的是使用多结果集来接收。另外对于输入参数一般采用参数化查询的方式进行,因此它的使用与参数化查询类似,但是相比于参数化查询来说要复杂一些。存储过程的使用对于输出参数,在绑定DBBINDING 结构的时候,将结构的eParamIO指定为DBPARAMIO_OUTPUT,调用存储过程可以使用类似下面的格式{? = call myProc(?, ?)}这个样式中两个大括号是必须的,其中?代表的输入输出参数,call表示调用存储过程,也是必须的。一般来说,存储过程的参数位置只接受输入,不作为输出参数,而存储过程的返回值位置只作为输出,不作为输入。另外最需要注意的一点是:当存储过程返回结果集的时候,返回的结果集指针如果没有被释放的话,输出参数的缓冲是不会被刷新的,也就是接收不到输出参数。这是由于数据提供者在返回这些数据的时候是按照流的方式。而结果集的流在输出参数和返回值的流之前,所以在结果集未被释放之前,应用程序是接收不到输出参数的。针对他的这个特性,我们一般是先使用存储过程返回的结果集,然后释放结果集的相关指针,接着从输出参数的缓冲中取出数据,最后释放这些缓冲。存储过程使用的例子BOOL ExecUsp(IOpenRowset *pIOpenRowset) { IAccessor *pParamAccessor = NULL; LPOLESTR pSQL = OLESTR("{? = call Usp_InputOutput(?,?)}"); DB_UPARAMS cParams = 0; DBPARAMINFO* rgParamInfo = NULL; DBBINDING* rgParamBind = NULL; LPOLESTR pParamNames = NULL; DWORD dwOffset = 0; HACCESSOR hAccessor = NULL; TCHAR szInputBuffer[11] = _T(""); DBPARAMS dbParams = {0}; BOOL bRet = FALSE; HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand); hRes = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown**)&pICommandText); hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, pSQL); hRes = pICommandText->QueryInterface(IID_ICommandPrepare, (void**)&pICommandPrepare); hRes = pICommandPrepare->Prepare(0); hRes = pICommandText->QueryInterface(IID_ICommandWithParameters, (void**)&pICommandWithParameters); hRes = pICommandWithParameters->GetParameterInfo(&cParams, &rgParamInfo, &pParamNames); //参数绑定 rgParamBind = (DBBINDING*)MALLOC(sizeof(DBBINDING) * cParams); for (int i = 0; i < cParams; i++) { rgParamBind[i].bPrecision = 11; rgParamBind[i].bScale = rgParamInfo[i].bScale; rgParamBind[i].cbMaxLen = rgParamInfo[i].ulParamSize; //由于都是int型,所以这里就不需要指定多余的缓冲 rgParamBind[i].iOrdinal = rgParamInfo[i].iOrdinal; rgParamBind[i].dwPart = DBPART_VALUE; //当发现参数是输入参数的时候,在eParamIO中加一个输入参数的性质, if (rgParamInfo[i].dwFlags & DBPARAMFLAGS_ISINPUT) { rgParamBind[i].eParamIO |= DBPARAMIO_INPUT; } //同上 if (rgParamInfo[i].dwFlags & DBPARAMFLAGS_ISOUTPUT) { rgParamBind[i].eParamIO |= DBPARAMIO_OUTPUT; } rgParamBind[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED; rgParamBind[i].obLength = 0; rgParamBind[i].obStatus = 0; rgParamBind[i].obValue = dwOffset; dwOffset = rgParamBind[i].obValue + rgParamBind[i].cbMaxLen; rgParamBind[i].wType = rgParamInfo[i].wType; //其实他们都是int型 } //创建参数的访问器 hRes = pICommandText->QueryInterface(IID_IAccessor, (void**)&pParamAccessor); COM_SUCCESS(hRes, _T("查询接口pParamAccessor失败,错误码为:%08x\n"), hRes); hRes = pParamAccessor->CreateAccessor(DBACCESSOR_PARAMETERDATA, cParams, rgParamBind, dwOffset, &hAccessor, NULL); COM_SUCCESS(hRes, _T("创建访问器失败,错误码为:%08x\n"), hRes); //设置参数值 dbParams.pData = MALLOC(dwOffset); ZeroMemory(dbParams.pData , dwOffset); dbParams.cParamSets = cParams; dbParams.hAccessor = hAccessor; for (int i = 0; i < cParams; i++) { if (rgParamBind[i].eParamIO & DBPARAMIO_INPUT) { COM_PRINT(_T("请输入第%d个参数的值[%s]:"), i + 1, rgParamInfo[i].pwszName); while (S_OK != StringCchGets(szInputBuffer, 11)); *(int*)((BYTE*)dbParams.pData + rgParamBind[i].obValue) = _ttoi(szInputBuffer); ZeroMemory(szInputBuffer, 11 * sizeof(TCHAR)); } } hRes = pICommandText->Execute(NULL, IID_IMultipleResults, &dbParams, NULL, (IUnknown**)&pIMultipleResults); //读取结果集 ReadRowset(pIMultipleResults); COM_PRINT(_T("返回值为:%d\n"), *(int*)dbParams.pData) bRet = TRUE; __CLEAR_UP: CoTaskMemFree(rgParamInfo); FREE(rgParamBind); CoTaskMemFree(pParamNames); FREE(dbParams.pData); SAFE_RELEASE(pIDBCreateCommand); SAFE_RELEASE(pICommandText); SAFE_RELEASE(pICommandPrepare); SAFE_RELEASE(pICommandWithParameters); SAFE_RELEASE(pParamAccessor); SAFE_RELEASE(pIMultipleResults); return bRet; }上述代码中调用的存储过程如下:ALTER PROCEDURE [dbo].[Usp_InputOutput] @input int, @output int Output AS BEGIN select @input, @output set @output = 2 return 7 END该存储过程输入两个参数,并通过select返回有这两个参数组成的结果集。存储过程的输出参数为7.在上述代码中,先定义了一个调用存储过程的sql语句,接着在ICommandText对象中设置该存储过程,然后获取参数的相关信息,然后绑定参数,提供输出、输出参数的缓冲,然后执行存储过程获取结果集。接着读取结果集。最后读取返回值。上面我们说过如果不释放返回的结果集的指针的话,是接收不到返回值的,但是在这段代码中好像在读取返回值之前没有释放返回的IMultipleResults指针的操作,但是还是可以取到结果集的,这是为什么呢?其实之前说的是返回的结果集,但是并没有说是多行结果集,这里释放的主要是结果集。针对多行结果集,我们只要释放它里面包含的所有结果集对象就可以了。释放结果集的代码在函数ReadRowset中,这里并没有列举出来。最后:完整的代码:请点击这里查看
2018年03月31日
6 阅读
0 评论
0 点赞
2018-03-25
OLEDB 参数化查询
一般情况下,SQL查询是相对固定的,一条语句变化的可能只是条件值,比如之前要求查询二年级学生信息,而后面需要查询三年级的信息,这样的查询一般查询的列不变,后面的条件只有值在变化,针对这种查询可以使用参数化查询的方式来提高效率,也可以时SQL操作更加安全,从根本上杜绝SQL注入的问题。参数化查询的优势:提高效率:之前说过,数据库在执行SQL的过程中,每次都会经过SQL的解析,编译,调用对应的数据库组件,这样如果执行多次同样类型的SQL语句,解析,编译的过程明显是在浪费资源,而参数化查询就是使用编译好的过程(也就是提前告诉数据库要调用哪些数据库组件),这样就跳过了对SQL语句的解析,编译过程,提高了效率(这个过程我觉得有点类似于C/C++语言的编译执行与脚本语言的解释执行)。更加安全:从安全编程的角度来说,对于防范SQL注入方面,它比关键字过滤更有效,实现起来也更加方便。科普SQL注入和安全编程什么是SQL注入:所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。举个例子来说在用户登录时会输入用户名密码,这个时候在后台就可以执行这样的SQL语句select count(*) from user where username = 'haha' and password = '123456'只有输入对了用户名和密码才能登录,但是如果没有对用户输入进行校验,当用户输入一些SQL中的语句,而后台直接将用户输入进行拼接并执行,就会发生注入,比如此时用户输入 'haha' or 1 = 1 -- ,此时再后台执行的sql语句就变成了这样:select count(*) from user where username = 'haha' or 1 = 1 -- and password = ''这样用户就可以不用密码,直接使用用户名就登录了。而防范这类攻击,一般采用的是关键字过滤的方式,但是关键字过滤并不能杜绝这类工具,当一时疏忽忘记了过滤某个关键字仍然会产生这类问题。而且关键字过滤一般采用正则表达式,而正则表达式并不是一般人可以驾驭的。而防范SQL注入最简单也是最一劳永逸的方式就是参数化查询。为什么参数化查询能够从根本上解决SQL注入发生SQL注入一般的原因是程序将用户输入当做SQL语句的一部分进行执行,但是参数化查询它只是将用户输入当做参数,当做查询的条件,从数据库的层面上来说,它不对应于具体的数据库组件,它只是一组数据,而不会执行。这里可以简单的将传统的SQL拼接方式理解为C语言中的宏,宏也可以有参数,但是它不对参数进行校验,只是简单的进行替换,那么我可以使用一些指令作为参数传入,但是函数就不一样,函数的参数就是具体类型的变量或者常量。所以参数化查询从根本上解决的SQL注入的问题。参数化查询的使用前面说了这么多参数化查询的好处,那么到底怎么使用它呢?在Java等语言中内置了数据库操作,而对于C/C++来说,它并没有提供这方方面的标准。不同的平台有自己独特的一套机制,但是从总体来说,思想是共通的,只是语法上的不同,这里主要是说明OLEDB中的使用方式。使用“?”符将SQL语句中的条件值常量进行替换,组成一个新的SQL语句,比如上面登录的查询语句可以写成select count(*) from user where username = ? and password = ?调用ICommandText的SetCommandText设置sql语句。调用ICommandParpare的Prepare方法对含有"?"的语句进行预处理调用ICommandWithParameters方法的GetParameterInfo方法获取参数详细信息的DBPARAMINFO结构(类似于DBCOLUMNINFO)分配对应大小的DBBINDING缓冲用来保存每个参数的绑定信息调用IAccessor的CreateAccessor方法创建对应的访问器为参数分配缓冲,设置合适的参数后准备DBPARAMS结构调用ICommandText的Execute方法并将DBPARAMS结构的指针作为参数传入。操作返回的结果集对象typedef struct tagDBPROPIDSET { DBPROPID * rgPropertyIDs; ULONG cPropertyIDs; GUID guidPropertySet; } DBPROPIDSET;DBPARAMS结构的定义如下:typedef struct tagDBPARAMS { void *pData; DB_UPARAMS cParamSets; HACCESSOR hAccessor; } DBPARAMS;pData是保存参数信息的缓冲;cParamSets: 表示又多少个参数hAccessor: 之前获取到的绑定结构的访问器句柄下面是一个使用的例子:BOOL QueryData(LPOLESTR pQueryStr, IOpenRowset* pIOpenRowset, IRowset* &pIRowset) { IAccessor *pParamAccessor = NULL; //与参数化查询相关的访问器接口 LPOLESTR pSql = _T("Select * From aa26 Where Left(aac031,2) = ?"); //参数化查询语句 BOOL bRet = FALSE; DB_UPARAMS uParams = 0; DBPARAMINFO* rgParamInfo = NULL; LPOLESTR pParamBuffer = NULL; DWORD dwOffset = 0; DBBINDING *rgParamBinding = NULL; HACCESSOR hAccessor = NULL; DBPARAMS dbParams = {0}; DBBINDSTATUS *pdbBindStatus = NULL; //设置SQL hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, pSql); //预处理SQL命令 pICommandPrepare->Prepare(0); hRes = pICommandText->QueryInterface(IID_ICommandPrepare, (void**)&pICommandPrepare); //获取参数信息 hRes = pICommandText->QueryInterface(IID_ICommandWithParameters, (void**)&pICommandWithParameters); COM_SUCCESS(hRes, _T("查询接口ICommandWithParameters失败,错误码为:%08x\n"), hRes); hRes = pICommandWithParameters->GetParameterInfo(&uParams, &rgParamInfo, &pParamBuffer); COM_SUCCESS(hRes, _T("获取参数信息失败,错误码为:%08x\n"), hRes); rgParamBinding = (DBBINDING*)MALLOC(sizeof(DBBINDING) * uParams); ZeroMemory(rgParamBinding, sizeof(DBBINDING) * uParams); //绑定参数信息 for (int i = 0; i < uParams; i++) { rgParamBinding[i].bPrecision = rgParamInfo[i].bPrecision; rgParamBinding[i].bScale = rgParamInfo[i].bScale; rgParamBinding[i].cbMaxLen = 7 * sizeof(WCHAR); //行政区编号最大长度为6 rgParamBinding[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED; rgParamBinding[i].dwPart = DBPART_LENGTH | DBPART_VALUE; rgParamBinding[i].eParamIO = DBPARAMIO_INPUT; rgParamBinding[i].iOrdinal = rgParamInfo[i].iOrdinal; rgParamBinding[i].obLength = dwOffset; rgParamBinding[i].obStatus = 0; rgParamBinding[i].obValue = dwOffset + sizeof(ULONG); rgParamBinding[i].wType = DBTYPE_WSTR; dwOffset = dwOffset + sizeof(ULONG) + rgParamBinding[i].cbMaxLen; dwOffset = UPROUND(dwOffset); } //获取访问器 pdbBindStatus = (DBBINDSTATUS*)MALLOC(uParams * sizeof(DBBINDSTATUS)); ZeroMemory(pdbBindStatus, uParams * sizeof(DBBINDSTATUS)) pParamAccessor->CreateAccessor(DBACCESSOR_PARAMETERDATA, uParams, rgParamBinding, dwOffset, &hAccessor, pdbBindStatus); COM_SUCCESS(hRes, _T("获取参数访问器失败,错误码为:%08x\n"), hRes); //准备参数 dbParams.pData = MALLOC(dwOffset); ZeroMemory(dbParams.pData, dwOffset); dbParams.cParamSets = uParams; dbParams.hAccessor = hAccessor; for (int i = 0; i < uParams; i++) { *(ULONG*)((BYTE*)dbParams.pData + rgParamBinding[i].obLength) = _tcslen(pQueryStr) * sizeof(WCHAR); StringCchCopy((LPTSTR)((BYTE*)dbParams.pData + rgParamBinding[i].obValue), _tcslen(pQueryStr) + 1, pQueryStr); } //执行SQL hRes = pICommandText->Execute(NULL, IID_IRowset, &dbParams, NULL, (IUnknown**)&pIRowset); return bRet; }完整代码
2018年03月25日
6 阅读
0 评论
0 点赞
2018-02-12
数据更新接口与延迟更新
title: 数据更新接口与延迟更新tags: [OLEDB, 数据库编程, VC++, 数据库]date: 2018-02-12 14:29:35categories: windows 数据库编程keywords: OLEDB, 数据库编程, VC++, 数据库,数据库数据更新, 延迟提交在日常使用中,更新数据库数据经常使用delete 、update等SQL语句进行,但是OLEDB接口提供了额外的接口,来直接修改和更新数据库数据。OLEDB数据源更新接口为何不使用SQL语句进行数据更新常规情况下,使用SQL语句比较简单,利用OLEDB中执行SQL语句的方法似乎已经可以进行数据库的任何操作,普通的增删改查操作似乎已经够用了。确实,在某种情况下,这些内容已经够了,能够执行SQL语句并得到结果集已经够了,但是某些情况下并不合适使用SQL语句。SQL语句的执行一般经过这样几个步骤:数据库通过sql语句对SQL语句进行分析,生成一些可以被数据库识别的步骤,在这里我们叫它计划任务数据库根据计划任务中的相关操作,调用对应的核心组件来执行SQL语句中规定的操作。将操作得到的结果集返回到应用程序我们可以简单的将SQL语句理解为一种运行在数据库平台上的一个脚本语言,它与一般的脚本语言一样需要对每句话进行解释执行。根据解释的内容调用对应的功能模块来完成用户的请求。如果我们能够跳过SQL语句的解释,直接调用对应的核心组件,那么就能大幅度的提升程序的性能。OLEDB中的数据更新的相关接口就是完成这个操作的。至此我们可能有点明白为什么不用SQL语句而是用OLEDB的相关接口来实现对应的更新操作。主要是为了提高效率。数据修改的相关接口数据修改的相关接口主要是IRowsetChange,接口中提供的主要方法如下:DeleteRows: 删除某行InsertRows: 插入行SetData: 更新行通过这些接口就可以直接对数据库进行相关的增删改操作,而由于查询的操作条件复杂特别是涉及到多表查询的时候,所以OLEDB没有提供对应的查询接口。更新数据更新数据需要IRowsetChange接口,而打开该接口需要设置结果集的相关属性。一般需要设置下面两个属性:DBPROP_UPDATABILITY:该属性表示是否允许对数据进行更新,它是一个INT型的数值,该属性有3个标志的候选值:DBPROPVAL_UP_CHANGE,允许对数据进行更新,也就是打开SetData方法;DBPROPVAL_UP_DELETE:允许对数据进行删除,打开的是DeleteRows方法。DBPROPVAL_UP_INSERT,允许插入新行,该标志打开InsertRows方法。这3个值一般使用或操作设置对应的标识位。DBPROP_IRowsetUpdate:该属性是一个BOOL值,表示是否打开IRowsetChange接口。如果要使用IRowsetChange接口,需要将该属性设置为TRUE。它们属于属性集DBPROPSET_ROWSET。使用命令对象来设置设置完属性后,调用Execute执行SQL语句并获取到接口IRowsetChange。 PS:使用IRowsetChange接口有一个特殊的要求,必须使用IRowsetChange替代IRowset等接口,作为创建并打开结果集对象的第一个接口。也就是说Execute方法中的最后一个表示结果集对象的参数必须是IRowsetChange。数据更新模式一般来说,使用OLEDB的接口对数据库中的数据进行操作时,操作的结果是实时的反映到数据库中的。对于一般的应用程序来说。采用数据更新的接口虽然在一定程度上解决的效率的问题,但是使用实时更新的模式仍然有一些问题:修改立即反映到数据库中,不利于数据库中数据完整性维护和数据安全如果是网络中的数据库,会形成很多小的网络数据包传输,造成网络传输效率低下。因此OLEDB提供了另外一种更新模式——延迟更新延迟更新延迟更新本质上提供了一种将所有更新都在本地中缓存起来,最后再一口气将所有更新都一次性提交的机制,它与数据库中的事务不同,事务是将一组操作组织起来,当其中某一步操作失败,那么回滚事务中的所有数据,强调的是一个完整性维护,而延迟提交并不会自己校验某一步是否错误,它强调的是将某些更改一次性的提交。延迟提交与实时提交有下面几个优点:当多个客户端都在修改数据库中的数据时,有机会将某些客户端对数据的修改通知到其他客户端。可以合并对一行数据多列的修改并一次提交到数据源上网络数据库中可以将对不同表的不同操作合并成一个大的网络数据包,提高网络的使用效率。当更新不合适的时候有机会进行回滚打开延迟更新的接口要使用延迟更新必须申请打开OLEDB的IRowsetUpdate接口,这个申请主要通过设置结果集的DBPROP_IRowsetUpdate属性来实现,这个属性是一个bool类型的值。同时要打开延迟更新的接口必须先打开IRowsetChange接口,所以它们二者一般都是同时打开的。另外为了方便操作一般也会将结果集的DBPROP_CANHOLDROWS属性打开,该属性允许在上一次对某行数据的更改未提交的情况下插入新行。如果不设置该属性,那么在调用SetData方法进行更新后就必须调用IRowsetUpdate的Update接口进行提交,否则在提交之前数据库不允许进行Insert操作(但是允许进行SetData操作)在使用延迟更新的时候需要注意一个问题。当我们使用了DBPROP_CANHOLDROWS属性后,数据源为了维护方便,会额外返回一个第0行的数据。通常改行数据是一个INT型,由数据提供者进行维护,它一般是只读的,如果尝试对它进行修改可能会返回一个错误造成对数据的其他修改操作也失败。在这种情况下,可以考虑建立2个访问器,一个包含第0行,只用来做显示使用,而另外一个更新的访问器不绑定第0行。一般情况下可以通过检测返回结果集中的列信息中的标志字段来确定哪些列可以进行变更,哪些列是只读列等标志来创建多个不同用途的行访问器下面是延迟更新的例子:BOOL ExecSql(IOpenRowset *pIOpenRowset, IRowsetChange* &pIRowsetChange) { COM_DECLARE_BUFFER(); COM_DECALRE_INTERFACE(IDBCreateCommand); COM_DECALRE_INTERFACE(ICommandText); COM_DECALRE_INTERFACE(ICommandProperties); LPOLESTR lpSql = OLESTR("select * from aa26;"); BOOL bRet = FALSE; //TODO:query出ICommandProperties接口并设置对应属性 //设置属性,允许返回的结果集对数据进行增删改操作 dbProp[0].colid = DB_NULLID; dbProp[0].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[0].dwPropertyID = DBPROP_UPDATABILITY; dbProp[0].vValue.vt = VT_I4; dbProp[0].vValue.intVal = DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT; //设置属性,打开IRowsetUpdate接口实现延迟更新 dbProp[1].colid = DB_NULLID; dbProp[1].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[1].dwPropertyID = DBPROP_IRowsetUpdate; dbProp[1].vValue.vt = VT_BOOL; dbProp[1].vValue.boolVal = VARIANT_TRUE; //设置属性,允许结果集在不提交上次行的更改的情况下插入行 dbProp[2].colid = DB_NULLID; dbProp[2].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[2].dwPropertyID = DBPROP_CANHOLDROWS; dbProp[2].vValue.vt = VT_BOOL; dbProp[2].vValue.boolVal = VARIANT_TRUE; dbPropset[0].cProperties = 3; dbPropset[0].guidPropertySet = DBPROPSET_ROWSET; dbPropset[0].rgProperties = dbProp; hRes = pICommandProperties->SetProperties(1, dbPropset); COM_SUCCESS(hRes, _T("设置属性失败,错误码为:%08x\n"), hRes); hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, lpSql); COM_SUCCESS(hRes, _T("设置SQL失败,错误码为:%08x\n"), hRes); hRes = pICommandText->Execute(NULL, IID_IRowsetChange, NULL, NULL, (IUnknown**)&pIRowsetChange); COM_SUCCESS(hRes, _T("执行SQL语句失败,错误码为:%08x\n"), hRes); bRet = TRUE; __CLEAR_UP: SAFE_RELEASE(pIDBCreateCommand); SAFE_RELEASE(pICommandText); SAFE_RELEASE(pICommandProperties); return bRet; } int _tmain(int argc, TCHAR* argv[]) { CoInitialize(NULL); COM_DECLARE_BUFFER(); COM_DECALRE_INTERFACE(IOpenRowset); COM_DECALRE_INTERFACE(IRowsetChange); COM_DECALRE_INTERFACE(IColumnsInfo); COM_DECALRE_INTERFACE(IAccessor); COM_DECALRE_INTERFACE(IRowset); COM_DECALRE_INTERFACE(IRowsetUpdate); HRESULT hRes = S_FALSE; DBORDINAL cColumns = 0; DBCOLUMNINFO* ColumnsInfo = NULL; OLECHAR *pColumnsName = NULL; DBBINDING *pDBBindinfo = NULL; HACCESSOR hAccessor = NULL; HROW* phRow = NULL; HROW hNewRow = NULL; DBCOUNTITEM cRows = 10; //一次读取10行 DBCOUNTITEM dbObtained = 0; //真实读取的行数 LPVOID pInsertData = NULL; LPVOID pData = NULL; LPVOID pCurrData = NULL; DWORD dwOffset = 0; if(!CreateSession(pIOpenRowset)) { COM_PRINTF(_T("创建回话对象失败\n")); goto __CLEAR_UP; } if (!ExecSql(pIOpenRowset, pIRowsetChange)) { COM_PRINTF(_T("执行SQL语句失败\n")); goto __CLEAR_UP; } hRes = pIRowsetChange->QueryInterface(IID_IRowset, (void**)&pIRowset); COM_SUCCESS(hRes, _T("查询接口IRowsets失败,错误码为:%08x\n"), hRes); hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo); COM_SUCCESS(hRes, _T("查询接口IColumnsInfo失败,错误码为:%08x\n"), hRes); hRes = pIColumnsInfo->GetColumnInfo(&cColumns, &ColumnsInfo, &pColumnsName); COM_SUCCESS(hRes, _T("获取结果集列信息失败,错误码为:%08x\n"), hRes); pDBBindinfo = (DBBINDING*)MALLOC(sizeof(DBBINDING) * (cColumns - 1)); for(int i = 0; i < cColumns; i++) { //取消第0行的绑定,防止修改数据时出错 if (ColumnsInfo[i].iOrdinal == 0) { continue; } pDBBindinfo[i - 1].iOrdinal = ColumnsInfo[i].iOrdinal; pDBBindinfo[i - 1].bPrecision = ColumnsInfo[i].bPrecision; pDBBindinfo[i - 1].bScale = ColumnsInfo[i].bScale; pDBBindinfo[i - 1].cbMaxLen = ColumnsInfo[i].ulColumnSize * sizeof(WCHAR); if (ColumnsInfo[i].wType == DBTYPE_I4) { //整型数据转化为字符串时4个字节不够,需要根据表中的长度来分配对应的空间 //数据库中表示行政单位的数据长度为6个字节 pDBBindinfo[i - 1].cbMaxLen = 7 * sizeof(WCHAR); } pDBBindinfo[i - 1].dwMemOwner = DBMEMOWNER_CLIENTOWNED; pDBBindinfo[i - 1].dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE; pDBBindinfo[i - 1].eParamIO = DBPARAMIO_NOTPARAM; pDBBindinfo[i - 1].obStatus = dwOffset; pDBBindinfo[i - 1].obLength = dwOffset + sizeof(DBSTATUS); pDBBindinfo[i - 1].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG); pDBBindinfo[i - 1].wType = DBTYPE_WSTR; //为了方便,类型由数据库进行转化 dwOffset = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG) + pDBBindinfo[i - 1].cbMaxLen; dwOffset = COM_UPROUND(dwOffset, 8); //8字节对齐 } //读取数据 hRes = pIRowsetChange->QueryInterface(IID_IAccessor, (void**)&pIAccessor); COM_SUCCESS(hRes, _T("查询IAccessor接口失败,错误码为:%08x\n"), hRes); hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cColumns - 1, pDBBindinfo, 0, &hAccessor, NULL); COM_SUCCESS(hRes, _T("创建访问器失败,错误码为:%08x\n"), hRes); hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &dbObtained, &phRow); COM_SUCCESS(hRes, _T("获取行数据失败,错误码为:%08x\n"), hRes); pData = MALLOC(cRows * dwOffset); for (int i = 0; i < dbObtained; i++) { pCurrData = (BYTE*)pData + dwOffset * i; hRes = pIRowset->GetData(phRow[i], hAccessor, pCurrData); //获取当前行 COM_SUCCESS(hRes, _T("获取数据失败, 错误码为:%08x\n"), hRes); DisplayColumnData(pDBBindinfo, cColumns - 1, pCurrData); // 将前10行第3列的数据修改为中国 LPOLESTR pUpdateData = (LPOLESTR)((BYTE*)pCurrData + pDBBindinfo[1].obValue); ULONG* pLen = (ULONG*)((BYTE*)pCurrData + pDBBindinfo[1].obLength); *pLen = 4; StringCchCopy(pUpdateData, pDBBindinfo[1].cbMaxLen, _T("中国")); hRes = pIRowsetChange->SetData(phRow[i], hAccessor, pCurrData); COM_SUCCESS(hRes, _T("修改数据失败, 错误码为:%08x\n"), hRes); } //插入一行数据 pInsertData = MALLOC(dwOffset); ZeroMemory(pInsertData, dwOffset); //为了方便直接在原来数据的基础上进行修改 CopyMemory(pInsertData, pData, dwOffset); DBSTATUS status[4] = {0}; ULONG uSize[4] = {0}; LPOLESTR lpValues[4] = {0}; for (int i = 0; i < 4; i++) { status[i] = *(DBSTATUS*)((BYTE*)pInsertData + pDBBindinfo[i].obStatus); uSize[i] = *(DBSTATUS*)((BYTE*)pInsertData + pDBBindinfo[i].obLength); lpValues[i] = (LPOLESTR)((BYTE*)pInsertData + pDBBindinfo[i].obValue); } StringCchCopy(lpValues[0], pDBBindinfo[0].cbMaxLen, _T("100001")); StringCchCopy(lpValues[1], pDBBindinfo[1].cbMaxLen, _T("测试")); hRes = pIRowsetChange->InsertRow(NULL, hAccessor, pInsertData, &hNewRow); COM_SUCCESS(hRes, _T("插入数据失败, 错误码为:%08x\n"), hRes); //删除一行数据 hRes = pIRowsetChange->DeleteRows(NULL, 1, &phRow[9], NULL); COM_SUCCESS(hRes, _T("查询IRowsetChange接口失败,错误码为:%08x\n"), hRes); hRes = pIRowsetChange->QueryInterface(IID_IRowsetUpdate, (void**)&pIRowsetUpdate); COM_SUCCESS(hRes, _T("查询IRowsetChange接口失败,错误码为:%08x\n"), hRes); ////提交上述修改 hRes = pIRowsetUpdate->Update(DB_NULL_HCHAPTER, 0, NULL, NULL, NULL, NULL); COM_SUCCESS(hRes, _T("提交数据更新失败, 错误码为:%08x\n"), hRes); //取消修改 //hRes = pIRowsetUpdate->Undo(DB_NULL_HCHAPTER, 0, NULL, NULL, NULL, NULL); COM_SUCCESS(hRes, _T("取消更新失败, 错误码为:%08x\n"), hRes); pIRowset->ReleaseRows(dbObtained, phRow, NULL, NULL, NULL); pIRowset->ReleaseRows(1, &hNewRow, NULL, NULL, NULL); __CLEAR_UP: SAFE_RELEASE(pIOpenRowset); SAFE_RELEASE(pIRowsetChange); SAFE_RELEASE(pIColumnsInfo); SAFE_RELEASE(pIRowsetUpdate); SAFE_RELEASE(pIRowset); SAFE_RELEASE(pIAccessor); CoTaskMemFree(ColumnsInfo); CoTaskMemFree(pColumnsName); FREE(pInsertData); FREE(pData); FREE(pDBBindinfo); CoUninitialize(); return 0; }在例子中仍然是首先进行数据库连接,创建出Session对象,创建Command对象,设置结果集对象的相关属性并执行sql语句。但是与之前不同的是,在执行SQL语句时不再返回IRowset接口而是返回IRowsetChange接口。然后利用IRowsetChange接口Query出其他需要的接口。接着仍然是绑定,与之前不同的是,在绑定中加了一个判断。跳过了第0行的绑定,以免它影响到后面的更新操作,然后打印输出对应的查询结果。并且在显示每行数据之后,调用SetData对数据进行更改。接着准备一个对应的缓冲,放入插入新行的数据。在这为了方便我们直接先拷贝了之前的返回的结果集中的一行的数据,然后再在里面进行修改,修改后调用InsertRows,插入一行数据。插入操作完成后,调用DeleteRows函数删除一行数据最后调用IRowsetUpdate接口的Update方法提交之前的更新,或者调用Undo取消之前的更改源代码查看
2018年02月12日
4 阅读
0 评论
0 点赞
2018-01-28
SQL语句执行与结果集的获取
上次说到命令对象是用来执行SQL语句的。数据源在执行完SQL语句后会返回一个结果集对象,将SQL执行的结果返回到结果集对象中,应用程序在执行完SQL语句后,解析结果集对象中的结果,得到具体的结果,这次的主要内容是如何解析结果集对象并获取其中的值。如何执行SQL语句执行SQL语句一般的步骤如下:创建ICommandText接口.使用ICommandText接口的SetCommandText方法设置SQL命令使用ICommandText接口的Excute方法执行SQL语句并接受返回的结果集对象,这个结果集对象一般是IRowset.其实OLEDB并不一定非要传入SQL语句,他可以传入简单的命令,只要数据源能够识别,也就是说我们可以根据数据源的不同传入那些只有特定数据源才会支持的命令,已达到简化操作或者实现某些特定功能的目的.针对有的SQL语句,我们并不是那么关心它返回了那些数据,比如说Delete语句,insert语句,针对这种情况我们可以将对应返回结果集的参数设置为NULL,比如像下面这样pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL)明确告诉数据源程序不需要返回结果集,这样数据源不会准备结果集,减少了数据源的相关操作,从某种程度上减轻了数据源的负担。设置command对象的属性与之前数据源对象和会话对象的属性不同,command对象的属性是作用在返回的数据源对象上的,比如我们没有设置对应的更新属性,那么数据源就不允许我们使用结果集进行更新数据的操作。这些属性必须在执行SQL语句得到结果集的操作之前定义好。因为在获得数据源返回的结果集的时候数据源已经设置了对应的属性。command对象的属性集ID是PROPSET_ROWSET.该属性集中有很多能够影响结果集对象的属性。下面是一个执行SQL语句的例子: LPOLESTR lpSql = OLESTR("select * from aa26"); CreateDBSession(pIOpenRowset); HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand); COM_SUCCESS(hRes, _T("查询接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown**)&pICommandText); COM_SUCCESS(hRes, _T("创建接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, lpSql); COM_SUCCESS(hRes, _T("设置sql语句失败,错误码:%08x\n"), hRes); DBPROP dbProp[16] = {0}; DBPROPSET dbPropset[1] = {0}; //设置结果集可以进行增删改操作 dbProp[0].colid = DB_NULLID; dbProp[0].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[0].dwPropertyID = DBPROP_UPDATABILITY; dbProp[0].vValue.vt = VT_I4; dbProp[0].vValue.lVal = DBPROPVAL_UP_DELETE | DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT; //申请打开书签功能 dbProp[1].colid = DB_NULLID; dbProp[1].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[1].dwPropertyID = DBPROP_BOOKMARKS; dbProp[1].vValue.vt = VT_BOOL; dbProp[1].vValue.boolVal = VARIANT_TRUE; //申请打开行查找功能 dbProp[2].colid = DB_NULLID; dbProp[2].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[2].dwPropertyID = DBPROP_IRowsetFind; dbProp[2].vValue.vt = VT_BOOL; dbProp[2].vValue.boolVal = VARIANT_TRUE; //申请打开行索引 dbProp[3].colid = DB_NULLID; dbProp[3].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[3].dwPropertyID = DBPROP_IRowsetIndex; dbProp[3].vValue.vt = VT_BOOL; dbProp[3].vValue.boolVal = VARIANT_TRUE; //申请打开行定位功能 dbProp[4].colid = DB_NULLID; dbProp[4].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[4].dwPropertyID = DBPROP_IRowsetLocate; dbProp[4].vValue.vt = VT_BOOL; dbProp[4].vValue.boolVal = VARIANT_TRUE; //申请打开行滚动功能 dbProp[5].colid = DB_NULLID; dbProp[5].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[5].dwPropertyID = DBPROP_IRowsetScroll; dbProp[5].vValue.vt = VT_BOOL; dbProp[5].vValue.boolVal = VARIANT_TRUE; //申请打开行集视图功能 dbProp[6].colid = DB_NULLID; dbProp[6].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[6].dwPropertyID = DBPROP_IRowsetView; dbProp[6].vValue.vt = VT_BOOL; dbProp[6].vValue.boolVal = VARIANT_TRUE; //申请打开行集刷新功能 dbProp[7].colid = DB_NULLID; dbProp[7].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[7].dwPropertyID = DBPROP_IRowsetRefresh; dbProp[7].vValue.vt = VT_BOOL; dbProp[7].vValue.boolVal = VARIANT_TRUE; //申请打开列信息扩展接口 dbProp[8].colid = DB_NULLID; dbProp[8].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[8].dwPropertyID = DBPROP_IColumnsInfo2; dbProp[8].vValue.vt = VT_BOOL; dbProp[8].vValue.boolVal = VARIANT_TRUE; //申请打开数据库同步状态接口 dbProp[9].colid = DB_NULLID; dbProp[9].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[9].dwPropertyID = DBPROP_IDBAsynchStatus; dbProp[9].vValue.vt = VT_BOOL; dbProp[9].vValue.boolVal = VARIANT_TRUE; //申请打开行集分章功能 dbProp[10].colid = DB_NULLID; dbProp[10].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[10].dwPropertyID = DBPROP_IChapteredRowset; dbProp[10].vValue.vt = VT_BOOL; dbProp[10].vValue.boolVal = VARIANT_TRUE; dbProp[11].colid = DB_NULLID; dbProp[11].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[11].dwPropertyID = DBPROP_IRowsetCurrentIndex; dbProp[11].vValue.vt = VT_BOOL; dbProp[11].vValue.boolVal = VARIANT_TRUE; dbProp[12].colid = DB_NULLID; dbProp[12].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[12].dwPropertyID = DBPROP_IGetRow; dbProp[12].vValue.vt = VT_BOOL; dbProp[12].vValue.boolVal = VARIANT_TRUE; //申请打开行集更新功能 dbProp[13].colid = DB_NULLID; dbProp[13].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[13].dwPropertyID = DBPROP_IRowsetUpdate; dbProp[13].vValue.vt = VT_BOOL; dbProp[13].vValue.boolVal = VARIANT_TRUE; dbProp[14].colid = DB_NULLID; dbProp[14].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[14].dwPropertyID = DBPROP_IConnectionPointContainer; dbProp[14].vValue.vt = VT_BOOL; dbProp[14].vValue.boolVal = VARIANT_TRUE; dbPropset[0].cProperties = 15; dbPropset[0].guidPropertySet = DBPROPSET_ROWSET; dbPropset[0].rgProperties = dbProp; hRes = pICommandText->QueryInterface(IID_ICommandProperties, (void**)&pICommandProperties); COM_SUCCESS(hRes, _T("查询接口ICommandProperties失败,错误码:%08x\n"), hRes); hRes = pICommandProperties->SetProperties(1, dbPropset); COM_SUCCESS(hRes, _T("设置属性失败,错误码:%08x\n"), hRes); hRes = pICommandText->Execute(NULL, IID_IRowset, NULL, NULL, (IUnknown**)&pIRowset); COM_SUCCESS(hRes, _T("执行sql语句失败,错误码:%08x\n"), hRes);这段代码详细的展示了如何执行SQL语句获取结果集并设置COMMANDUI对象的属性。结果集对象结果集一般是执行完SQL语句后返回的一个代表二维结构化数组的对象。这个结构化对象可以理解为一个与数据表定义相同的一个结构体。而结果集中保存了这个结构体的指针下面是结果集对象的详细定义CoType TRowset { [mandatory] interface IAccessor; [mandatory] interface IColumnsInfo; [mandatory] interface IConvertType; [mandatory] interface IRowset; [mandatory] interface IRowsetInfo; [optional] interface IChapteredRowset; [optional] interface IColumnsInfo2; [optional] interface IColumnsRowset; [optional] interface IConnectionPointContainer; [optional] interface IDBAsynchStatus; [optional] interface IGetRow; [optional] interface IRowsetChange; [optional] interface IRowsetChapterMember; [optional] interface IRowsetCurrentIndex; [optional] interface IRowsetFind; [optional] interface IRowsetIdentity; [optional] interface IRowsetIndex; [optional] interface IRowsetLocate; [optional] interface IRowsetRefresh; [optional] interface IRowsetScroll; [optional] interface IRowsetUpdate; [optional] interface IRowsetView; [optional] interface ISupportErrorInfo; [optional] interface IRowsetBookmark; }结果集对象的一般用法得到结果集后,它的使用步骤一般如下:首先Query出IColumnsInfo接口通过调用IColumnsInfo::GetColumnInfo方法得到关于结果集的列的详细信息DBCOLUMNINFO结构的数组,包括:列序号,列名,类型,字节长度,精度,比例等3.通过该结构数组,准备一个对应的DBBINDING结构数组,并计算每行数据实际需要的缓冲大小,并填充结构DBBINDING。这个过程一般叫做绑定4.利用DBBINDING数组和IAccessor::CreateAccessor方法创建一个数据访问器并得到句柄HACCESSOR调用IRowset::GetNextRow遍历行指针到下一行,第一次调用就是指向第一行,并得到行句柄HROW,这个行句柄表示我们访问的当前是结果中的第几行,一般它的值是一个依次递增的整数调用IRowset::GetData传入准备好的行缓冲内存指针,以及之前创建的访问器HACCESSOR句柄和HROW句柄。最终行数据就被放置到了指定的缓冲中。循环调用GetNextRow和GetData即可遍历整个二维结果集。列信息的获取取得结果集对象后,紧接着的操作一般就是获取结果集的结构信息,也就是获取结果集的列信息(有些材料中称为字段信息)要获取列信息,就需要QueryInterface出结果集对象的IColumnsInfo接口,并调用IColumnsInfo::GetColumnInfo方法获得一个称为DBCOLUMNINFO结构体的数组该结构体中反映了列的逻辑结构信息(抽象数据类型)和物理结构信息(内存需求大小等信息)函数GetColumnInfo定义如下:HRESULT GetColumnInfo ( DBORDINAL *pcColumns, DBCOLUMNINFO **prgInfo, OLECHAR **ppStringsBuffer);第一个参数表示总共有多少列,第二个参数是一个DBCOLUMNINFO,返回一个列信息的数组指针,第三个参数返回一个字符串指针,这个字符串中保存的是个列的名称,每个名称间以\0\0分割。但是我们一般不使用它来获取列名,我们一般使用DBCOLUMNINFO结构的pwszName成员。DBCOLUMNINFO定义如下:typedef struct tagDBCOLUMNINFO { LPOLESTR pwszName; //列名 ITypeInfo *pTypeInfo; //列的类型信息 DBORDINAL iOrdinal; //列序号 DBCOLUMNFLAGS dwFlags; //列的相关标识 DBLENGTH ulColumnSize; //列最大可能的大小,对于字符串来说,它表示的是字符个数 DBTYPE wType; //列类型 BYTE bPrecision; //精度(它表示小数点后面的位数) BYTE bScale; //表示该列的比例,目前没有用处,一般给的是0 DBID columnid; //列信息在数据字典表中存储的ID } DBCOLUMNINFO;对于columnid成员,DBMS系统一般会有多个系统表来表示众多的信息,比如用户信息,数据库信息,数据表信息等等,其中针对每个表中的列的相关信息DBMS系统使用特定的系统表来存储,而查询这个系统表来获取列信息时使用的就是这个columnid值。数据绑定一般绑定需要两步,1是获取列信息的DBCOLUMNINFO结构,接着就是根据列信息来填充DBBINDING数据结构。有的时候可能会觉得绑定好麻烦啊,还不如直接返回一个缓冲,将所有结果放入里面,应用程序根据需求自己去解析它,这样岂不是更方便。之所以需要绑定,有下面一个理由:并不是所有的数据类型都能被应用程序支持,比如说数据库中的NUMBER类型在VC++中找不到对应的数据结构来支持。有时一行数据并不能完全读取到内存中,比如说我们给的缓冲不够或者是数据库中的数据本身比较大,比如存储了一个视频文件等等。在程序中并不是所有的访问器都是为了读取数据,而且使用返回所有结果的方式太简单粗暴了,比如我只想要一列的数据那个数据可能占用内存不足1K,但是数据库表中某一列数据特别大,可能占用内存会超过一个G,如果全都返回的话太浪费内存了。所以在绑定时候可以灵活的指定返回那些数据,返回数据长度是多少,针对特别大的数据,我们可以指定它只返回部分,比如只返回前面的1K使用绑定可以灵活的安排返回数据在内存中的摆放形式。绑定结构的定义如下:typedef struct tagDBBINDING { DBORDINAL iOrdinal; //列号 DBBYTEOFFSET obValue; DBBYTEOFFSET obLength; DBBYTEOFFSET obStatus; ITypeInfo *pTypeInfo; DBOBJECT *pObject; DBBINDEXT *pBindExt; DBPART dwPart; DBMEMOWNER dwMemOwner; DBPARAMIO eParamIO; DBLENGTH cbMaxLen; //数据的最大长度,一般给我们为这个列的数据准备的缓冲的长度 DWORD dwFlags; DBTYPE wType; //将该列转化为何种数据类型展示 BYTE bPrecision; BYTE bScale; }DBBINDING;参数的详细说明:obValue、obLength、obStatus:数据源在返回结果的时候一般会返回该列信息的三中数据,数据长度、数据状态、数据值。数据状态表示数据源在提供数据的一个状态信息,比如该列信息为空时它会返回一个DBSTATUS_S_ISNULL,列数据比较长,而提供的数据可能不够,这个时候会返回一个状态表示发生了截断。而数据长度表示返回结果的长度。这个值是返回的数据对应的字节长度,注意这里需要与前面ulColumnSize区分开来。三个数据在内存中摆放的顺序如下:其中每列数据都是按照status length value结构排布,而不同的列的数据按照顺序依次向后排放,这个内存的布局有点像结构体数组在内存中的的布局方式。而绑定结构中的obValue、obLength、obStatus规定了它们三者在一块内存缓冲中的偏移,要注意后面一列的开始位置是在前面一列的结束位置而不是所有数据都是从0开始。dwPart:前面说数据源返回结果中有3个部分,但是我们可以指定数据源返回这3个部分的哪些部分,它的值是一些标志位,根据这些标志来决定需要返回哪些数据,不需要返回哪些数据.它的值主要有:DBPART_LENGTH、 DBPART_STATUS、DBPART_VALUE;dwMemOwner,这个值表示将使用何种内存缓冲来保存数据源返回的结果,我们一般使用DBMEMOWNER_CLIENTOWNED,表示使用用户自定义内存的方式,即这个缓冲需要自行准备。eParamIO:我们将返回的值做何种用途,DBPARAMIO_NOTPARAM表示不做特殊用途,DBPARAMIO_INPUT,作为输入值,一般在需要更新列数据的时候使用这个标志,DBPARAMIO_OUTPUT,作为输出值,这个输出时相对于数据源来说的,表示输出到应用程序程序缓冲,作为展示用。wType:将数据源中的原始数据做何种类型的转化,比如原来数据库中存储的是整数的123456,而这个值是DBTYPE_WSTR的话,数据源中的结果会被转化为字符串的"123456",放入到缓冲中。DBBINDING 与DBCOLUMNSINFO结构的比较它们二者中有许多数据成员是相同的,表示的含义也基本相同,但是二者也有显著的区别:DBCOLUMNINFO是数据提供者给使用者的信息,它是固定的,对相同的查询来说,列总是相同的,因此数据提供者返回的 DBCOLUMNINFO数组也是固定的.而DBBINDING是作为数据消费者创建之后给数据提供者的一个结构数组,它的内容则由调用者来完全控制,通过这个结构可以指定数据提供者最终将数据摆放成调用者指定的格式,并进行指定的数据类型转换.针对相同的查询我们可以指定不同的DBBINDINGS结构。DBCOLUMNINFO反映的是二维结果集的原始列结构信息而DBBINDING则反映的是二维结果集数据最终按要求摆放在内存中的样式下面是一个针对绑定的列子:ExecSql(pIOpenRowset, pIRowset); //创建IColumnsInfo接口 HRESULT hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo); COM_SUCCESS(hRes, _T("查询接口IComclumnsInfo,错误码:%08x\n"), hRes); //获取结果集的详细信息 hRes = pIColumnsInfo->GetColumnInfo(&cClumns, &rgColumnInfo, &lpClumnsName); COM_SUCCESS(hRes, _T("获取列信息失败,错误码:%08x\n"), hRes); //绑定 pDBBindings = (DBBINDING*)MALLOC(sizeof(DBBINDING) * cClumns); for (int iRow = 0; iRow < cClumns; iRow++) { pDBBindings[iRow].bPrecision = rgColumnInfo[iRow].bPrecision; pDBBindings[iRow].bScale = rgColumnInfo[iRow].bScale; pDBBindings[iRow].cbMaxLen = rgColumnInfo[iRow].ulColumnSize * sizeof(WCHAR); if (rgColumnInfo[iRow].wType == DBTYPE_I4) { //数据库中行政单位的长度最大为6位 pDBBindings[iRow].cbMaxLen = 7 * sizeof(WCHAR); } pDBBindings[iRow].dwMemOwner = DBMEMOWNER_CLIENTOWNED; pDBBindings[iRow].dwPart = DBPART_LENGTH | DBPART_STATUS | DBPART_VALUE; pDBBindings[iRow].eParamIO = DBPARAMIO_NOTPARAM; pDBBindings[iRow].iOrdinal = rgColumnInfo[iRow].iOrdinal; pDBBindings[iRow].obStatus = dwOffset; pDBBindings[iRow].obLength = dwOffset + sizeof(DBSTATUS); pDBBindings[iRow].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG); pDBBindings[iRow].wType = DBTYPE_WSTR; dwOffset = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG) + pDBBindings[iRow].cbMaxLen * sizeof(WCHAR); dwOffset = COM_ROUNDUP(dwOffset); //进行内存对齐 } //创建访问器 hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor); COM_SUCCESS(hRes, _T("查询IAccessor接口失败错误码:%08x\n"), hRes); hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cClumns, pDBBindings, 0, &hAccessor, NULL); COM_SUCCESS(hRes, _T("创建访问器失败错误码:%08x\n"), hRes); //输出列名信息 DisplayColumnName(rgColumnInfo, cClumns); //分配对应的内存 pData = MALLOC(dwOffset * cRows); while (TRUE) { hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &uRowsObtained, &phRows); if (hRes != S_OK && uRowsObtained != 0) { break; } ZeroMemory(pData, dwOffset * cRows); //显示数据 for (int i = 0; i < uRowsObtained; i++) { pCurrData = (BYTE*)pData + dwOffset * i; pIRowset->GetData(phRows[i], hAccessor, pCurrData); DisplayData(pDBBindings, cClumns, pCurrData); } //清理hRows pIRowset->ReleaseRows(uRowsObtained, phRows, NULL, NULL, NULL); CoTaskMemFree(phRows); phRows = NULL; } //显示列名称 void DisplayColumnName(DBCOLUMNINFO *pdbColumnInfo, DBCOUNTITEM iDbCount) { COM_DECLARE_BUFFER(); for (int iColumn = 0; iColumn < iDbCount; iColumn++) { COM_CLEAR_BUFFER(); TCHAR wszColumnName[MAX_DISPLAY_SIZE + 1] =_T(""); size_t dwSize = 0; StringCchLength(pdbColumnInfo[iColumn].pwszName, MAX_DISPLAY_SIZE, &dwSize); dwSize = min(dwSize, MAX_DISPLAY_SIZE); StringCchCopy(wszColumnName, MAX_DISPLAY_SIZE, pdbColumnInfo[iColumn].pwszName); COM_PRINTF(wszColumnName); COM_PRINTF(_T("\t")) } COM_PRINTF(_T("\n")); } //显示数据 void DisplayData(DBBINDING *pdbBindings, DBCOUNTITEM iDbColCnt, void *pData) { COM_DECLARE_BUFFER(); for (int i = 0; i < iDbColCnt; i++) { COM_CLEAR_BUFFER(); DBSTATUS status = *(DBSTATUS*)((PBYTE)pData + pdbBindings[i].obStatus); ULONG uSize = (*(ULONG*)((PBYTE)pData + pdbBindings[i].obLength)) / sizeof(WCHAR); PWSTR pCurr = (PWSTR)((PBYTE)pData + pdbBindings[i].obValue); switch (status) { case DBSTATUS_S_OK: case DBSTATUS_S_TRUNCATED: COM_PRINTF(_T("%s\t"), pCurr); break; case DBSTATUS_S_ISNULL: COM_PRINTF(_T("%s\t"), _T("(null)")); break; default: break; } } COM_PRINTF(_T("\n")); }在使用前一个列子中的方法设置对应的属性并执行SQL语句后,得到一个结果集,然后调用对应的Query方法,得到一个pIColumnsInfo接口,接着调用接口的GetColumnsInfo方法,获取结构的具体信息。最需要注意的是绑定部分的代码,根据返回的具体列数,我们定义了一个对应的绑定结构的数组,将每个赋值,赋值的时候定义了一个dwOffset结构来记录当前使用内存的情况,这样每次在循环执行一次后,它的位置永远在上一个列信息缓冲的尾部,这样我们可以很方便的进行偏移的计算。绑定完成后这个dwOffset的值就是所有列使用的内存的总大小,因此在后面利用这个值分配一个对应长度的内存。然后循环调用GetNextRows、GetData方法依次获取每行、每列的数据。最后调用相应的函数来进行显示,至此就完成了数据的读取操作。最后,我发现码云上的代码片段简直就是为保存平时例子代码而生的,所以后面的代码将不再在GitHub上更新了,而换到码云上面。源代码查看
2018年01月28日
31 阅读
5 评论
0 点赞
2018-01-21
事务对象和命令对象
上次说到数据源对象,这次接着说事务对象和命令对象。事务是一种对数据源的一系列更新进行分组或批处理以便当所有更新都成功时同时提交这些更新,或者如果任何一个更新失败则不提交任何更新并且回滚整个事务的方法.命令对象一般是用来执行sql语句并生成结果集的对象会话对象在OLEDB中通过以下3中方式支持事务:ITransactionLocal::StartTransactionITransaction::commitITransaction::AbortOLEDB中定义事务和回话对象的接口如下:CoType TSession { [mandatory] interface IGetDataSource; [mandatory] interface IOpenRowset; [mandatory] interface ISessionProperties; [optional] interface IAlterIndex; [optional] interface IAlterTable; [optional] interface IBindResource; [optional] interface ICreateRow; [optional] interface IDBCreateCommand; [optional] interface IDBSchemaRowset; [optional] interface IIndexDefinition; [optional] interface ISupportErrorInfo; [optional] interface ITableCreation; [optional] interface ITableDefinition; [optional] interface ITableDefinitionWithConstraints; [optional] interface ITransaction; [optional] interface ITransactionJoin; [optional] interface ITransactionLocal; [optional] interface ITransactionObject; }在创建了数据库连接之后使用QueryInterface 查询出IDBCreateSeesion对象,然后调用IDBCreateSession的CreateSession方法创建一个回话对象。需要注意的是,一个数据源连接可以创建多个回话对象,这里只能通过这种方式创建回话对象,而不能直接通过CoCreateInstance 来创建。一般来说应用中至少创建一个会话对象Command对象Command对象的定义如下:CoType TCommand { [mandatory] interface IAccessor; [mandatory] interface IColumnsInfo; [mandatory] interface ICommand; [mandatory] interface ICommandProperties; [mandatory] interface ICommandText; [mandatory] interface IConvertType; [optional] interface IColumnsRowset; [optional] interface ICommandPersist; [optional] interface ICommandPrepare; [optional] interface ICommandWithParameters; [optional] interface ISupportErrorInfo; [optional] interface ICommandStream; }一般创建Command对象是通过Session对象Query出来一个IDBCreateCommand接口让后调用CreateCommand方法创建。与会话对象相似,一个会话对象可以创建多个命令对象,但是从上面会话对象的定义可以看出IDBCreateCommand接口是一个可选接口,并不是所有的数据库都支持,因此在创建命令对象的时候一定要注意判断是否支持。对于不支持的可以采用其他办法(一般采用直接打开数据库表的方式)。下面是一个演示例子:#define CHECK_OLEDB_INTERFACE(I, iid)\ hRes = (I)->QueryInterface(IID_##iid, (void**)&(p##iid));\ if(FAILED(hRes))\ {\ OLEDB_PRINTF(_T("不支持接口:%s\n"), _T(#iid));\ }\ else\ {\ OLEDB_PRINTF(_T("支持接口:%s\n"), _T(#iid));\ } BOOL CreateDBSession(IOpenRowset* &pIOpenRowset) { DECLARE_BUFFER(); DECLARE_OLEDB_INTERFACE(IDBPromptInitialize); DECLARE_OLEDB_INTERFACE(IDBInitialize); DECLARE_OLEDB_INTERFACE(IDBCreateSession); BOOL bRet = FALSE; HWND hDesktop = GetDesktopWindow(); HRESULT hRes = CoCreateInstance(CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER, IID_IDBPromptInitialize, (void**)&pIDBPromptInitialize); OLEDB_SUCCESS(hRes, _T("创建接口IDBPromptInitialize失败,错误码:%08x\n"), hRes); hRes = pIDBPromptInitialize->PromptDataSource(NULL, hDesktop, DBPROMPTOPTIONS_WIZARDSHEET, 0, NULL, NULL, IID_IDBInitialize, (IUnknown**)&pIDBInitialize); OLEDB_SUCCESS(hRes, _T("弹出接口数据源配置对话框失败,错误码:%08x\n"), hRes); hRes = pIDBInitialize->Initialize(); OLEDB_SUCCESS(hRes, _T("链接数据库失败,错误码:%08x\n"), hRes); hRes = pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession); OLEDB_SUCCESS(hRes, _T("创建接口IDBCreateSession失败,错误码:%08x\n"), hRes); hRes = pIDBCreateSession->CreateSession(NULL, IID_IOpenRowset, (IUnknown**)&pIOpenRowset); OLEDB_SUCCESS(hRes, _T("创建接口IOpenRowset失败,错误码:%08x\n"), hRes); bRet = TRUE; __CLEAR_UP: OLEDB_SAFE_RELEASE(pIDBPromptInitialize); OLEDB_SAFE_RELEASE(pIDBInitialize); OLEDB_SAFE_RELEASE(pIDBCreateSession); return bRet; } int _tmain(int argc, TCHAR *argv[]) { DECLARE_BUFFER(); DECLARE_OLEDB_INTERFACE(IOpenRowset); DECLARE_OLEDB_INTERFACE(IDBInitialize); DECLARE_OLEDB_INTERFACE(IDBCreateCommand); DECLARE_OLEDB_INTERFACE(IAccessor); DECLARE_OLEDB_INTERFACE(IColumnsInfo); DECLARE_OLEDB_INTERFACE(ICommand); DECLARE_OLEDB_INTERFACE(ICommandProperties); DECLARE_OLEDB_INTERFACE(ICommandText); DECLARE_OLEDB_INTERFACE(IConvertType); DECLARE_OLEDB_INTERFACE(IColumnsRowset); DECLARE_OLEDB_INTERFACE(ICommandPersist); DECLARE_OLEDB_INTERFACE(ICommandPrepare); DECLARE_OLEDB_INTERFACE(ICommandWithParameters); DECLARE_OLEDB_INTERFACE(ISupportErrorInfo); DECLARE_OLEDB_INTERFACE(ICommandStream); CoInitialize(NULL); if (!CreateDBSession(pIOpenRowset)) { OLEDB_PRINTF(_T("调用函数CreateDBSession失败,程序即将推出\n")); return 0; } HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand); OLEDB_SUCCESS(hRes, _T("创建接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pIDBCreateCommand->CreateCommand(NULL, IID_IAccessor, (IUnknown**)&pIAccessor); OLEDB_SUCCESS(hRes, _T("创建接口IAccessor失败,错误码:%08x\n"), hRes); CHECK_OLEDB_INTERFACE(pIAccessor, IColumnsInfo); CHECK_OLEDB_INTERFACE(pIAccessor, ICommand); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandProperties); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandText); CHECK_OLEDB_INTERFACE(pIAccessor, IConvertType); CHECK_OLEDB_INTERFACE(pIAccessor, IColumnsRowset); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandPersist); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandPrepare); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandWithParameters); CHECK_OLEDB_INTERFACE(pIAccessor, ISupportErrorInfo); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandStream); __CLEAR_UP: OLEDB_SAFE_RELEASE(pIOpenRowset); OLEDB_SAFE_RELEASE(pIDBInitialize); OLEDB_SAFE_RELEASE(pIDBCreateCommand); OLEDB_SAFE_RELEASE(pIAccessor); OLEDB_SAFE_RELEASE(pIColumnsInfo); OLEDB_SAFE_RELEASE(pICommand); OLEDB_SAFE_RELEASE(pICommandProperties); OLEDB_SAFE_RELEASE(pICommandText); OLEDB_SAFE_RELEASE(pIConvertType); OLEDB_SAFE_RELEASE(pIColumnsRowset); OLEDB_SAFE_RELEASE(pICommandPersist); OLEDB_SAFE_RELEASE(pICommandPrepare); OLEDB_SAFE_RELEASE(pICommandWithParameters); OLEDB_SAFE_RELEASE(pISupportErrorInfo); OLEDB_SAFE_RELEASE(pICommandStream); CoUninitialize(); return 0; }在上述的例子中,首先定义了一个宏,用来判断是否支持对应的接口。同时定义了一个CreateDBSession方法来创建一个会话对象。在该函数中首先利用上一节的方法创建一个数据库连接,然后在数据源对象上调用QueryInterface来获取接口IDBCreateSeesion,接着利用IDBCreateSeesion接口的CreateSeesion方法创建一个会话对象,由于IDBCreateCommand接口并不是所有的数据源都支持,所以为了保证一定能创建会话对象,我们选择会话对象必须支持的接口IOpenRowset。在得到会话对象后,尝试创建IDBCreateSession对象,如果它不支持,那么程序直接退出。接着调用IDBCreateCommand接口来创建一个命令对象并尝试query命令对象的其他接口,得出数据源支持哪些接口。这个例子非常简单,只是为了演示如何创建会话对象和数据源对象罢了。本节例子代码
2018年01月21日
3 阅读
0 评论
0 点赞
2018-01-12
OLEDB数据源
数据源在oledb中指数据提供者,这里可以简单的理解为数据库程序。数据源对象代表数据库的一个连接,是需要创建的第一个对象。而数据源对象主要用于配置数据库连接的相关属性如连接数据库的用户名密码等等
2018年01月12日
6 阅读
0 评论
0 点赞
2018-01-06
Windows数据库编程接口简介
数据库是计算机中一种专门管理数据资源的系统,目前几乎所有软件都需要与数据库打交道(包括操作系统,比如Windows上的注册表其实也是一种数据库),有些软件更是以数据库为核心因此掌握数据库系统的使用方法以及数据库系统编程接口的使用方法是程序员非常重要的基本技能之一。所以我花了一定的时间学习了在Windows平台上使用COM接口的方式操作数据库。这段时间我会将自己学习过程中掌握的知识和其中的一些坑都发布出来,供个人参考,也方便他人学习
2018年01月06日
3 阅读
0 评论
0 点赞
2017-11-02
COM学习(四)——COM中的数据类型
上一次说到,COM为了跨语言,有一套完整的规则,只要COM组件按照规则编写,而不同的语言也按照对应的规则调用,那么就可以实现不同语言间相互调用。但是根据那套规则,只能识别接口,并调用没有参数和返回类型的接口,毕竟不同语言里面的基本数据类型不同,可能在VC++中char * 就表示字符串,而在Java或者c#中string是一个对象,二者的内存结构不同,不能简单的进行内存数据类型的强制转化。为了实现数据的正常交互,COM中又定义了一组公共的数据类型。HRESULT类型:在COM中接口的返回值强制定义为该类型,用于表示当前执行的状态是完成或者是出错,这个类型一般在VC中使用,别的语言在调用时根据接口的这个值来确定接下来该如何进行。HRESULT类型的定义如下:typedef _Return_type_success_(return >= 0) long HRESULT;其实它就是一个32位的整数,微软将这个整数分成几个部分,各个部分都有详细的含义,这个值的详细解释在对应的winerror.h中。// // Note: There is a slightly modified layout for HRESULT values below, // after the heading "COM Error Codes". // // Search for "**** Available SYSTEM error codes ****" to find where to // insert new error codes // // Values are 32 bit values laid out as follows: // // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +-+-+-+-+-+---------------------+-------------------------------+ // |S|R|C|N|r| Facility | Code | // +-+-+-+-+-+---------------------+-------------------------------+ // // where // // S - Severity - indicates success/fail // // 0 - Success // 1 - Fail (COERROR) // // R - reserved portion of the facility code, corresponds to NT's // second severity bit. // // C - reserved portion of the facility code, corresponds to NT's // C field. // // N - reserved portion of the facility code. Used to indicate a // mapped NT status value. // // r - reserved portion of the facility code. Reserved for internal // use. Used to indicate HRESULT values that are not status // values, but are instead message ids for display strings. // // Facility - is the facility code // // Code - is the facility's status code // // // Define the facility codes //根据上面的注释可以看到,以及我自己查阅相关资料,它里面总共有7个部分,各个部分代表的含义如下:S - 严重性 - 表示成功或失败0 - 成功,1 - 失败R - 设施代码的保留部分,对应于NT的第二严重性位。1 - 严重故障C - 第三方。 此位指定值是第三方定义还是Microsoft定义的。0 - Microsoft-定义,1 - 第三方定义N - 保留部分设施代码。 用于指示映射的NT状态值。X - 保留部分设施代码。 保留供内部使用。 用于指示不是状态值的HRESULT值,而是用于显示字符串的消息标识。Facility - 表示引发错误的系统服务. 示例Facility代码如下所示:2 - 调度(COM调度)3 - 存储 (OLE存储)4 - ITF (COM/OLE 接口管理)7 - (原始 Win32 错误代码)8 - Windows9 - SSPI10 - 控制11 - CERT (客户端或服务器认证)...Code - 设施的状态代码其实这些没有必要知道的很详细,只需要知道里面常用的几个即可:S_OK:成功S_FALSE:失败E_NOINTERFACE:没有接口,一般是由QueryInterface或者CoCreateInterface函数返回,当我们传入的ID不对它找不到对应的接口时返回该值E_OUTOFMEMORY:当内存不足时返回该值。一般在COM的调用者看来,有的时候只要最高位不为0就表示成功,这个时候可能会继续使用,所以在我们自己编写组件的时候要根据具体情况选择返回值,不要错误了就返回S_FALSE,其实我们看它的定义可以知道它是等于1的,最高位为0,仍然是成功的。如果返回S_FALSE可能会造成意想不到的错误,而且还难以调试。BSTRCOM中规定了一种通用的字符串类型BSTR,查看BSTR的定义如下:typedef /* [wire_marshal] */ OLECHAR *BSTR; typedef WCHAR OLECHAR;从上面的定义上不难看出BSTR其实就是一个WCHAR ,也就是一个指向宽字符的指针。COM中使用的是UNICODE字符串,在编写COM程序的时候经常涉及到CString、WCHAR、char等的相互转化,其实本质上就是多字节字符与宽字节字符之间的转化。我们平时在进行char 与WCHAR*之间转化的函数像WideCharToMultiByte和MultiByteToWideChar,以及W2A和A2W等。COM为了方便使用,另外也提供了一组转化函数_com_util::ConvertBSTRToString以及_com_util::ConvertStringToBSTR用在在char*与BSTR之间进行转化。需要注意的是,这组函数返回的字符串是在堆上分配出来的,使用完后需要自己释放。在BSTR类型中,定义了两个函数SysAllocString(),和SysFreeString()用来分配和释放一个BSTR的内存空间。在这总结一下他们之间的相互转化:char*----->BSTR: _com_util::ConvertStringToBSTRWCHAR*---->BSTR:可以直接用 = 进行赋值,也可以使用SysAllocStringBSTR---->WCHAR:一般是直接使用等号即可,但是在WCHAR使用完之前不能释放,所以一般都是赋值给一个CStringBSTR---->char*:_com_util::ConvertBSTRToStringConvert函数是定义在头文件atlutil.h中并且需要引用comsupp.lib文件另外COM封装了一个_bstr_t的类,使用这个类就更加方便了,它封装了与char*之间的相互转化,可以直接使用赋值符号进行相互转化,同时也不用考虑回收内存的问题,它自己会进行内存回收。VARIANT 万能类型现代编程语言一般有强类型的语言和弱类型的语言,强类型的像C/C++、Java这样的,必须在使用前定义变量类型,而弱类型像Python这样的可以直接定义变量而不用管它的类型,甚至可以写出像:i = 0 i = "hello world"这样的代码,而且不同语言中可能同一种类型的变量在内存布局上也可能不一样。解决不同语言之间变量类型的冲突,COM定义了一种万能类型——VARIANT。typedef struct tagVARIANT VARIANT; typedef struct tagVARIANT VARIANTARG; struct tagVARIANT { union { struct __tagVARIANT { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; _VARIANT_BOOL bool; SCODE scode; CY cyVal; DATE date; BSTR bstrVal; IUnknown *punkVal; IDispatch *pdispVal; SAFEARRAY *parray; BYTE *pbVal; SHORT *piVal; LONG *plVal; LONGLONG *pllVal; FLOAT *pfltVal; DOUBLE *pdblVal; VARIANT_BOOL *pboolVal; _VARIANT_BOOL *pbool; SCODE *pscode; CY *pcyVal; DATE *pdate; BSTR *pbstrVal; IUnknown **ppunkVal; IDispatch **ppdispVal; SAFEARRAY **pparray; VARIANT *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; ULONGLONG ullVal; INT intVal; UINT uintVal; DECIMAL *pdecVal; CHAR *pcVal; USHORT *puiVal; ULONG *pulVal; ULONGLONG *pullVal; INT *pintVal; UINT *puintVal; struct __tagBRECORD { PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; };从定义上看出,它其实是一个巨大的联合体,将所有C/C++的基本类型都包含进来,甚至包含了像BSTR, 这样的COM中使用的类型。它通过成员vt来表示它当前使用的是哪种类型的变量。vt的类型是一个枚举类型,详细的定义请参见MSDN。为了简化操作,COM中也对它进行了一个封装——_variant_t,该类型可以直接使用任何类型的数据对其进行初始化操作。但是在使用里面的值时还是得判断它的vt成员的值COM中的其他操作最后附上一张COM常用函数表以供参考:
2017年11月02日
6 阅读
0 评论
0 点赞
2017-10-30
COM学习(三)——COM的跨语言
COM是基于二进制的组件模块,从设计之初就以支持所有语言作为它的一个目标,这篇文章主要探讨COM的跨语言部分。idl文件一般COM接口的实现肯定是以某一具体语言来实现的,比如说使用VC++语言,这就造成了一个问题,不同的语言对于接口的定义,各个变量的定义各不相同,如何让使用vc++或者说Java等其他语言定义的接口能被别的语言识别?为了达到这个要求,定义了一种文件格式idl——(Interface Definition Language)接口定义语言,IDL提供一套通用的数据类型,并以这些数据类型来定义更为复杂的数 据类型。一般来说,一个文件有下面几个部分说明接口的定义组件库的定义实现类的定义而各个部分又包括他们的属性定义,以及函数成员的定义属性:属性是在接口定义的上方,使用“[]”符号包裹,一般在属性中使用下面几个关键字:object:标明该部分是一个对象(可以理解为c++中的对象,包括接口和具体的实现类)uuid:标明该部分的GUIDversion:该部分的版本接口定义接口定义采用关键字interface,接口函数定义在一对大括号中,它的定义与类的定义相似,其中函数定义需要修饰函数各个参数的作用,比如使用in 表示它作为输入参数,out表示作为输出参数,retval表示该参数作为返回值,一般在VC++定义的接口中,函数返回值为HRESULT,但是需要返回一个值供外界调用,此时就使用输出参数并加上retval表示它将在其他语言中作为函数的返回值。组件库定义库使用library关键字定义,在定义库的时候,它的属性一般定义GUID和版本信息,而在库中通常定义库中的实现类的相关信息,库中的信息也是写在一对大括号中实现类的定义接口实现类使用关键字coclass,接口类的属性一般定义一个object,一个GUID,然后一般定义实现类不需要向在C++中那样定义它的各个接口,各个数据成员,只需要告知它实现哪些接口即可,也就是说它继承自哪些接口。下面是一个具体的例子:import "unknwn.idl"; [ object, uuid(CF809C44-8306-4200-86A1-0BFD5056999E) ] interface IMyString : IUnknown { HRESULT Init([in] BSTR bstrInit); HRESULT GetLength([out, retval] ULONG *pretLength); HRESULT Find([in] BSTR bstrFind, [out, retval] BSTR* bstrSub); }; [ uuid(ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2), version(1.0) ] library ComDemoLib { importlib("stdole32.tlb"); [ uuid(EBD699BA-A73C-4851-B721-B384411C99F4) ] coclass CMyString { interface IMyString; }; };上面的例子中定义了一个IMyString接口继承自IUnknown接口,函数参数列表中in表示参数为输入参数,out表示它为输出参数,retval表示该参数是函数的返回值。import导入了一个库文件类似于include。而importlib导入一个tlb文件,我们可以将其看成VC++中的#pragma comment导入一个lib库从上面不难看出一个IDL文件至少有3个ID,一个是接口ID,一个是库ID,还有一个就是实现类的ID在VC环境中通过midl命令可以对该文件进行编译,编译会生成下面几个我们在编写实现时会用到的重要文件:一个.h文件:包含各个部分的声明,以及接口的定义一个_i.c文件:包含各个部分的定义,主要是各个GUI的定义需要实现的导出函数一般我们需要在dll文件中导出下面几个全局的导出函数:STDAPI DllRegisterServer(void); STDAPI DllUnregisterServer(void); STDAPI DllGetClassObject(const CLSID & rclsid, const IID & riid, void ** ppv); STDAPI DllCanUnloadNow(void);其中DllRegisterServer用来向注册表中注册模块的相关信息,主要注测在HKEY_CLASSES_ROOT中,主要定义下面几项内容:字符串名称项,该项中包含一个默认值,一般给组件的字符串名称;CLSID子健,一般给实现类的GUID;CurVer子健一般是子健的版本以版本字符串为键的注册表项,该项中主要保存:默认值,当前版本的项目名称;CLSID当前版本库的实现类的GUID在HKEY_CLASSES_ROOT/CLSID子健中注册以实现类GUID字符串为键的注册表项,里面主要包含:默认值,组件字符串名称;InprocServer32,组件所在模块的全路径;ProgID组件名称;TypeLib组件类型库的ID,也就是在定义IDL文件时,定义的实现库的GUID。下面是具体的定义:const TCHAR *g_RegTable[][3] = { { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}"), 0, _T("FirstComLib.MyString")}, //组件ID { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\InprocServer32"), 0, (const TCHAR*)-1 }, //组建路径 { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\ProgID"), 0, _T("FirstComLib.MyString")}, //组件名称 { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\TypeLib"), 0, _T("{ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2}") }, //类型库ID { _T("FirstComLib.MyString"), 0, _T("FirstComLib.MyString") }, //组件的字符串名称 { _T("FirstComLib.MyString\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")}, //组件的CLSID { _T("FirstComLib.MyString\\CurVer"), 0, _T("FirstComLib.MyString.1.0") }, //组件版本 { _T("FirstComLib.MyString.1.0"), 0, _T("FirstComLib.MyString") }, //当前版本的项目名称 { _T("FirstComLib.MyString.1.0\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")} //当前版本的CLSID };使用上一篇博文的代码,来循环注册这些项即可DllGetClassObject:该函数用来生成对应的工厂类,而工厂类负责产生对应接口的实现类。DllCanUnloadNow:函数用来询问是否可以卸载对应的dll,一般在COM中有两个全局的引用计数,用来记录当前内存中有多少个模块中的类,以及当前有多少个线程在使用它,如果当前没有线程使用或者存在的对象数为0,则可以卸载实现类的定义实现部分的整体结构图如下:由于所有类都派生自IUnknown,所在在这里就不显示这个基类了。每个实现类都对应了一个它具体的类工厂,而项目中CMyString类的类厂的定义如下:class CMyClassFactory : public IClassFactory { public: CMyClassFactory(); ~CMyClassFactory(); STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObject); STDMETHOD(LockServer)(BOOL isLock); STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject); STDMETHOD_(ULONG, AddRef)(void); STDMETHOD_(ULONG, Release)(void); protected: ULONG m_refs; };STDMETHOD宏展开如下:#define STDMETHOD(method) virtual HRESULT __stdcall method所以上面的代码展开后就变成了:virtual HRESULT __stdcall CreateInstance((IUnknown *pUnkOuter, REFIID riid, void **ppvObject);另外3个派生自IUnknown接口就没什么好说的,主要说说另外两个:CreateInstance:主要用来生成对应的实现类,然后再调用实现类——CMyString的QueryInterface函数生成对应的接口LockServer:当前是否被锁住:如果传入的值为TRUE,则表示被锁住,对应的锁计数器+1, 否则 -1至于CMyString类的代码与之前的大同小异,也就没什么说的。其他语言想要调用,以该项目为例,一般会经历下面几个步骤:调用对应语言提供的产生接口的函数,该函数参数一般是传入一个组件的字符串名称。如果要引用该项目中的组件则会传入FirstComLib.MyString在注册表的HKEY_CLASSES_ROOT\组件字符串名\CLSID(比如HKEY_CLASSES_ROOT\FirstComLib.MyString\CLSID)中找到对应的CLSID值在HKEY_CLASSES_ROOT\CLSID\对应ID\InprocServer32(CLSID\{EBD699BA-A73C-4851-B721-B384411C99F4}\InprocServer32)位置处找到对应模块的路径加载该模块根据IDL文件告知其他语言里面存在的接口,由语言调用对应的创建接口的函数创建接口调用模块的导出函数DllGetClassObject将查询到的CLSID作为第一个参数,并将接口ID作为第二个参数传入,得到一个接口6.后面根据idl文件中的定义,直接调用接口中提供的函数真实ATLCOM项目的解析最后来看看一个正式的ATLCOM项目里面的内容,来复习前面的内容,首先通过VC创建一个ATLCOM的dll项目在项目上右键-->New Atl Object,输入接口名称,IDE会根据名称生成一个对应的接口,还是以MyString接口为例,完成这一步后,整个项目的类结构如下:这些全局函数的作用与之前的相同,它里面多了一个_Module的全局对象,该对象类似于MFC中的CWinApp类,它用来表示整个项目的实例,里面封装了对于引用计数的管理,以及对项目中各个接口注册信息的管理,所以看DllRegisterServer等函数就会发现它们里面其实很简单,大部分的工作都由_Module对象完成。整个IDL文件的定义如下:import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(E3BD0C14-4D0C-48F2-8702-9F8DBC96E154), dual, helpstring("IMyString Interface"), pointer_default(unique) ] interface IMyString : IDispatch { }; [ uuid(A61AC54A-1B3D-4D8E-A679-00A89E2CBE93), version(1.0), helpstring("FirstAtlCom 1.0 Type Library") ] library FIRSTATLCOMLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(11CBC0BE-B2B7-4B5C-A186-3C30C08A7736), helpstring("MyString Class") ] coclass MyString { [default] interface IMyString; }; };里面的内容与上一次的内容相差无几,多了一个helpstring属性,该属性用于产生帮助信息,当使用者在调用接口函数时IDE会将此提示信息显示给调用者。由于有系统框架给我们做的大量的工作,我们再也不用关心像引用计数的问题,只需要将精力集中在编写接口的实现上,减少了不必要的工作量。至此从结构上说明了为了实现跨语言COM组件内部做了哪些工作,当然只有这些工作是肯定不够的,后面会继续说明它所做的另一块工作——提供的一堆通用的变量类型。
2017年10月30日
5 阅读
0 评论
0 点赞
1
2
3
4
...
9