OLEDB数据源


title: OLEDB数据源
date: 2018-01-12 21:42:37
tags: [OLEDB, 数据库编程, VC++, 数据库]
categories: windows 数据库编程
keywords: OLEDB, 数据库编程, VC++, 数据库


数据源在oledb中指数据提供者,这里可以简单的理解为数据库程序。数据源对象代表数据库的一个连接,是需要创建的第一个对象。而数据源对象主要用于配置数据库连接的相关属性如连接数据库的用户名密码等等

数据源主要完成的功能如下:

  1. 进行数据库身份认证
  2. 为每个连接准备对应的资源,如对应的数据缓冲,网络连接资源
  3. 设置连接属性,给访问者何种权限,设置连接的超时值等等,对象会根据对应的属性打开对应的接口。它的这些设置都是通过属性进行的

OLEDB属性与属性设置

OLEDB虽然是基于COM的一组接口,但是它与标准的COM接口有点不同,它的一大特色在于它自身的属性设置,有的接口虽然对象中存在但是调用QueryInterface是查询不出来的,只有设置相应的接口才会打开,有的接口可以根据属性值表现不同的行为。比如设置了对应的只读属性则不允许使用更新接口。
每个属性都有值、类型、说明和读写属性,对于行集对象,还有一个用于指示是否可以逐列应用它的指示器。
属性由一个GUID和一个整数ID进行唯一标识。
属性集是所有具有相同 组GUID 的一组属性。在逻辑上它们都用于同一种功能,比如有的属性集用于设置数据源连接属性,有的用于设置行集属性等等。它们是应用在同一个特定对象上的一组属性。在每个这样的属性组中都有属性每个属性属于一个或者多个属性组。
属性定义如下:

typedef struct tagDBPROP {
   DBPROPID        dwPropertyID; //属性GUID
   DBPROPOPTIONS   dwOptions; //属性的操作方式
   DBPROPSTATUS    dwStatus; //属性设置状态
   DBID            colid; //属性ID,一般给DB_NULLID
   VARIANT         vValue; //属性值
} DBPROP;

dwOptions:属性的操作方式有3种,但是一般只使用其中的两种:DBPROPOPTIONS_REQUIRED表示必须设置成功,如果设置失败,则设置属性的操作失败,DBPROPOPTIONS_OPTIONAL,表示可选,即即使该属性设置失败,设置属性的操作也返回成功。DBPROPOPTIONS_SETIFCHEAP表示如果在设置属性操作时在在dwStatus参数中返回该属性设置的状态,是否成功,失败的原因等等。
属性集的定义如下:

typedef struct tagDBPROPSET {
   DBPROP *   rgProperties; //属性数组的指针
   ULONG      cProperties; //属性数组中元素个数
   GUID       guidPropertySet; //属性集的GUID
} DBPROPSET;

目前属性组包括初始化属性组、数据源属性组、会话属性组、行集属性组、表属性组和列属性组等等。
设置属性一般包含如下几个步骤:

  1. 分配一个属性类型DBPRO的数组,一般倾向于多分配一个,最后一个数组元素全0,作为结尾
  2. 确定每个属性的属性GUID,即明确我们需要设置的是对象的哪个属性
  3. 填充对应的属性值,属性操作方式
  4. 填充对应的属性集DBPROPSET结构。设置该属性集的GUID
  5. 调用对应的接口设置属性

数据源对象接口

数据源对象的接口定义如下:

CoType TDataSource {
   [mandatory]   interface IDBCreateSession; //创建回话对象
   [mandatory]   interface IDBInitialize; //创建数据源连接对象
   [mandatory]   interface IDBProperties; ///创建数据源的属性操作对象
   [mandatory]   interface IPersist;
   [optional]    interface IConnectionPointContainer;
   [optional]    interface IDBAsynchStatus;
   [optional]    interface IDBDataSourceAdmin;
   [optional]    interface IDBInfo;
   [optional]    interface IObjectAccessControl;
   [optional]    interface IPersistFile;
   [optional]    interface ISecurityInfo;
   [optional]    interface ISupportErrorInfo;
   [optional]    interface ITrusteeAdmin;
   [optional]    interface ITrusteeGroupAdmin;
}

在上面代码中,mandatory表示是数据源必须提供的接口,optional表示的是可选性提供的接口,在创建对应的接口时尽量使用必须实现的接口,如果需要使用可选择的接口,一定要判断数据源是否支持。在数据源对象中最主要的还是前三个必须提供的接口

连接到数据库

连接到数据源一般使用IDBInitialize接口的Initialize方法,但是生成IDBInitialize接口有几种不同的方式,下面一一列举出来

直接创建IDBInitialize接口

这种方式一般调用CoCreateInstance函数创建,下面是具体的代码

#include <tchar.h>
#include <windows.h>
#include <strsafe.h>

#define COM_NO_WINDOWS_H    //如果已经包含了Windows.h或不使用其他Windows库函数时
#define OLEDBVER 0x0260     //MSDAC2.6版
#include <oledb.h>
#include <oledberr.h>

#define GRS_ALLOC(sz)       HeapAlloc(GetProcessHeap(),0,sz)
#define GRS_CALLOC(sz)      HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sz)
#define GRS_SAFEFREE(p)     if(NULL != p){HeapFree(GetProcessHeap(),0,p);p=NULL;}

#define GRS_USEPRINTF() TCHAR pBuf[1024] = {}
//定义输出宏
#define GRS_PRINTF(...) \
    GRS_USEPRINTF();\
    StringCchPrintf(pBuf,1024,__VA_ARGS__);\
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),pBuf,lstrlen(pBuf),NULL,NULL);

//安全释放,为了养成良好的编码习惯,特作此宏定义
#define GRS_SAFERELEASE(I)\
    if(NULL != (I))\
    {\
        (I)->Release();\
        (I)=NULL;\
    }
//检测上一步的操作是否成功
#define GRS_COM_CHECK(hr,...)\
    if(FAILED(hr))\
    {\
        GRS_PRINTF(__VA_ARGS__);\
        goto CLEAR_UP;\
    }

int _tmain(int argc, TCHAR* argv[])
{
    CoInitialize(NULL);
    //创建OLEDB init接口
    IDBInitialize *pDBInit = NULL;
    IDBProperties *pIDBProperties = NULL;
    //设置链接属性
    DBPROPSET dbPropset[1] = {0};
    DBPROP dbProps[5] = {0};
    CLSID clsid_MSDASQL = {0}; //sql server 的数据源对象
    
    HRESULT hRes = CLSIDFromProgID(_T("SQLOLEDB"), &clsid_MSDASQL);
    GRS_COM_CHECK(hRes, _T("获取SQLOLEDB的CLSID失败,错误码:0x%08x\n"), hRes);
    hRes = CoCreateInstance(clsid_MSDASQL, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize,(void**)&pDBInit);
    GRS_COM_CHECK(hRes, _T("无法创建IDBInitialize接口,错误码:0x%08x\n"), hRes);

    //指定数据库实例名,这里使用了别名local,指定本地默认实例
    dbProps[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
    dbProps[0].dwOptions = DBPROPOPTIONS_REQUIRED;
    dbProps[0].vValue.vt = VT_BSTR;
    dbProps[0].vValue.bstrVal = SysAllocString(OLESTR("LIU-PC\\SQLEXPRESS"));
    dbProps[0].colid = DB_NULLID;

    //指定数据库库名
    dbProps[1].dwPropertyID = DBPROP_INIT_CATALOG;
    dbProps[1].dwOptions = DBPROPOPTIONS_REQUIRED;
    dbProps[1].vValue.vt = VT_BSTR;
    dbProps[1].vValue.bstrVal = SysAllocString(OLESTR("Study"));
    dbProps[1].colid = DB_NULLID;

    //指定链接数据库的用户名
    dbProps[2].dwPropertyID = DBPROP_AUTH_USERID;
    dbProps[2].vValue.vt = VT_BSTR;
    dbProps[2].vValue.bstrVal = SysAllocString(OLESTR("sa"));
    
    //指定链接数据库的用户密码
    dbProps[3].dwPropertyID = DBPROP_AUTH_PASSWORD;
    dbProps[3].vValue.vt = VT_BSTR;
    dbProps[3].vValue.bstrVal = SysAllocString(OLESTR("123456"));
    
    
    //设置属性
    hRes = pDBInit->QueryInterface(IID_IDBProperties, (void**)&pIDBProperties);
    GRS_COM_CHECK(hRes, _T("查询IDBProperties接口失败, 错误码:%08x\n"), hRes);
    dbPropset->guidPropertySet = DBPROPSET_DBINIT;
    dbPropset[0].cProperties = 4;
    dbPropset[0].rgProperties = dbProps;
    hRes = pIDBProperties->SetProperties(1, dbPropset);
    GRS_COM_CHECK(hRes, _T("设置属性失败, 错误码:%08x\n"), hRes);

    //链接数据库
    hRes = pDBInit->Initialize();
    GRS_COM_CHECK(hRes, _T("链接数据库失败:错误码:%08x\n"), hRes);
    //do something
    pDBInit->Uninitialize();

    GRS_PRINTF(_T("数据库操作成功!!!!!\n"));
CLEAR_UP:
    GRS_SAFEFREE(pDBInit);
    GRS_SAFEFREE(pIDBProperties);
    CoUninitialize();
    return 0;
}

这是一份完整的可执行代码,后续的部分对于重复的代码将不再给出。
在上述代码中我们首先根据字符串SQLOLEDB查找到SQL Server对应的数据源对象,然后根据数据源对象查询出IDBProperties对象,接着分配一些空间来设置属性和属性集,调用IDBProperties接口的SetProperties函数来设置对应的数据源对象的接口。最后调用IDBInitialize接口的Initialize链接数据源,调用Uninitialize函数来断开连接。
一般数据源对象的属性集合的GUID为DBPROPSET_DBINIT,下面包含的属性最主要的有:

  1. DBPROP_INIT_DATASOURCE:数据连接实例(具体的DBMS实例名)
  2. DBPROP_INIT_CATALOG:目录名(在SQL Server中对应的是具体的数据库名称,对于ORACLE来说没有意义)
  3. DBPROP_AUTH_USERID: 用户名
  4. DBPROP_AUTH_PASSWORD: 密码

我们也注意到上面调用SysAllocString的BSTR类型的字符串并没有调用对应的函数进行释放,会不会发生内存泄露?其实不用担心OLEDB在断开连接的时候已经帮助我们释放了这部分空间。

使用IDBPromptInitialize接口来创建数据源对象

上述方法是依托于标准的COM,虽然也成功创建的数据源连接,但是无法在标准的com之上进行更多的初始化操作,导致了有些特定的高级功能无法使用,所以在实践中常用的还是利用IDBPromptInitialize和IDataInitialize的方式比较多。
IDBPromptInitialize创建时会弹出一个数据源选择的对话框,供用户选择相关配置信息(数据源/用户名/密码等)然后根据这些配置自动生成连接对象。
下面看一个弹出数据源对话框的例子:

void ConnectSQLServerByDialog() //通过弹出对话框来链接SQL SERVER数据库
{
    DECLARE_BUFFER();
    DECLARE_OLEDB_INTERFACE(IDBPromptInitialize);
    DECLARE_OLEDB_INTERFACE(IDBInitialize);

    HWND hDesktop = GetDesktopWindow();
    HRESULT hRes = CoCreateInstance(CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER, IID_IDBPromptInitialize, (void**)&pIDBPromptInitialize);
    COM_CHECK_SUCCESS(hRes, _T("创建IDBPromptInitialize接口失败: %08x"), hRes);
    //调用该函数弹出数据源对话框
    hRes = pIDBPromptInitialize->PromptDataSource(NULL, hDesktop, DBPROMPTOPTIONS_PROPERTYSHEET, 0, NULL, NULL, IID_IDBInitialize, (IUnknown**)&pIDBInitialize);
    COM_CHECK_SUCCESS(hRes, _T("弹出数据源对话框失败:%08x\n"), hRes);

    hRes = pIDBInitialize->Initialize();
    COM_CHECK_SUCCESS(hRes, _T("链接数据库失败:%08x\n"), hRes);
    COM_PRINTF(_T("链接数据库成功\n"));

    hRes = pIDBInitialize->Uninitialize();
__CLEAN_UP:
    SAFE_RELEASE(pIDBPromptInitialize);
    SAFE_RELEASE(pIDBInitialize);
}

除了这种方式,他还可以直接创建出IDBInitialize接口,利用之前设置属性的方式来连接到数据库,下面是一个演示的例子:

    HRESULT hRes = CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER, IID_IDataInitialize, (void**)&pIDataInitialize);
    COM_CHECK_SUCCESS(hRes, _T("创建接口IDBInitialize失败:%08x\n"), hRes);
    hRes = CLSIDFromProgID(_T("SQLOLEDB"), &clsid);
    COM_CHECK_SUCCESS(hRes, _T("查询SQLOLEDB CLSID 失败:%08x\n"), hRes);
    hRes = pIDataInitialize->CreateDBInstance(clsid, NULL,
        CLSCTX_INPROC_SERVER, NULL, IID_IDBInitialize,
        (IUnknown**)&pIDBInitialize);
    COM_CHECK_SUCCESS(hRes, _T("创建IDBInitialize接口失败:%08x\n"), hRes);

    //后续的代码就是我们之前写的那段定义属性,设置属性,连接数据库的代码

使用IDataInitialize接口来创建数据源对象

使用IDataInitialize接口可以直接使用连接字串连接到数据库,下面是使用连接字串的例子:

void ConnectSQLServerByConnstr() //通过连接字符串连接数据库
{
    DECLARE_OLEDB_INTERFACE(IDataInitialize);
    DECLARE_OLEDB_INTERFACE(IDBInitialize);
    DECLARE_BUFFER();
    HRESULT hRes = CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER, IID_IDataInitialize, (void**)&pIDataInitialize);
    COM_CHECK_SUCCESS(hRes, _T("创建IDataInitialize接口失败:%08x!\n"), hRes);

    hRes = pIDataInitialize->GetDataSource(NULL, CLSCTX_INPROC_SERVER, 
        OLESTR("Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Password = 123456;Initial Catalog=Study;Data Source=LIU-PC\\SQLEXPRESS;"), 
        IID_IDBInitialize, (IUnknown**)&pIDBInitialize);
    COM_CHECK_SUCCESS(hRes, _T("获取IDBInitialize接口失败:%08x!\n"), hRes);
    hRes = pIDBInitialize->Initialize();
    COM_CHECK_SUCCESS(hRes, _T("连接数据库失败:%08x!\n"), hRes);
    COM_PRINTF(_T("连接数据库成功\n"));
    pIDBInitialize->Uninitialize();
__CLEAN_UP:
    SAFE_RELEASE(pIDataInitialize);
    SAFE_RELEASE(pIDBInitialize);
}

获取连接字串

其实除了上面这种直接创建IDataInitialize接口的方法外,还可以使用IDBPromptInitialize接口Query出一个IDataInitialize接口,然后再设置连接字串连接到数据库。
其实在OLEDB中,可以认为连接字串最终被翻译为对应的属性,也就是说OLEDDB保存着对应连接的属性,我们可以通过不同的方式来获取不同类型的属性,比如使用IDBProperties接口来获取对应的链接属性,或者使用IDataInitialize的GetInitializationString函数来获取连接的链接字串。
既然它保存着每个连接的对应属性,那么是不是可以将用户在数据源对话框上的操作最终保存为数据连接字串呢,答案是肯定的。实现的思路如下:

  1. 调用IDBPromptInitialize接口的PromptDataSourc方法弹出数据源对话框,让用户操作
  2. 根据IDBPromptInitialize接口Query出IDataInitialize接口
  3. 调用IDataInitialize接口的GetInitializationString来获取连接字串
    下面是具体实现的代码:
void GetConnectString()
{
    DECLARE_OLEDB_INTERFACE(IDBPromptInitialize);
    DECLARE_OLEDB_INTERFACE(IDataInitialize);
    DECLARE_OLEDB_INTERFACE(IDBInitialize);
    DECLARE_BUFFER();
    
    LPOLESTR pConnStr = NULL;
    HWND hDeskTop = GetDesktopWindow();
    HRESULT hRes = CoCreateInstance(CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER, IID_IDBPromptInitialize, (void**)&pIDBPromptInitialize);
    COM_CHECK_SUCCESS(hRes, _T("创建IDBPromptInitialize接口失败:%08x!\n"), hRes);
    hRes = pIDBPromptInitialize->PromptDataSource(NULL, hDeskTop, DBPROMPTOPTIONS_PROPERTYSHEET, 0, NULL, NULL, IID_IDBInitialize, (IUnknown**)&pIDBInitialize);
    COM_CHECK_SUCCESS(hRes, _T("弹出数据源对话框失败:%08x\n"), hRes);


    hRes= pIDBPromptInitialize->QueryInterface(IID_IDataInitialize, (void**)&pIDataInitialize);
    COM_CHECK_SUCCESS(hRes, _T("创建IDataInitialize接口失败:%08x!\n"), hRes);

    hRes = pIDataInitialize->GetInitializationString(pIDBInitialize, TRUE, &pConnStr);
    COM_CHECK_SUCCESS(hRes, _T("获取连接字串失败失败:%08x\n"), hRes);

    COM_PRINTF(_T("连接字符串:%s"), pConnStr);
    SysAllocString(pConnStr);
__CLEAN_UP:
    SAFE_RELEASE(pIDataInitialize);
    SAFE_RELEASE(pIDBInitialize);
    SAFE_RELEASE(pIDBPromptInitialize);
}

为了节约篇幅,这些笔记内容只会列举部分关键的代码,至完整的代码我会随着博客内容的进度慢慢上传到GitHub项目中,并在博文的最末尾给出对应文件的地址
本次代码地址1
本次代码地址2

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容