首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
93 阅读
2
nvim番外之将配置的插件管理器更新为lazy
73 阅读
3
2018总结与2019规划
57 阅读
4
PDF标准详解(五)——图形状态
39 阅读
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
累计撰写
313
篇文章
累计收到
31
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE 运动
菜谱
页面
归档
友情链接
关于
搜索到
313
篇与
的结果
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日
5 阅读
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-03-09
使用pyh生成HTML文档
最近在项目中需要将结果导出到HTML中,在网上搜索的时候发现了这个库,通过官方的一些文档以及网上的博客发现它的使用还是很简单的,因此选择在项目中使用它。在使用的时候发现在Python3中有些问题,网上很多地方都没有提到,因此我在这将它的使用以及我遇到的问题和解决方案整理出来供大家参考本文主要参考pyh中文文档下载的样本也是该文中提到的地址常规使用在使用时一般先导入模块:from phy import *然后可以创建一个PyH对象就像这样page = PyH(title)其中title是一个字符串,这个字符串将作为页面的标题显示,也就是说此时产生的HTML代码就是在头部加上一个title标签并将这个字符串作为文本值然后我们可以addCSS方法或者addJS方法引入外部的js文件或者css文件(调用这两个函数将在HTML的头部产生一个引入的代码,对于那种在body中添加style代码的我暂时没有找到什么办法)然后就是创建标签对象,对应标签类的名字所与在HTML中的对应的名称相同,传入对象的参数就是标签中的属性,除了class属性对应的参数名称是cl外,其余的参数名称与在HTML中的属性一一对应。比如我们要创建一个div标签可以这样写myDiv = div('测试div', id = 'div1', cl = "cls_div")最终生成的HTML代码如下:<div id = 'div1' class = 'cls_div'>测试div</div>将元素加入某个元素中可以使用<<符号,该符号返回的是最后被包含的符号对象。比如这样div(id = 'div1') << p('测试' cl = 'p_tag')这句代码会返回p元素对应的对象,而生成的HTML代码如下:<div id = 'div1'> <p class = 'p_tag'>测试</p> </div>当生成了合适的HTML文档后可以使用printOut方法将其打印,也可以使用render函数返回对应的HTML代码,以便我们进行存盘或者做进一步处理上面只是简单的做一下介绍,详细的使用方法请参看上面提到的一篇文章,这上面写的比较详细。下面来通过一个例子代码来说明我是如何处理一些出现的错误、做一些简单的扩展,并大致看看里面的源代码例子from pyh import * import codecs from xml.sax.saxutils import escape WORD_WIDTH = 100 def create_base(table_title, page): page.addCSS('base.css') #展示信息的表 base_table = page << table(cl = 'diff', id = 'difflib_chg_to0__top', cellspacing = '0', cellpadding = '0', rules = 'groups') for i in range(4): base_table << colgroup() #表头 t_head = base_table << thead() tr_tag = t_head << tr() tr_tag << th(cl = 'diff_next') << br() tr_tag << th(table_title, colspan = '2', cl = 'diff_header') t_body = base_table << tbody() return t_body #写入一行信息 def write_line(tr_tag, mark, data): tr_tag << td(mark, cl = 'diff_header') tr_tag << td(data) def txt2html(title, table_title, ifile, ofile): i_f = codecs.open(ifile, 'r',encoding='utf-8') lines = i_f.read().splitlines() i_f.close() page = PyH(title) t_body = create_base(table_title, page) lineno = 1 for data in lines: if len(data) >= WORD_WIDTH: for i in range(len(data) // WORD_WIDTH + 1): sub_data = data[WORD_WIDTH * i: min(WORD_WIDTH * (i + 1), len(data) - 1)] if i == 0: mark = str(lineno) else: mark = '>' tr_tag = t_body << tr() sub_data = escape(sub_data) sub_data = sub_data.replace(" ", " ") sub_data = sub_data.replace("\t", " ") write_line(tr_tag, mark, sub_data) else: tr_tag = t_body << tr() data = escape(data) data = data.replace(" ", " ") data = data.replace("\t", " ") write_line(tr_tag, str(lineno), data) lineno += 1 html = page.render() o_f = codecs.open(ofile, 'w', encoding= 'utf-8') o_f.write(html) o_f.close()这是一个将任意文本文件转化为HTML文档的例子,主要是在调用txt2html函数,该函数有4个参数,页面的标题,展示文本内容的表格的标题,输入文件路径,输出文件路径同时做了一些简单的处理,对原文档中的每行进行标号,同时设置一行只显示100个字符多余的进行换行,以便阅读最终打开生成的HTML大致如下:![最终效果]](https://img.masimaro.top/pyh/1.jfif)在Python3环境下直接运行发现它报了一个错误:在Python2中存在Unicode字符串和普通字符串的区别,但是在Python3中所有字符串都默认是Unicode的,它取消了关于Python2中unicode函数,这里报错主要是这个原因,因此我们定位到报错的地方,将代码进行修改,去掉unicode函数(在Python2中unicode函数需要传入一个普通字符串,因此这里我们只需要去掉unicode函数,保留原来的参数即可,对于进行字符号转化的直接注释或者改为pass即可解决了unicode问题之后再次运行,又报了这样一个错误定位到对应代码处,在原来的代码位置有这么一段代码:def TagFactory(name): class f(Tag): tagname = name f.__name__ = name return f thisModule = modules[__name__] for t in tags: setattr(thisModule, t, TagFactory(t))从这段代码上可以知道,每当我们通过对应名称创建一个标签时,会在tags里面里面寻找到对应的标签,然后调用工厂方法生成一个对应的标签,这个工厂方法生成的其实是一个Tag对象,并且所有HTML标签都是这个Tag类,因此可以猜测如果要添加新的标签对象,那么可以通过修改tags里面的值,我们加入对应的标签值之后发现代码可以运行了,至此问题都解决了。其实这些错误都是Python2代码移植到python3环境下常见的错误,至于它的源码我没怎么看太明白,主要是它生成标签的这一块,我也不知道为什么修改了tags之后就可以运行了,python类厂的概念我还是不太明白,看来要花时间好好补一下基础内容了。
2018年03月09日
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日
3 阅读
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日
29 阅读
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-07
《疯狂的程序员》读后感
之前我在新年计划中说,争取新加一点读书笔记的内容,为博客赚点流量,所以这是我的第一篇读书笔记,为什么选这本书呢?可以说这本书给我详细介绍了程序员的工作,世界观,使我对计算机编程有了浓厚的兴趣,同时它也给我指明了后面努力的方向。初次读这本书是在我大二那年,由一个同学推荐,后来在毕业之后我在万能的某宝上找到了它的纸质版,又读了几遍,每次读都有很大的感触,所以我决定拿他作为我的第一篇读书笔记。感悟初次读这本书是我的一个大学同学推荐的,当时我正在大二,那是个时候我感觉到有一丝迷茫,有一丝压力,压力主要在将来就业上,当时我读的不是什么名牌大学,没有什么特别突出的能力,专业课也听得似懂非懂,那个时候总感觉以现在的状态坑定要失业。虽说想要学习点东西,不知道从何学起。那个时候学习的东西太多了,C/C++、Java、HTML、ASP.NET等等,很多东西都是浅尝辄止,一直感觉很无力,。我与几个朋友一起交流的时候,他给我说:“我给你推荐一本书吧,叫《疯狂的程序员》网上有电子版的,这本书是作者根据自身经历写,讲述了主人公从刚进入大学到最后工作再到后期创业的整个故事。里面详细介绍了主角的整个学习过程,主人公从大学一直学习汇编,然后到毕业实习、工作,逐渐过渡到VC++的种种,至此我似乎有了一丝学习的脉络,当时我给自己定下的目标是利用一个寒假好好补一下之前落下的汇编,然后再利用一个学期学习C/C++,当我把汇编学习完了之后我发现C语言里面的指针,数组,结构体,共用体什么的都不再那么神秘,从汇编角度上看只不过是内存的不同摆放方式,采用不同的寻址方式罢了,函数调用,各种传参等等就没有什么难度了,学完汇编再看C语言的种种语法就觉得是那么顺理成章。后来两年中,我基本上是按照这本书中主人公的学习脉络来的,先从汇编到Win32 汇编再到VC++,MFC编程,依照这个顺序,在自学的过程中再也没有遇到过之前那种离了书本就什么都不会的经历,这也给我后面学习带来很大的动力。以前我一直把程序员当做一个理工科的内容,一直认为它只是类似于水利工程师,建筑工程师之类的职业,但是我从这本书中读到一个观点“程序员的双手是魔术师的双手,他将枯燥的代码编程优美的可执行的软件”。原来程序员可以是一个艺术家,是类似于画家的,是可以写出优美的软件,从书中的字里行间,我读到作者作为一个程序员的骄傲,作者那种学习新技术,努力写出优美软件的那种执着。这些都使我对程序员的世界产生了浓厚的兴趣,根据这本书上的相关内容,我注册了一个CSDN,一个看雪的账号,经常在各大程序员论坛潜水,努力希望融入这个圈子,学习程序员文化。可以说这本书带给我关于程序员的启蒙教育,以前一直把程序员当做一个赚钱的行业,当时从那个时候起,我是真正喜欢上了这个职业,喜欢上这种文化。名言警句书中也有许多有意思的想法,时不时爆出一些名言警句什么的,下面我列举出我比较喜欢的一些句子:要么做第一个,要么做最好的一个。什么是鸡肋课?就是每个人都在自己课表的这门课旁边标注一个“可旷”或者“选修”。什么程序优化啊,都抵不上有钱,有钱就是最好的优化。说什么“效率就是金钱”,其实“金钱就是效率”。中国人和外国人很大不同就在于外国人总想热衷于第一时间把自己的新发现公布出来以此向广大人民群众展示自己的水平。中国人往往喜欢把自己的新发现阴在心里,自己偷偷垄断享用。唯恐被第二个人知道。公布出去,相关部门肯定马上解决。所谓好的病毒就是要:“持续时间特别长,波及范围特别广,破坏力特别大。”能进别人系统偷东西,那就是小偷;能进别人系统又不偷东西,那就是黑客。当你不是黑客的时候,总说:“我是个黑客”。当你真正成为黑客的时候,你往往会说:“我不是黑客”程序员是值得尊敬的,程序员的双手是魔术师的双手,他们把枯燥无味的代码变成了丰富多彩的软件……一个人静静的坐在电脑前写大卖的感觉,那是什么感觉?那就是武林高手闭关修炼的感觉。一本好书,就像高级武功秘籍一样,哪怕只从里面领悟个一招半式,功力提升起来都是惊人的,眉超风学的那半生不熟的九阴真经就是证明。所以练武功和写程序一样,不在乎你修炼了几十年还是几百年,也不在乎你少林武当娥眉拜了多少门派,关键是你有没有把一门武艺炼到出神入化的境界。学武艺,最忌讳总想着去追求大而全,你要明白,其实只需要“打狗棍法”这么一招,就足够你掌舵丐帮,受用终生。天下武功出少林,天下语言出汇编提升功力大低有两种方法,一是自己不断写代码,不断完善,不断把自己的代码写好,二是看牛人的代码。牛人的代码实在太高深了,一句看似平凡的语句,也许背后都蕴含着惊天地泣鬼神的智慧。现在你看不出什么端倪,等十年八年之后,你猛然醒悟:啊,原来大牛的代码竟是如此博大精深!对男人来说钱什么都不是,但是你没钱,你就失去了让她了解你的机会。对女人来说漂亮什么都不是,但是你不漂亮,你就失去了让他了解你的机会。人不怕被识破也不怕丢脸,怕就怕被当场识破和当众丢脸。。力的作用是相互的,你打别人有多疼,自己的手就有多疼。与其大家都疼,还不如最开始就不要下手打。每个人都期盼着便宜能光顾到自己,就算没有正儿八经地想,多多少少也偷偷的想过,但是有天便宜真的来了,可要小心了。写程序并不是一辈子都只是写代码。IT这一行是相当广博的,不管你是男的还是女的,不管你技术是初级、中级还是高级,你都能在这行中找到你自己合适的位置。如果你真的用心了,它带给你的会是一生的回报。男人的工资,和女人的年龄差不多。没钱的时候,最忌讳别人追问自己的工资待遇。可男人的工资,又和女人的年龄不一样。女人要是年轻,别人问她年龄,她自然敢大方地回答。男人的工资呢?要是高了,还是怕别人追问。“疯狂的程序员”绝对不是靠狂妄和拼命的程序员,而是能够脚踏实地、持续努力的程序员。一个程序员真正做到了这两点,技术上去之后,唯一能够限制他的只有想像力,到那个时候,才算“疯狂的程序员”,这种程序员啊,才能令竞争对手无比恐惧。技术其实还是我们最需要的东西,以前我们没有过硬的技术,所以疯狂地追求它。现在呢?有了一点技术,便觉得技术不那么重要。如果这样放任下去,等到我们失去技术的那一天,一定会后悔莫及的!最后说点题外话从当时懵懂的菜鸟到现在入行块两年的老菜鸟,目前仍然在学习Windows编程方面的东西,很多人都说现在PC端萎缩,学习PC端的编程没有出路,但是我自己不这么想,编程这个东西与具体的平台无关,与语言无关,编程靠的永远都是基础知识,数学和算法才是王道。但是算法和数学就只看理论是没有办法入门的,必须要实践,而实践的最好方法就是深入一个平台,一门语言不停的学习某个平台某一个语言的相关内容。在学习的途中会涉及到许多算法,操作系统,网络,数据库等等基础部分的知识,有的还可能会涉及到具体的硬件知识。比如Windows平台编程中的进程、线程、内存管理,想要学通这些没有操作系统的相关知识是不可能的,学习网络编程的时候需要网络知识,而数据库则需要数据库相关的知识,而且在Windows中采用的许多框架许多机制一般在其他平台也适用,比如Windows上的回调、消息机制、映射等等相关内容在其他平台其他语言基本上都有,现代操作系统,编程语言都是想通的,基本上是一通百通。因此在学习的过程中不要太纠结于平台和语言。随便选择一个平台,努力深入进去,在编程的同时学习一下基础理论的内容。不要被目前很火的大数据、人工智能云计算,等等东西蒙蔽了头脑,它们说到底也是对数学和算法在具体环境中的应用而已,只要有扎实的基础,想要跳到对应行业,只需要了解对应语言的语法,相应的接口而已。归结起来就一句话:数学和算法是王道。以上都是一个IT菜鸟的肤浅的理解而已,同意就点个赞,不同意也不要喷!!!!
2018年01月07日
7 阅读
0 评论
0 点赞
2018-01-06
Windows数据库编程接口简介
数据库是计算机中一种专门管理数据资源的系统,目前几乎所有软件都需要与数据库打交道(包括操作系统,比如Windows上的注册表其实也是一种数据库),有些软件更是以数据库为核心因此掌握数据库系统的使用方法以及数据库系统编程接口的使用方法是程序员非常重要的基本技能之一。所以我花了一定的时间学习了在Windows平台上使用COM接口的方式操作数据库。这段时间我会将自己学习过程中掌握的知识和其中的一些坑都发布出来,供个人参考,也方便他人学习
2018年01月06日
3 阅读
0 评论
0 点赞
1
...
20
21
22
...
32