奇趣技术网 收藏本站
设为主页
商务合作
首页 新闻中心 行业动态 软件新闻 安全资讯 病毒预警 漏洞发布 操作系统 Dos Win9x Win2000 WinXP Win2003 WinVista Linux Unix
数据库 DB2 Access MSSQL MySQL Oracle Sybase 编程技术 ASP PHP JSP CGI/Perl XML .Net C/C++/C# VB VC Delphi Java 汇编
安全技术 安全教学 工具介绍 漏洞利用 病毒防范 入侵检测 防火墙 安全防范 汉化破解 攻击实例 加密解密 技术论坛
中华网络安全联盟 >> 程序开发 >> VC >> Visual C++中使用OLE DB读写SQL Server简明指南
程序开发
Asp
PHP
JSP
CGI/Perl
XML
.Net
C/C++/C#
Visual Basic
Visual C++
Delphi
Java
汇编语言
  • VC中利用多线程技术实

  • 利用OpenGL实现三维绘

  • 基于Visual C++的Wins

  • 基于Visual C++6.0的D

  • Visual C++ MFC 中常用

  • 在MFC下如何定义全局变

  • 使用MFC在应用程序中嵌

  • VC++中进程与多进程管

  • Visual C++中使用OLE DB读写SQL Server简明指南
    字体:

    中华网络安全联盟    作者:佚名    来源:网络转载    时间:2006-3-22

    ©2005 黄友生。本文由原作者发布于MSN Space、CSDN。你可以保存、在非商业软件中使用、在非盈利性文章中引用本文中的部分或全部文字,但请注明作者及原文地址。要用于其它用途,请先联系作者(eien@eyou.com)。作者不保证本文完全正确无误、不对因本文中的理论或代码缺陷造成的损失负责。

    在需要对数据库进行操作时,OLE DB总是被认为是一种效率最高但最难的方法。但是以我最近使用OLE DB的经验看来,OLE DB的效率高则高矣,但却一点都不难。说它难恐怕主要是因为可参考的中文资料太少,为了帮助以后需要接触OLE DB的同行,我撰写了这篇文章。本文包含如下内容:

    1. OLE DB写数据库;
    2. OLE DB读数据库;
    3. OLE DB对二进制数据(text、ntext、image等)的处理。

    首先来看看对SQL Server进行写操作的代码,有一定VC基础的读者应该可以很顺利地看懂。OLE DB写数据库,就是这么简单!

    注:
    1.以下代码中使用的模板类EAutoReleasePtr<T>与ATL中的CComPtr<T>类似,是一个在析构时自动调用Release的类。CComPtr<T>的代码在ATLBASE.H中定义。
    2.以下代码均在UNICODE环境下编译,因为执行的SQL语句必须是UNICODE的。设置工程为UNICODE的方法是:首先在project->settings->C/C++的属性页中的Preprocessor中,删除_MBCS写入UNICODE,_UNICODE。然后在link属性页中Category中选择output,在Entry-Point symbol 中添加wWinMainCRTStartup。

    EAutoReleasePtr<IDBInitialize> pIDBInitialize;
    HRESULT hResult = ConnectDatabase( &pIDBInitialize, _T("127.0.0.1"), _T(“sa”), _T("password") );
    if( FAILED( hResult ) )
    {
        //失败,可能是因为数据库没有启动、用户名密码错等等
        return;
    }

    EAutoReleasePtr<IOpenRowset> pIOpenRowset;
    hResult = CreateSession( pIDBInitialize, &pIOpenRowset );
    if( FAILED( hResult ) )
    {
        //出错
        return;
    }

    EAutoReleasePtr<ICommand> pICommand;
    EAutoReleasePtr<ICommandText> pICommandText;
    hResult = CreateCommand( pIOpenRowset, &pICommand, &pICommandText );
    if( FAILED( hResult ) )
    {
       //出错
        return;
    }

    hResult = ExecuteSQL( pICommand, pICommandText, _T("USE PBDATA") );
    if( FAILED( hResult ) )
    {
        //如果这里失败,那就是SQL语句执行失败。在此处,就是PBDATA还未创建
        return;
    }

    // 创建表
    ExecuteSQL( pICommand, pICommandText, _T("CREATE TABLE 2005_1(Volume real NOT NULL,ID int NOT NULL IDENTITY)") );

    // 添加记录
    ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO 2005_1 VALUES(100.0)") );

    //...
      
    其中几个函数的代码如下:

    HRESULT ConnectDatabase( IDBInitialize** ppIDBInitialize, LPCTSTR pszDataSource, LPCTSTR pszUserID, LPCTSTR pszPassword )
    {
        ASSERT( ppIDBInitialize != NULL && pszDataSource != NULL && pszUserID != NULL && pszPassword != NULL );

        UINT uTimeout = 15U; // 连接数据库超时(秒)
        TCHAR szInitStr[1024];
        VERIFY( 1023 >= wsprintf( szInitStr, _T("Provider=SQLOLEDB;Data Source=%s;Initial Catalog=master;User Id=%s;Password=%s;Connect Timeout=%u"), pszDataSource, pszUserID, pszPassword, uTimeout ) );
        //Initial Catalog=master指明连接成功后,"USE master"。

        EAutoReleasePtr<IDataInitialize> pIDataInitialize;
        HRESULT hResult = ::CoCreateInstance( CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,
                IID_IDataInitialize, ( void** )&pIDataInitialize );
        if( FAILED( hResult ) )
        {
            return hResult;
        }

        EAutoReleasePtr<IDBInitialize> pIDBInitialize;
        hResult = pIDataInitialize->GetDataSource( NULL, CLSCTX_INPROC_SERVER, ( LPCOLESTR )szInitStr,
                  IID_IDBInitialize, ( IUnknown** )&pIDBInitialize );
        if( FAILED( hResult ) )
        {
            return hResult;
        }
        hResult = pIDBInitialize->Initialize( );
        if( FAILED( hResult ) )
        {
            return hResult;
        }
        * ppIDBInitialize = pIDBInitialize.Detach( );
        return S_OK;
    }

    HRESULT CreateSession( IDBInitialize* pIDBInitialize, IOpenRowset** ppIOpenRowset )
    {
        ASSERT( pIDBInitialize != NULL && ppIOpenRowset != NULL );
        EAutoReleasePtr<IDBCreateSession> pSession;
        HRESULT hResult = pIDBInitialize->QueryInterface( IID_IDBCreateSession, ( void** )&pSession );
        if( FAILED( hResult ) )
        {
            return hResult;
        }
        EAutoReleasePtr<IOpenRowset> pIOpenRowset;
        hResult = pSession->CreateSession( NULL, IID_IOpenRowset, ( IUnknown** )&pIOpenRowset );
        if( FAILED( hResult ) )
        {
            return hResult;
        }
        * ppIOpenRowset = pIOpenRowset.Detach( );
        return S_OK;
    }

    HRESULT CreateCommand( IOpenRowset* pIOpenRowset, ICommand** ppICommand, ICommandText** ppICommandText )
    {
        ASSERT( pIOpenRowset != NULL && ppICommand != NULL && ppICommandText != NULL );
        HRESULT hResult;
        EAutoReleasePtr<ICommand> pICommand;
        {
            EAutoReleasePtr<IDBCreateCommand> pICreateCommand;
            hResult = pIOpenRowset->QueryInterface( IID_IDBCreateCommand, ( void** )&pICreateCommand );
            if( FAILED( hResult ) )
            {
                return hResult;
            }
      
            hResult = pICreateCommand->CreateCommand( NULL, IID_ICommand, (IUnknown**)&pICommand );
            if( FAILED( hResult ) )
            {
                return hResult;
            }
        }
        EAutoReleasePtr<ICommandText> pICommandText;
        hResult = pICommand->QueryInterface( &pICommandText );
        if( FAILED( hResult ) )
        {
            return hResult;
        }
        * ppICommand = pICommand.Detach( );
        * ppICommandText = pICommandText.Detach( );
        return S_OK;
    }

    HRESULT ExecuteSQL( ICommand* pICommand, ICommandText* pICommandText, LPCTSTR pszCommand, LONG* plRowsAffected )
    {
        ASSERT( pICommand != NULL && pICommandText != NULL && pszCommand != NULL && pszCommand[0] != 0 );

        HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )pszCommand );
        if( FAILED( hResult ) )
        {
            return hResult;
        }
        LONG lAffected;
        hResult = pICommand->Execute( NULL, IID_NULL, NULL, plRowsAffected == NULL ? &lAffected : plRowsAffected, ( IUnknown** )NULL );
        return hResult;
    }

    以上就是写数据库的全部代码了,是不是很简单呢?下面再来读的。

    // 先用与上面代码中一样的步骤获取pICommand,pICommandText。此处省略

    HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )_T("SELECT Volume FROM 2005_1 WHERE ID = @@IDENTITY") ); //取我们刚刚添加的那一条记录
    if( FAILED( hResult ) )
    {
        return;
    }

    LONG lAffected;
    EAutoReleasePtr<IRowset> pIRowset;
    hResult = pICommand->Execute( NULL, IID_IRowset, NULL, &lAffected, ( IUnknown** )&pIRowset );

    if( FAILED( hResult ) )
    {
        return;
    }

    EAutoReleasePtr<IAccessor> pIAccessor;
    hResult = pIRowset->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );

    if( FAILED( hResult ) )
    {
        return;
    }

    // 一个根据表中各字段的数值类型而定义的结构,用于存储返回的各字段的值
    struct CLoadLastFromDB
    {
         DBSTATUS dwdsVolume;
         DWORD     dwLenVolume;
         float          fVolume;
    };

    // 此处我们只查询了一个字段。如果要查询多个字段,CLoadLastFromDB中要添加相应的字段定义,下面的dbBinding也要相应扩充。dbBinding[].iOrdinal要分别指向各个字段,dbBinding[].wType要根据字段类型赋合适的值。

    DBBINDING dbBinding[1];
    dbBinding[0].iOrdinal            = 1;   // Volume 字段的位置,从 1 开始
    dbBinding[0].obValue           = offsetof( CLoadLastFromDB, fVolume );
    dbBinding[0].obLength         = offsetof( CLoadLastFromDB, dwLenVolume );
    dbBinding[0].obStatus         = offsetof( CLoadLastFromDB, dwdsVolume );
    dbBinding[0].pTypeInfo       = NULL;
    dbBinding[0].pObject           = NULL;
    dbBinding[0].pBindExt         = NULL;
    dbBinding[0].dwPart            = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
    dbBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
    dbBinding[0].eParamIO       = DBPARAMIO_NOTPARAM;
    dbBinding[0].cbMaxLen       = 0;
    dbBinding[0].dwFlags          = 0;
    dbBinding[0].wType            = DBTYPE_R4; // float就是DBTYPE_R4,int就是DBTYPE_I4。参见MSDN
    dbBinding[0].bPrecision       = 0;
    dbBinding[0].bScale             = 0;

    HACCESSOR hAccessor = DB_NULL_HACCESSOR;
    DBBINDSTATUS dbs[1];
    hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, dbBinding, sizeof( CLoadLastDataFromDB ), &hAccessor, dbs );

    if( FAILED( hResult ) )
    {
        return;
    }

    ASSERT( dbs[0] == DBBINDSTATUS_OK );
    ULONG uRowsObtained = 0;
    HROW  hRows[1];                          // 这里我们只查询了最新的那一条记录
    HROW* phRows = hRows;
    CLoadLastFromDB rmd;
    hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
    if( SUCCEEDED( hResult ) && uRowsObtained != 0U )
    {
        hResult = pIRowset->GetData( phRows[0], hAccessor, &rmd );
        if( FAILED( hResult ) )
        {
            ASSERT( FALSE );
        }
        ASSERT( rmd.dwdsVolume == DBSTATUS_S_OK );
        // rmd.fVolume 就是我们要取的值
    }

    pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
    pIAccessor->ReleaseAccessor( hAccessor, NULL );
    pIAccessor.Release( );
    pIRowset.Release( );
      
    读操作也完成了,是不是仍然很简单呢?下面我们再来看看最麻烦的二进制数据(text、ntext、image等)的读写。要实现BLOB数据的读写,我们需要一个辅助的类,定义如下:

    class CSequentialStream : public ISequentialStream    // BLOB 数据访问类
    {
    public:
        CSequentialStream( );
        virtual ~CSequentialStream( );
        virtual BOOL Seek( ULONG uPosition );
        virtual BOOL Clear( );
        virtual ULONG GetLength( ) { return m_uBufferUsed; };
        virtual operator void* const( ) { return m_pBuffer; };
        STDMETHODIMP_( ULONG ) AddRef( ) { return ++ m_uRefCount; };
        STDMETHODIMP_( ULONG ) Release( ) { ASSERT( m_uRefCount != 0U ); -- m_uRefCount; if( m_uRefCount == 0U ) { delete this; } return m_uRefCount; };
        STDMETHODIMP QueryInterface( REFIID riid, LPVOID* ppv );
        STDMETHODIMP Read( void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbRead );
        STDMETHODIMP Write( const void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbWritten );
    void    ResetPosition( ) { m_uPosition = 0U; };
    HRESULT PreAllocBuffer( ULONG uSize );

    private:
        ULONG m_uRefCount;     // reference count
        void* m_pBuffer;           // buffer
        ULONG m_uBufferUsed;  // buffer used
        ULONG m_uBufferSize;   // buffer size
        ULONG m_uPosition;       // current index position in the buffer
    };

    实现如下:

    CSequentialStream::CSequentialStream( ) : m_uRefCount( 0U ), m_pBuffer( NULL ), m_uBufferUsed( 0U ), m_uBufferSize( 0U ), m_uPosition( 0U )
    {
        AddRef( );
    }

    CSequentialStream::~CSequentialStream( )
    {
        Clear( );
    }

    HRESULT CSequentialStream::QueryInterface( REFIID riid, void** ppv )
    {
        if( riid == IID_IUnknown || riid == IID_ISequentialStream )
        {
            * ppv = this;
            ( ( IUnknown* )*ppv )->AddRef( );
            return S_OK;
        }
        * ppv = NULL;
        return E_NOINTERFACE;
    }

    BOOL CSequentialStream::Seek( ULONG uPosition )
    {
        ASSERT( uPosition < m_uBufferUsed );
        m_uPosition = uPosition;
        return TRUE;
    }

    BOOL CSequentialStream::Clear( )
    {
        m_uBufferUsed = 0U;
        m_uBufferSize = 0U;
        m_uPosition = 0U;
        ( m_pBuffer != NULL ? CoTaskMemFree( m_pBuffer ) : 0 );
        m_pBuffer = NULL;
        return TRUE;
    }

    HRESULT CSequentialStream::PreAllocBuffer( ULONG uSize )
    {
        if( m_uBufferSize < uSize )
        {
            m_uBufferSize = uSize;
            m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );
            if( m_pBuffer == NULL )
            {
                Clear( );
                return STG_E_INSUFFICIENTMEMORY;
            }
        }
        return S_OK;
    }

    HRESULT CSequentialStream::Read( void* pv, ULONG cb, ULONG* pcbRead )
    {
        ( pcbRead != NULL ? ( * pcbRead = 0U ) : 0 );
        if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
        if( cb == 0U ) { return S_OK; }

        ASSERT( m_uPosition <= m_uBufferUsed );
        ULONG uBytesLeft = m_uBufferUsed - m_uPosition;

        if( uBytesLeft == 0U ) { return S_FALSE; } //no more bytes

        ULONG uBytesRead = ( cb > uBytesLeft ? uBytesLeft : cb );
        memcpy( pv, ( BYTE* )m_pBuffer + m_uPosition, uBytesRead );
        m_uPosition += uBytesRead;

        ( pcbRead != NULL ? ( * pcbRead = uBytesRead ) : 0 );
        return ( cb != uBytesRead ? S_FALSE : S_OK );
    }

    HRESULT CSequentialStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )
    {
        if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
        ( pcbWritten != NULL ? ( * pcbWritten = 0U ) : 0 );
        if( cb == 0U ){ return S_OK; }

        ASSERT( m_uPosition <= m_uBufferUsed );
        if( m_uBufferSize < m_uPosition + cb )
        {
            m_uBufferSize = m_uPosition + cb;
            m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );
            if( m_pBuffer == NULL )
            {
                Clear( );
                return STG_E_INSUFFICIENTMEMORY;
            }
        }
        m_uBufferUsed = m_uPosition + cb;
        memcpy( ( BYTE* )m_pBuffer + m_uPosition, pv, cb );
        m_uPosition += cb;
        ( pcbWritten != NULL ? ( * pcbWritten = cb ) : 0 );
        return S_OK;
    }

    下面我们开始往一个包含ntext字段的表中添加记录。假设这个表(News)的结构为:ID int NOT NULL IDENTITY、Title nchar(80)、 Contents ntext。

    // 先将记录添加进去,ntext字段留空。我们稍后再更新ntext的内容。
    HRESULT hResult = ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO News VALUES(''TEST'','''')") );

    DBPROP dbProp;
    dbPropSet.guidPropertySet = DBPROPSET_ROWSET;
    dbPropSet.cProperties        = 1;
    dbPropSet.rgProperties       = &dbProp;

    DBPROPSET dbPropSet;
    dbPropSet.rgProperties[0].dwPropertyID     = DBPROP_UPDATABILITY;
    dbPropSet.rgProperties[0].dwOptions         = DBPROPOPTIONS_REQUIRED;
    dbPropSet.rgProperties[0].dwStatus           = DBPROPSTATUS_OK;
    dbPropSet.rgProperties[0].colid                  = DB_NULLID;
    dbPropSet.rgProperties[0].vValue.vt           = VT_I4;
    V_I4( &dbPropSet.rgProperties[0].vValue ) = DBPROPVAL_UP_CHANGE;
        
    EAutoReleasePtr<ICommandProperties> pICommandProperties;
    hResult = pICommandText->QueryInterface( IID_ICommandProperties, ( void** )&pICommandProperties );

    // 设置 Rowset 属性为“可以更新某字段的值”
    hResult = pICommandProperties->SetProperties( 1, &dbPropSet );

    hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )L"SELECT Contents FROM News WHERE ID = @@IDENTITY" );

    LONG lAffected;
    EAutoReleasePtr<IRowsetChange> pIRowsetChange;
    hResult = pICommand->Execute( NULL, IID_IRowsetChange, NULL, &lAffected, ( IUnknown** )&pIRowsetChange );

    EAutoReleasePtr<IAccessor> pIAccessor;
    hResult = pIRowsetChange->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );

    struct BLOBDATA
    {
        DBSTATUS           dwStatus;
        DWORD              dwLength;
        ISequentialStream* pISeqStream;
    };

    // 有关DBOBJECT、DBBINDING的设置,建议参考MSDN,很容易懂。
    DBOBJECT dbObj;
    dbObj.dwFlags = STGM_READ;
    dbObj.iid         = IID_ISequentialStream;

    DBBINDING dbBinding;
    dbBinding.iOrdinal   = 1;                              // BLOB 字段的位置,从 1 开始
    dbBinding.obValue    = offsetof( BLOBDATA, pISeqStream );
    dbBinding.obLength   = offsetof( BLOBDATA, dwLength );
    dbBinding.obStatus   = offsetof( BLOBDATA, dwStatus );
    dbBinding.pTypeInfo  = NULL;
    dbBinding.pObject    = &dbObj;
    dbBinding.pBindExt   = NULL;
    dbBinding.dwPart     =  DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
    dbBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
    dbBinding.eParamIO   = DBPARAMIO_NOTPARAM;
    dbBinding.cbMaxLen   = 0;
    dbBinding.dwFlags    = 0;
    dbBinding.wType      = DBTYPE_IUNKNOWN;
    dbBinding.bPrecision = 0;
    dbBinding.bScale     = 0;

    HACCESSOR hAccessor = DB_NULL_HACCESSOR;
    DBBINDSTATUS dbs;
    hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, &dbBinding, sizeof( BLOBDATA ), &hAccessor, &dbs );

    EAutoReleasePtr<IRowset> pIRowset;
    hResult = pIRowsetChange->QueryInterface( IID_IRowset, ( void** )&pIRowset );

    ULONG uRowsObtained = 0;
    HROW* phRows = NULL;
    hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );

    CSequentialStream* pss = new CSequentialStream;
    pss->PreAllocBuffer( 1024 );                           // 预先分配好内存,并读入数据
    pss->Write( pszSomebuffer, 512, NULL );        // pss->Write可以连续调用
    pss->Write( pszSomebuffer+512, 512, NULL );
    pss->ResetPosition( );

    BLOBDATA bd;
    bd.pISeqStream = ( ISequentialStream* )pss;
    bd.dwStatus    = DBSTATUS_S_OK;
    bd.dwLength    = pss->GetLength( );

    // 将 BLOB 数据写入到数据库
    hResult = pIRowsetChange->SetData( phRows[0], hAccessor, &bd );

    pIAccessor->ReleaseAccessor( hAccessor, NULL );
    pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );

    // pss was released by pIRowsetChange->SetData.

    这样,我们就完成了一条记录的添加。读取BLOB字段的代码跟上面的完全类似,只要把
    hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
    后面的那些改成下面的代码即可。

    BLOBDATA bd;
    hResult = pIRowset->GetData( phRows[0], hAccessor, &bd );
    if( bd.dwStatus == DBSTATUS_S_ISNULL )
    {
        // 此字段为空
    }
    else if( bd.dwStatus != DBSTATUS_S_OK || bd.pISeqStream == NULL )
    {
        // 失败
    }
    else
    {
        // 从系统分配的 ISequentialStream 接口读入 BLOB 数据
        BYTE szReadBuffer[1024];
        for( ULONG uRead = 0U; ; )
        {
            if( FAILED( bd.pISeqStream->Read( szReadBuffer, 1024, &uRead ) ) )
            {
                break;
            }
            //szReadBuffer中就包含了BLOB字段的数据
            if( uRead != 1024 )
            {
                break;
            }
        }
        bd.pISeqStream->Release( );
    }
    pIAccessor->ReleaseAccessor( hAccessor, NULL );
    pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );

    至此,要讲的已全部讲完,希望对你能有所帮助。文中贴出的代码都是可以复制使用的,只是某些地方需要加入返回值判断、错误处理代码。

    字体:
     
    设为主页 收藏本站 联系我们 友情连接 商务合作 网友留言
    Copyright©2006-2008 中华网络安全联盟 All rights reserved.