OLEDB 枚举数据源

在之前的程序中,可以看到有这样一个功能,弹出一个对话框让用户选择需要连接的数据源,并输入用户名和密码,最后连接;而且在一些数据库管理软件中也提供这种功能——能够自己枚举出系统中存在的数据源,同时还可以枚举出能够连接的SQL Server数据库的实例。其实这个功能是OLEDB提供的高级功能之一。

枚举对象用于搜寻可用的数据源和其它的枚举对象(层次式),枚举出来的对象是一个树形结构。在程序中提供一个枚举对象就可以枚举里面的所有数据源,如果没有指定所使用的的上层枚举对象,则可以使用顶层枚举对象来枚举可用的OLEDB提供程序,其实我们使用枚举对象枚举数据源时它也是在注册表的对应位置进行搜索,所以我们可以直接利用操作注册表的方式来获取数据源对象,但是注册表中的信息过于复杂,而且系统对注册表的依赖比较严重,所以并不推荐使用这种方式。
枚举对象的原型如下:

1
2
3
4
5
6
7
CoType TEnumerator {
[mandatory] IParseDisplayName;
[mandatory] ISourcesRowset;
[optional] IDBInitialize;
[optional] IDBProperties;
[optional] ISupportErrorInfo;
}

顶层枚举对象的获取和遍历

要利用数据源枚举功能,第一个要获取的枚举对象就是顶层枚举对象。或者称之为根枚举器,根枚举器对象的CLSID是CLSID_OLEDB_ENUMNRATOR,顶层枚举对象可以使用标准的COM对象创建方式来创建,之后可以使用ISourceRowset对象的GetSourcesRowset,得到数据源组合成的结果集。接着可以根据行集中的行类型来判断是否是一个子枚举对象或者数据源对象。如果是子枚举对象,可以利用名字对象的方法创建一个新的子枚举对象,然后根据这个枚举对象来枚举其中的数据源对象。
一般来说这颗数结构只有两层。

OLEDB提供者结果集

在上面我们说可以根据结果集中的行类型来判断是否是一个子枚举对象或者数据源对象,那么怎么获取这个行类型呢?这里需要了解返回的行集的结构。

字段名称 类型 最大长度 含义描述
SOURCES_NAME DBTYPE_WSTR 128 枚举对象或数据源名称
SOURCES_PARSENAME DBTYPE_WSTR 128 可以传递给IParseDisplayName接口并得到一个moniker对象的字符串(枚举对象或数据源的moniker)
SOURCES_DESCRIPTION DBTYPE_WSTR 128 枚举对象或数据源的描述
SOURCES_TYPE DBTYPE_UI2 2(单位字节) 枚举对象或实例的类型,有下列值:
DBSOURCETYPE_BINDER (=4)- URL
DBSOURCETYPE_DATASOURCE_MDP (=3) - OLAP提供者
DBSOURCETYPE_DATASOURCE_TDP (=1) - 关系型或表格型数据源
DBSOURCETYPE_ENUMERATOR (=2) - 子枚举对象
SOURCES_ISPARENT DBTYPE_BOOL 2(单位字节) 是否是父枚举器

在枚举时根据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段,枚举数据源,枚举数据源中对应的数据库实例,以及根据选择的实例生成对应的数据源对象接口并测试连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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上供大家参考。
项目地址