zoukankan      html  css  js  c++  java
  • VC++/MFC学习笔记(五)

     ADO技术介绍
    ADO是Microsoft最新和最强大的数据访问接口OLE DB而设计的,是一个便于使用的应用程序层。OLE DB为任何数据源都提供了高性能的访问,这些数据源包括关系和非关系数据库、电子邮件、文件系统、文本和图形以及自定义业务对象等。ADO在关键的Internet方案中使用最少的网络流量,并且在前端和数据源之间使用最少的层数,所有这些都是为了提供轻量、高性能的接口。同时ADO使用与DAO相似的约定和特性,使得它更易于学习。这里先介绍一下ADO技术的历史和ADO访问数据源的特点,然后介绍访问方法。

    1.ADO的历史回顾
    ADO共发布了1.0、1.5和2.0三个版本。
    第一个版本1.0是RDO的一个功能子集,它的目标是为了帮助开发人员在IIS(Internet Informatioin Server)上建立ASP(Active Server Pages)应用。
    第二个版本1.5是随IIS4.0和Internet Explorer4.0一起发布的。它也被包含在MDAC(Microsoft Data Access Components)里。从这个版本开始,ADO成为了功能和运作效率上都高出RDO和DAO一筹的数据库界面。
    最新的版本2.0加入了别的数据库客户技术从没有的新技术。ADO2.0实际上是基于MSADO15.DLL这个动态链接库的,这个库文件的名字虽然和ADO1.5的一样,但是它实现了更新的接口。ADO2.0的新技术有:
    1)异步操作和事件模型。
    2)数据集的持续性。
    3)层次化的数据运输。
    2.ADO特点概述
    用ADO访问数据源的特点可概况如下:
    第一,易于使用,可以说这是ADO最重要的特点之一。由于ADO是最高成数据库访问技术,所以相对于ODBC来说,具有面向对象的特点。同时,在ADO对象结构中,对象与对象之间的层次结构不是非常明显,这会给编写数据库程序带来很多便利,比如,在应用程序中如果要使用记录集对象,就不一定要先建立连接、会话对象,如果需要就可以直接构造记录集对象。总之,已经没有必要去关心对象的构造层次和构造顺序了。
    第二,可以访问多种数据源。这一点和OLE DB一样的,使应用程序具有很好的通用性和灵活性。
    第三,访问数据源效率高。这是由于ADO本身就是基于OLE DB的接口,自然具有OLD DB的特点。
    第四,方便地Web应用。ADO可以以ActiveX控件的形式出现,这就大大方便了Web应用程序的编制。
    第五,技术编程接口丰富。ADO支持Visual C++、Visual Basic、Visual J++以及VBScript和JavaScript等脚本语言。
    3.ADO的对象
    ADO包含了连接对象(Connection)、命令对象(Command)、记录集对象(RecordSet)、字段对象(Field)、参数对象(Parameter)、错误对象(Error)、属性对象(Property)和集合与事件等。
    1)连接(Connection)
    用于表示和数据源的连接,以及处理一些命令和事务。通过它可以从应用程序访问数据源,是交换数据必需的环境。
    2)命令(Command)
    用于执行某些命令来进行诸如查询、修改数据库结构的操作。
    3)记录集(RecordSet)
    用于和处理数据源的表格表,它是在表中修改、检索数据的最主要的方法。
    4)字段(Field)
    用于描述数据集中的列信息。
    5)参数(Parameter)
    用于对传递给数据源的命令赋参数值。
    6)错误(Error)
    用于承载说产生错误的详细信息。
    7)属性(Property)
    通过属性,每个ADO对象借此来让用户描述和控制自身的行为。
    8)集合(Set)
    集合是一种可方便地包含其他特殊类型对象的对象类型。ADO提供了4种类型的集合。
    a、Connention对象具有Errors集合
    b、Command对象具有Parameters集合
    c、Recordset对象具有Fields集合
    d、Connection、Command、Recordset、Field对象都具有Properties集合
    9)事件(Event)
    事件模型是异步操作的基础,这是ADO2.0引进的新特性。
    4.在Visual C++中使用ADO
    为在Visual C++中使用ADO,需要在头文件中加入下面几行代码:
    #import "C:/Program Files/Common Files/system/ado/msado15.dll" no_namespace rename("EOF","adoEOF")
    #include <adoid.h>
    #include <adoint.h>
    #include "icrsint.h"
    第一行的#import语句告诉编译器把此指令中的动态连接库文件引入到程序中,并从库中抽取其中的对象的类的信息,并产生出两个头文件包含在工程中。其中的no_namespace用来对动态链接库的名称域进行隔离。最后的rename,把ADO中的EOF重新命名,避免和其他地方定义的EOF产生冲突。
    第二、第三行引入的头文件定义了ADO2.0类和接口标识。
    第四行引入了ADO2.0数据绑定扩展。
    5.到数据源的连接
    Connection对象用来实现和数据源的连接。创建一个Connection对象非常简单,只需声明一个_ConnectionPtr型的指针,调用它的CreateInstance方法即可(_ConnectionPtr是所谓的智能指针,包装了一个Connection接口指针,具体技术细节可以参考有关的COM书籍)。
    _ConnectionPtr pConn=NULL;
    try
    {
        //创建Connection对象实例
        pConn.CreateInstance(__uuidof(Connection));
        _bstr_t conn="DSN=SomeDS;UID=sa;PWD=123";
        //打开连接
        pConn->Open(conn,"","",-1);
        //执行一条SQL命令
        _bstr_t sql="DELETE * FROM some_table WHERE id='111'";
        pConn->Close();
    }
    catch(_com_error &e)
    {
        AfxMessageBox(e.ErrorMessage());
    }
    上边的代码演示了用Connection对象建立连接,执行一条SQL语句后关闭连接的全过程。
    Open函数的原型如下:
    HRESULT Open(_bstr_t ConnectionString,
        _bstr_t UID,
        _bstr_T PSWD,
        long option);
    各个参数的意义为:
    a、ConnectionString:包含连接信息的字符串
    b、UID:访问数据库的用户名。
    c、PSWD:访问数据库的口令。
    d、option:可选参数。
    在用Open函数建立连接之前,还可以先设置一些Connection对象的属性。其中用的有ConnectionTimeOut属性和Mode属性。前者用来设置建立连接时的等待时间,后者用来设置数据库的打开模式。打开模式可以是下列值的组合。
    a、adModeRead:读模式。
    b、adModeWrite:写模式。
    c、adModeReadWrite:读写模式。
    d、adModeShareDenyNone:防止用户用任何权限打开连接。
    e、adModeShareDenyRead:防止用户以读权限打开连接。
    f、adModeShareDenyWrite:防止用户以写权限打开连接。
    g、adModeShareExclusive:防止其他用户打开连接。
    h、adModeUnknown:未知模式。

    6.使用记录集
    记录集对象是ADO中最常用的对象,以后下面将详细地介绍它的功能。
    首先是Recordset对象的创建函数Open,它的原型如下:
    HRESULT Open(const_variant_t &source,
        const_variant & connection,
        enum CursorTypeenum cursorType,
        enum LockTypeEnum lockType,
        long options);
    对其参数解释如下:
    source参数是一个变体类型,它可以是一个Command对象、一个SQL语句、一个表名或一个存储过程,甚至可以是一个URL、一个文件名、一个流对象。
    connection也是一个变体类型,它可以是一个Connection对象,也可以是一个指明连接目标的字符串。
    cursorType指明了数据集游标的类型。它可以是下列几个值:adOpenDynamic,adOpenForwardOnly,adOpenKeyset,adOpenStatic,adOpenUnspecified。
    lockType参数可以是下列几个值之一:adLockBatchOptimistic,adLockOptimistic,adLockPessimistic,adLockReadOnly,adLockUnspecified。
    options参数指明了第一个参数source的类型,其值可以是adCmdUnspecified,adCmdText,adCmdTable,adCmdStoredProc,adCmdUnkown,adCmdFile,adCmdTableDirect。
    Recordset最常用的3个方法涉及到数据库的增加、更改和删除操作。下面的代码解释了他们的用法。
    删除函数Delete的用法。
    HRESULT hr;
    _bstr_t bstrQuery("SELECT * FROM Products WHERE PartNumber='8TRACK-003'");
    _variant_t vNull;
    vNull.vt=VT_ERROR;
    vNull.scode=DISP_E_PARAMNOTFOUND;
    try
    {
        hr=pRecordSet.CreateInstance(__uuidof(Recordset));
        if(SUCCEEDED(hr))
        {
            //这里假设Connection对象pConnection已经初始化。
            pRecordSet->PutRefActiveConnectin(pConnection);
            hr=pRecordSet->Open(_variant_t(bstrQuery),vNUll,
                adOpenForwardOnly,adLockOptimistic,adCmdText);
            if(!pRecordSet->GetadoEOF())
            {
                //删除当前游标所指的记录
                pRecordSet->Delete(adAffectCurrent);
                pRecordSet->Close();
            }
        }
    }
    catch(_com_error &e)
    {
        //异常处理部分,省略
    }
    下面的代码演示了修改函数Update的用法。
    _RecordsetPtr pRcordSet;
    HRESULT hr;
    _bstr_t bstrQuery("SELELCT * FROM Products WHERE PartNumber='8TRACK-003'");
    _variant_t vNull;
    vNull.vt=VT_ERROR;
    vNull.scode=DISP_E_PARAMNOTFOUND;
    try
    {
        hr=pRecordSet.CreateInstance(__uuidof(Recordset));
        if(SUCCEEDED(hr))
        {
            //这里假设Connection对象pConnection已经初始化。
            pRecordSet->PutRefActiveConnectin(pConnection);
            hr=pRecordSet->Open(_variant_t(bstrQuery),vNUll,
                adOpenForwardOnly,adLockOptimistic,adCmdText);
            if(!pRecordSet->GetadoEOF())
            {
                pRecordSet->PutCollect(L"ProductName",L"Bell Botooms and Bass Guitar");
                //执行更新操作
                pRecordSet->Update(vNull,vNull);
                pRecordSet->Close();
            }
        }
    }
    catch(_com_error &e)
    {
        //异常处理部分,省略
    }
    下面的代码演示了AddNew函数的用法。
    HRESULT hr;
    _bstr_t bstrQuery("SELELCT * FROM Products WHERE PartNumber= IS NULL");
    _variant_t vNull;
    vNull.vt=VT_ERROR;
    vNull.scode=DISP_E_PARAMNOTFOUND;
    try
    {
        hr=pRecordSet.CreateInstance(__uuidof(Recordset));
        if(SUCCEEDED(hr))
        {
            //这里假设Connection对象pConnection已经初始化。
            pRecordSet->PutRefActiveConnectin(pDoc->pConnection);
            hr=pRecordSet->Open(_variant_t(bstrQuery),vNUll,
                adOpenForwardOnly,adLockOptimistic,adCmdText);

            if(SUCCEEDED(h))
            {
                //创建一个记录字段信息的数组
                COleSafeArray vaFieldlist;
                vaFieldlist.CreateOneDim(VT_VARIANT,3);
                long lArrayIndex[1];
                lArrayIndex[0]=0;
                vaFieldlist.PutElement(lArrayIndex,&(_variatn_t("PartNumber")));
                lArrayIndex[0]=1;
                vaFieldlist.PutElement(lArrayIndex,&(_variatn_t("ProductName")));
                lArrayIndex[0]=2;
                vaFieldlist.PutElement(lArrayIndex,&(_variatn_t("Price")));
                //创建一个保存字段值的数组
                COleSafeArry vaValuelist;
                vaValuelist.CreateOneDim(VT_VARIANT,3);
                lArrayIndex[0]=0;
                vaValuelist.PutElement(lArrayIndex,&(_variatn_t("8TRACK-003")));
                lArrayIndex[0]=1;
                vaValuelist.PutElement(lArrayIndex,&(_variatn_t("Bell Botoom Hits")));
                lArrayIndex[0]=2;
                vaValuelist.PutElement(lArrayIndex,&(_variatn_t((float)19.95)));
                //执行添加操作
                pRecordSet->AddNew(vaFieldlist,vaValuelist);
                pRecordSet->Close();
            }
        }
    }
    catch(_com_error &e)
    {
        //异常处理部分,省略
    }
    从上面的示例代码看出,AddNew和Update方法的参数都是两个变体类型的数组。这种参数在编程的时候比较麻烦,需要编写从VARIANT类型到C++类型的转换代码。而且,使用VARIANT类型检索C/C++数据的过程也有损性能。ADO For VC++ Extensions引入了数据绑定技术,解决了上述的问题。它能把数据直接绑定到C++类型上;通过提供简化接口使用的宏,使它成为一个灵活、易用、高效的工具。
    为了绑定一个数据库里的表格到一个C++对象,要求这个C++对象必须继承CADORecordBinding类。通过宏,使我们能很容易地构造出一个这样的对象。例子代码如下:
    class CCPRs : public CADORecordBinding
    {
        BEGIN_ADO_BINDING(CCPRs)
            ADO_VARIABLE_LENGTH_ENTRY2(1,adVarChar,m_sz_no,
                sizeof(m_sz_no),m_sts_no,TRUE)
            ADO_VARIABLE_LENGTH_ENTRY2(2,adVarChar,m_sz_name,
                sizeof(m_sz_name),m_sts_name,TRUE)
            ADO_VARIABLE_LENGTH_ENTRY2(3,adVarChar,m_sz_kind,
                sizeof(m_sz_kind),m_sts_kind,TRUE)
            ADO_VARIABLE_LENGTH_ENTRY2(4,adSingle,m_f_price,
                sizeof(m_f_price),m_sts_price,TRUE)
            ADO_VARIABLE_LENGTH_ENTRY2(5,adVarChar,m_sz_detail,
                sizeof(m_sz_detail),m_sts_detail,TRUE)
        END_ADO_BINGDING()
    public:
        ULONG m_sts_no;
        ULONG m_sts_name;
        ULONG m_sts_kind;
        ULONG m_sts_price;
        ULONG m_sts_detail;

        CHAR m_sz_no[10];
        CHAR m_sz_name[20];
        CHAR m_sz_kind[202];
        float m_f_price;
        CHAR m_sz_detail[50];
    };
    由于宏的帮助,这种类的定义是相当简单而清晰的。数据绑定写在BEGIN_ADO_BINGDING和END_ADO_BINDING之间。
    ADO_VARIABLE_LENGTH_ENTRY2宏的第一个参数指明了字段在表中的顺序,该顺序不能填错,否则运行时会出现错误。这个宏的第二个参数指明了说绑定的字段的类型。有趣的是,adVarChar可以应付大部分类型,哪怕是日期或整数。浮点数这样的类型。当然,为了提高效率和方便编程,还是设置成正确的类型为好。第四个参数用来刻画字段的状态--对每一个字段,还要定义与之对应的参整数变量来记录此字段的状态。最后一个参数指明了此字段是否可写,如果是FALSE,说明是只读的字段。
    下面的例子演示了使用这种方法读取表格,并把其中的price字段加倍保存回数据库中。其中数据绑定的部分见上例。
    _RecordsetPtr pRst=NULL;
    IADORecordBinding *picRs=NULL;
    CCPRs rs;

    try
    {
        _bstr_t strSQL="SELECT * FROM CP";

        TESTHR(pRst.CreateInstrance(__uuidof(Recordset)));
        pRst=m_DBCnt->Execute(strSQL,NULL,adCmdText);

        TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),(LPVOID*)&picRs));
        TESTHR(picRs->BindToRecordset(&rs));

        while(!pRst->adoEOF)
        {
            //打印出读取的记录
            printf("%s/n",rs.m_sz_no);
            printf("%s/n",rs.m_sz_name);
            printf("%s/n",rs.m_sz_kind);
            printf("%f/n",rs.m_sz_price);
            printf("%s/n",rs.m_sz_detail);
            //价格翻倍后保存
            rs.m_f_price*=2;
            picRs->Update(&rs);
            //游标移动到下一个记录
            pRst->MoveNext();
        }
        picRs->Release();
        pRst->Close();
    }
    catch(_com_err &e)
    {
        AfxMessageBox(e.ErrorMessage());
        return;
    }

    7.使用命令
    在ADO中,可以用Connection对象的Execute()方法执行命令,也可以用Command对象。
    第一条路径相对简单。Execute函数的原型如下:
    _RecordsetPtr Execute(_bstr_t cmd,
        VARIANT* rcds,
        long optn);
    第一个参数cmd用一个字符串作为参数,第三个参数说明了cmd的类型:
    adCmdText:cmd是一个SQL命令。
    adCmdTable:cmd指明了一个表名。
    adCmdStoredProc:cmd指明了一个存储过程。
    Execute返回的结果是一个Recordset指针。下面是一段Execute的简单例子:
    _bstr_t sql="SELECT id FROM tb1 WHERE id='Tom' AND pswd='111'";
    _RecordsetPtr pRst;
    pRst=pDBCnt->Execute(sql,NULL,adCmdText);
    if(!pRst->adoEOF)
            Message("登录成功! ");
    第二条路径使用的Command对象相对复杂,需要设置很多属性和参数,因而功能也更为强大。这里通过一段示例来解释它的用法。
    HRESULT hr=S_OK;
    //定义字符串常量
    _bstr_t strSQLChange("UPDATE Titles SET Type= "
        "'self_help' WHERE Type='psychology'");
    _bstr_t strSQLChange("UPDATE Titles SET Type= "
        "'psychology' WHERE Type='self_help'");
    _bstr_t strCnn("Provider=sqloledb;Data Source=MyServer; "
        "Initial Catalog=pubs;User Id=sa;Password=;");

    //定义ADO对象
    _ConnectioinPtr pConnection=NULL;
    _CommandPtr pCmdChange=NULL;
    _RecordsetPtr pRstTitle=NULL;
    try
    {
        //建立连接
        TESTHR(pConnection.CreateInstance(__uuidof(Connection)));
        pConnection->Open(strCnn,"","",adConnectionUnspecified);

        //创建命令对象
        TESTHR(pCmdChange.CreateInstance(__uuidof(Command)));
        pCmdChange->ActiveConnection=pConnection;
        pCmdChange->CommandText=strSQLChagne;

        //打开titles表
        TESTHR(pRstTitles.CreateInstance(__uuidof(Recordset)));
        pRstTitles->Open("Title",_variant_t((IDispatch*)pConnection,
            true),adOpenStatic,adLockOptimistic,adCmdTable);

        //打印出原有的数据
        printf("/n/nData in Titles table before executing the query:/n");

        //这里省略这个函数的实现
        PrintOutput(pRstTitles);

        //清除原有错误记录
        pConnection->Errors->Clear();

        //执行命令
        pCmdChange->Execute(NULL,NULL,adCmdText);
        pRstTitles->Requery(adCmdUnknown);

        //输出新数据
        printf("/n/n/tData in Titles table after executing the query:/n");
        PrintOutput(pRstTitles);

        //恢复记录数据
        pConnection->Execute(strSQLRestore,NULL,adExecuteNoRecords);
        pRstTitles->Requery(adCmdUnknown);
        printf("/n/n/tData after exec. query to restore original info:/n");
        PrintOutput(pRstTitles);

        //结束程序,释放对象
        pRstTitles->Close();
        pConnection->Close();
    }
    catch(_com_error &e)
    {
        //异常处理部分,省略
    }

    8.使用事务
    在数据库中,事务的概念可以把多个操作作为单一的最基本的活动来进行。例如,在一个银行数据库中,需要从一个账户取钱汇入另一个账户中。这里涉及了至少两个操作:减少A的账户,增加B的账户。如果第一个操作顺利完成而第二个操作不幸失败的化,数据库数据的完整性将受到破坏--操作前后银行里的钱的总数会变少,说这里就要用到ADO中事务处理的概念。在所有操作开始之前调用Connection对象的Begintrans方法来开始一个事务:
    pDBCnt->BeginTrans();
    在所有操作成功之后,调用Connection对象的CommitTrans方法提交此事务,这时数据库的内容菜作实质新的改变:
    pDBCnt->CommitTrans();
    如果操作中途出现异常,则在异常处理中使用RollBackTrans取消这次事务,数据库将回到BeginTrans之前的状态。
    catch(_com_error &e)
    {
        AfxMessageBox(e.ErrorMessage());
        pDBCnt->RollBackTrans();
    }
    9.使用ADO事件
    ADO从2.0版本开始加入了对事件的支持。ADO事件是由某些操作在开始之前或结束之后发出的通知;所谓通知,实质上是对预定义的时间处理回调函数的调用。
    ADO事件分两类:ConnectionEvent和RecordsetEvent。前者出现在连接(Connection)打开、切断,事务(Transaction)开始、提交或命令(Command)被执行等与Connection对象有关的操作处;后者则出现在与记录集(Recordset)对象有关的操作处,如在记录集中定位,修改记录、删除记录集行等。
    若按时间性质来分,ADO事件又可以分为Will事件、Complete事件和其他事件3类。顾名思义,Will事件发生在某个操作之前,Complete 事件发生在某个操作完成之后。
    表1和表2是一些典型的ADO事件
    表1:                                    ConnectionEvent
    ---------------------------------------------------------------------------------------------
            连接事件(ConnectionEvent)                |                说明
    ---------------------------------------------------------------------------------------------
    BeginTransComplete                                |事务管理有关的事件
    CommitTransComplete                                |
    RollbackTransComplete                            |
    ---------------------------------------------------------------------------------------------
    WillConnect                                        |连接相关的事件
    ConnectionComplete                                |
    Disconnect                                        |
    ---------------------------------------------------------------------------------------------
    WillExecute                                        |命令相关的事件
    ExecuteComplete                                    |
    ---------------------------------------------------------------------------------------------
    InfoMessage                                        |关于当前操作有附加信息的通知
    ---------------------------------------------------------------------------------------------


    表2:                                    RecordsetEvent
    ---------------------------------------------------------------------------------------------
            记录集事件(RecordsetEvent)            |                说明
    ---------------------------------------------------------------------------------------------
    FetchProgress                                    |数据检索相关的事件
    FetchComplete                                    |
    ---------------------------------------------------------------------------------------------
    WillChangeField                                    |字段更改相关的事件
    FieldChangeComplete                                |
    ---------------------------------------------------------------------------------------------
    WillMove                                        |定位操作相关的事件
    MoveComplete                                    |
    EndOfRecordset                                    |
    ---------------------------------------------------------------------------------------------
    WillChangeRecord                                |行更改相关的事件
    RecordChangeComplete                            |
    ---------------------------------------------------------------------------------------------
    WillChangeRecordset                                |数据集更改相关的事件
    RecordsetChangeComplete                            |
    ---------------------------------------------------------------------------------------------


    下面结合一个历程的片段来说明如何在应用程序中使用ADO事件。
    //从ConnectionEventsVt派生出一个连接事件类的对象
    class CConnEvent:public ConnectionEventVt
    {
    private:
        //接口引用计数
        ULONG m_cRef;
    public:
        CConnEvent()    {m_cRef=0;};
        ~CConnEvent() {};
        //实现IUnknown的三个标准方法
        STDMETHODIMP QueryInterface(REFIID riid,void**ppv);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
        //处理InfoMessage事件的回调函数
        STDMETHODIMP raw_InfoMessage(
            struct Error*pError;
            EventStatusEnum * adStatus,
            struct_Connection* pConnection);
        //处理BeginTransComplete事件的回调函数
        STDMETHODIMP raw_BeginTransComplete(
            LONG TransactionLevel,
            struct Error*pError;
            EventStatusEnum*adStatus,
            struct_Connection*pConnection);
        //处理CommitTransComplete事件的回调函数
        STDMETHODIMP raw_CommitTransComplete(
            struct Error*pError,
            EventStatusEnum *adStatus,
            struct_Connection *pConnection);
        //以下还有若干事件函数,在此省略
        //......
    };
    上面定义的这个类继承了ConnectionEventVt接口,实现了Connection事件类所有事件的回调函数。读者可以仿照它定义自己的RecordsetEvent事件类。这两个类是所谓的COM组件类,所以它们必须实现IUnknown的三个标准方法:QueryInterface、AddRef和Release。读者可以使用合适的工具(如ATL)简化这部分工作,这里只给出和这一节内容有关系最大的几个回调函数的代码。
    //CConnEvent类的实现
    STDMETHODIMP CConnEvent::raw_InfoMessage(
        struct Error*pError,
        EventStatusEnum*adStatus,
        struct_Connection *pConnection)
    {
        *adStatus=adStatuUnwantedEvent;
        return S_OK;
    };
    STDMETHODIMP raw_BeginTransComplete(
        LONG TransactionLevel,
        struct Error*pError;
        EventStatusEnum*adStatus,
        struct_Connection*pConnection)
    {
        *adStatus=adStatuUnwantedEvent;
        return S_OK;
    };
    STDMETHODIMP raw_CommitTransComplete(
        struct Error*pError,
        EventStatusEnum *adStatus,
        struct_Connection *pConnection)
    {
        *adStatus=adStatuUnwantedEvent;
        return S_OK;
    };
    由于是演示,这几个函数没有做任何有意义的事情。需要注意每个函数的第一句,在这里把adStatus的值设为adStatusUnwantedEnvet,这样这个函数在第一次被调用之后将不会被再次调用。如果用户需要这个事件处理函数的工作,就必须去掉第一句。
    下面的代码演示了怎样在程序中使用上面定义的ADO事件类。
    int main(int argc,char*argv[])
    {
        HRESULT hr;
        DWORD dwConnEvt;
        DWORD dwRstEvt;
        IConnectionPointContainer *pCRC =NULL;
        IConnectionPoint*pCP=NULL;
        lUnkown*pUnk=NULL;
        CRstEvent*pRstEvent=NULL;
        CConnEvent*pConnEvent=NULL;
        int rc=0;
        _RecordsetPtr pRst;
        _ConnectionPtr pConn;

        ::CoInitialize(NULL);

        hr=pConn.CreateInstance(__uuidof(Connection));
        if(FAILED)(hr)) return rc;

        hr=pRst.CreateInstance(__uuidof(Recordset));
        if(FAILED(hr)) return rc;

        //开始使用Connection事件
        hr=pConn->QueryInterface(__uuidof(IConnectionPointContainer),
            (void**)&pCPC);
        if (FAILED)(hr)) return rc;
        hr=pCRC->FindConnectionPoint(__uuidof(ConnectionEvents),&pCP);
        pCPC->Release();
        if (FAILED(hr)) return rc;

        //开始使用Recordset事件
        hr=pRst->QueryInterface(__uuidof(IConnectionPointContainter),
            (void**)&pCPC);
        if(FAILED(hr)) return rc;
        hr=pCRC->FindConnetionPoint(__uuidof(RecordsetEvents),&pCP);
        pCPC->Release();
        if (FAILED(hr)) return rc;

        pRstEvent=new CRstEvent();
        hr=pRstEvent->QueryInterface(__uuidof(IUnknown),(void**)&pUnk);
        if (FAILED(hr)) return rc;
        hr=pCP->Advise(pUnk,&dwRstEvt);
        pCP->Release();
        if (FAILED(hr)) return rc;

        //进行一些数据库操作
        pConn->Open("DSN=Pubs;","sa","",adConnectionUnspecified);
        pRst->Open("SELECT * FROM authors",(IDispatch*)pConn,
            adOpenStatic,adLockReadOnly,adCmdText);
        pRst->MoveFirst();
        while(pRst->EndOfFile==FALSE)
        {
            wprintf(L"Name='%s'/n",(wchar_t*)
                ((_bstr_t)pRst->Fields->GetItem("au_lname")->Value));
            pRst->MoveNext();
        }

        pRst->Close();
        pConn->Close();

        //停止使用Connection事件类
        hr=pConn->QueryInterface(__uuidof(IConnectionPointContainter),
            (void**)&pCPC);
        if(FAILED(hr)) return rc;
        hr=pCPC->FindConnectionPoint(__uuidof(ConnectionEvents),&pCP);
        pCPC->Release();
        if (FAILED(hr)) return rc;

        //停止使用Recordset事件类
        hr=pConn->QueryInterface(__uuidof(IConnectionPointContainter),
            (void**)&pCPC);
        if(FAILED(hr)) return rc;
        hr=pCPC->FindConnectionPoint(__uuidof(RecordsetEvents),&pCP);
        pCPC->Release();
        if (FAILED(hr)) return rc;
        hr=pCP->Unabvise(dwRstEvt);

        pCP->Release();
        if(FAILED(hr)) return rc;

        CoUninitialize();
        return 1;
    }

    注: 本文内容基本来自网络,自己只做少量修改.

  • 相关阅读:
    There is an overlap in the region chain修复
    There is an overlap in the region chain
    region xx not deployed on any region server
    python 中的re模块,正则表达式
    TCP粘包问题解析与解决
    yield from
    Git push提交时报错Permission denied(publickey)...Please make sure you have the correct access rights and the repository exists.
    mysql 中Varchar 与char的区别
    Mysql 字符集及排序规则
    请实现一个装饰器,限制该函数被调用的频率,如10秒一次
  • 原文地址:https://www.cnblogs.com/ainima/p/6332069.html
Copyright © 2011-2022 走看看