Figure 2   Object Types

Data Sources

 cotype TDataSource {
[mandatory] interface IDBInfo;
[mandatory] interface IDBInitialize;
[mandatory] interface IDBCreateSession;
[optional]  interface IPersistFile; //for persistence, see OLE2
[optional]  interface IProperties; //for persistence, see OLE2
}

DB Sessions

 cotype TDBSession {
[mandatory] interface IOpenRowset;
[optional]  interface IDBCreateCommand;  //mandatory if Commands are supported
[optional]  interface ITransactionLocal; //mandatory if transactions are
                                         //supported
[optional]  interface ITransactionObject;//optional if transactions are 
                                         //supported
[optional]  interface ITransactionJoin;  //mandatory if coordinated 
                                         //transactions
[optional]  interface ITableDefinition;  //mandatory if table creation is 
                                         //supported
[optional]  interface IIndexDefinition;  //mandatory if index creation is 
                                         //supported
[optional]  interface IDBSchemaRowset;   //mandatory if Schema are supported
}

Commands

 cotype TCommand {
[mandatory] interface ICommand;
[mandatory] interface IAccessor;
[mandatory] interface IColumnsInfo;
[mandatory] interface ICommandProperties;
[mandatory] interface ICommandText;
[optional]  interface ICommandWithParameters; //mandatory if parameters 
                                              //supported
[optional]  interface ICommandPrepare; // mandatory
[optional]  interface IColumnsRowset;
}

Rowsets

 cotype TRowset {
[mandatory] interface IAccessor;
[mandatory] interface IRowsetInfo;
[mandatory] interface IColumnsInfo;
[mandatory] interface IRowset;      //IRowset or a derivative is mandatory
[optional]  interface IColumnsRowset;
[optional]  interface IRowsetIdentity;
[optional]  interface IRowsetLocate;
[optional]  interface IRowsetScroll;
[optional]  interface IRowsetExactScroll;
[optional]  interface IRowsetChange;
[optional]  interface IRowsetDelete;
[optional]  interface IRowsetNewRow;
[optional]  interface IRowsetUpdate;
[optional]  interface IRowsetResynch;
[optional]  interface IRowsetLockRows;
[optional]  interface IRowsetWithParameters;
[optional]  interface IRowsetCopyRows;
[optional]  interface IConnectionPointContainer; //mandatory if notifications supported
[optional]  interface IProvideMoniker;
}

Indexes

 cotype TIndex {
[mandatory] interface IRowset;
[mandatory] interface IRowsetIndex;
[mandatory] interface IAccessor;
[mandatory] interface IColumnsInfo;
[optional]  interface IRowsetDelete;
[optional]  interface IRowsetNewRow;
};

Errors

 cotype TErrorObject {
[mandatory] interface IErrorInfo;
[mandatory] interface IErrorRecords;
};

Transactions

 cotype TTransaction {
[mandatory] interface ITransaction;    
[mandatory] interface ITransactionOptions;
[mandatory] interface IConnectionPointContainer;
};

Figure 4   DBBINDING Structure

 typedef struct  tagDBBINDING
    {
    DBCOLUMNPART dwPart;            // Value | Length | Status
    DBPARAMIO eParamIO;             // How Accessor is used (Input/Output 
                                    // parameter)
    ULONG iColumn;                  // Row Column Ordinal
    ULONG dwType;                   // DBTYPE of Consumers Structure
    ITypeInfo __RPC_FAR *pTypeInfo; // COM abstract data type
    DBNUMERIC __RPC_FAR *pNum;      // Contains values for properties
    ULONG obValue;                  // Offset of value consumer's struct
    ULONG cbMaxLen;                 // Size of consumers field
    DBOBJECT *pObject;              // Information for binding to a OLE object
    ULONG obLength;                 // Offset of length consumers struct
    ULONG obStatus;                 // Offset of status consumers struct
    }    DBBINDING;

Figure 5   Binding Creation

 //**********************************************************************
// SetupBindings
// 
// Purpose:
// 
//     Creates bindings that map the data in the rowset's columns to 
//     locations in the data consumer's data buffer.
// Parameters:
// 
//     ULONG ulCol                  - number of columns in rowset to bind
//     DBCOLUMNINFO*    pColumnInfo - pointer to column metadata
//     DBBINDING* rgBind_out        - out pointer through which to return
//                                    an array of binding structures, one
//                                    structure per column bound
//     ULONG* pcBind_out            - out pointer through which to return
//                                    the number of columns bound (number
//                                    of valid elements in rgBind_out)
//     ULONG* pcMaxRowSize_out      - out pointer through which to return
//                                    the buffer size necessary to hold
//                                    the largest row data
//**********************************************************************
HRESULT SetupBindings (ULONG cCol, DBCOLUMNINFO* pColumnInfo, 
                       DBBINDING* rgBind_out, ULONG* pcBind_out, 
                       ULONG* pcMaxRowSize_out)
{
    ULONG dwOffset = 0;

    for (UNLONG ulCol = 0; ulCol < cCol; ulCol++)
    {
        rgBind_out[ulCol].dwPart    = DBCOLUMNPART_VALUE|DBCOLUMNPART_LENGTH|
                                      DBCOLUMNPART_STATUS;
        rgBind_out[ulCol].eParamIO  = DBPARAMIO_NOTPARAM;                              
        rgBind_out[ulCol].iColumn   = pColumnInfo[ulCol].iNumber;
        rgBind_out[ulCol].dwType    = DBTYPE_STR;
        rgBind_out[ulCol].pTypeInfo = NULL;
        rgBind_out[ulCol].pNum      = NULL;
        rgBind_out[ulCol].obValue   = dwOffset + offsetof(COLUMNDATA,bData);
        rgBind_out[ulCol].obLength  = dwOffset + offsetof(COLUMNDATA,
                                                          dwLength);
        rgBind_out[ulCol].obStatus  = dwOffset + offsetof(COLUMNDATA,
                                                          dwStatus);
        rgBind_out[ulCol].cbMaxLen  = pColumnInfo[ulCol].dwType == 
                                         DBTYPE_STR ?
                                         pColumnInfo[ulCol].cbMaxLength +
                                         sizeof(char) : DEFAULT_CBMAXLENGTH;
        rgBind_out[ulCol].pObject   = NULL;
        dwOffset += rgBind_out[ulCol].cbMaxLen + offsetof( COLUMNDATA, 
                                                          bData );
        dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
    }  
    *pcBind_out       = ulCol;
    *pcMaxRowSize_out = dwOffset;

    return NOERROR;
}

Figure 6   CreateAccessor Parameters

DBACCESSORFLAGS dwAccessorFlags [in]

A bitmask that describes the properties of the Accessor and how to use it (DBACCESSOR_READ, DBACCESSOR_READWRITE, and so on).

ULONG cBindings [in]

The number of bindings in the Accessor.

const DBBINDING rgBindings[] [in]

An array of DBBINDING structures.

ULONG cbRowSize [in]

The number of bytes applicable for a single row of data in a data consumer's buffer.

ULONG* pulErrorBinding [out]

If an error occurs in the binding, this index will call out which binding caused the problem. It begins with zero. If no error occurs this value is not touched. If the caller passes a null pointer, this parameter is ignored.

HACCESSOR *phAccessor [out]

A pointer to memory in which to return the handle, HACCESSOR, of the created Accessor.

Figure 7   Getting Data from a Rowset

 // Read the rows;  160 rows at a time
// Approximately 16KB of data assuming 100 Byte records

while (SUCCEEDED(hr = pIRowset -> GetNextRows (0, NULL, 0, 160, 
                                               &cRowsObtained, 
                                               &rghRows)) && cRowsObtained)
{
    for (ULONG ulrow = 0; ulrow < cRowsObtained; ulrow++)
    {
      pIRowset -> GetData (rghRows(ulrow), hAccessor, rgData);

      // code to print the data
      PrintData (rgData);
    }
    pIRowset -> ReleaseRows (cRowsObtained, rghRows, NULL, NULL);
    CoTaskMemoryFree(rghRows);
}

// release the Accessor and the Rowset
pIAccessor -> ReleaseAccessor(hAccessor);
pIRowset -> Release();

GetNextRows does not give you the data yet. Calling IRowset::GetData will actually get the data from rows that were returned to you from the prior call to IRowset::GetNextRows.

IRowset::GetData is prototyped as follows:

 HRESULT GetData (HROW hRow, HACCESSOR hAccessor, 
                 void *pData);

Figure 10   Execute()Parameters

Parameter

Description

rgpUnkOuters [in]

An array of pointers to the controlling IUnknown interfaces if the Rowsets are created as parts of aggregates. If NULL, then no rowsets are parts of aggregates.

riid [in]

The requested interface's ID.

pParams [in/out]

The DBPARAMS structure specifies values for one or more parameters.

phChapter [out]

A pointer to memory in which to return a pointer to the results chapter. If none of the Rowsets returned supports IRowsetWithParameters, *phChapter is set to DB_INVALID_HCHAPTER.

fResults [in]

TRUE indicates resume a suspended execution. FALSE indicates execute the command.

pcRowsets [in/out]

A pointer to memory containing the count of Rowsets.

prgpRowsets [in/out]

An output array of Rowsets.

ppRowsetNames [out]

An output array of the string names of all hierarchy levels. Array index 0 will contain the pseudo-name DBROWSET_ROOT, a constant that is always associated with the root of the hierarchy. The caller MUST NOT free the name strings; they are owned by the command object.

Figure 14   CheckBook's CDocumentClass Rewritten to Use OLE DB

 /////////////////////////////////////////////////////////////////////////////
//
//    CHECKBOOKDOC.CPP
//
//////////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#define     DBINITCONSTANTS
#include "oledb.h"
#include "viewhints.h"
#include "row.h"
#include "Checkbook.h"
#include "CheckbookDoc.h"
#include "checkview.h"
#include "ledgerview.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CCheckbookDoc

IMPLEMENT_DYNCREATE(CCheckbookDoc, CDocument)

BEGIN_MESSAGE_MAP(CCheckbookDoc, CDocument)
    //{{AFX_MSG_MAP(CCheckbookDoc)
    ON_COMMAND(ID_EDIT_NEW_CHECK, OnNewCheck)
    ON_COMMAND(ID_NEXT_CHECK, OnNextCheck)
    ON_UPDATE_COMMAND_UI(ID_NEXT_CHECK, OnUpdateNextCheck)
    ON_COMMAND(ID_PREV_CHECK, OnPrevCheck)
    ON_UPDATE_COMMAND_UI(ID_PREV_CHECK, OnUpdatePrevCheck)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

BEGIN_DISPATCH_MAP(CCheckbookDoc, CDocument)
    //{{AFX_DISPATCH_MAP(CCheckbookDoc)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //      DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCheckbookDoc construction/destruction

CCheckbookDoc::CCheckbookDoc()
{
    // Initialize variables
    m_pIDBInitialize = NULL;
    m_pIRowset = NULL;
    m_pIAccessor = NULL;
    m_hAccessor = NULL;
    m_nActiveRecord = 0;
    m_cColumns = 0;
    m_pColumnInfo = NULL;
    m_ulMaxRowSize = 0;
}

CCheckbookDoc::~CCheckbookDoc()
{
    // Cleanup all of the interfaces and Rows
    CleanUp();
}

// Release all interfaces that may have been retrieved from the data
// provider and frees up memory that may have been allocated
// to hold Row data.
void CCheckbookDoc::CleanUp() 
{
    if (m_pColumnInfo != NULL)
    {
        CoTaskMemFree (m_pColumnInfo);
        m_pColumnInfo = NULL;
    }

    // Release the accessor
    if (m_pIAccessor != NULL)
    {
        if  (m_hAccessor != NULL)
        {
            m_pIAccessor->ReleaseAccessor(m_hAccessor);
            m_hAccessor = NULL;
        }
        m_pIAccessor->Release();
        m_pIAccessor = NULL;
    }

    // Take care of the ulIndexset interface and any rows that may
    // have been retrieved from the data provider.
    if (m_pIRowset != NULL)
    {
        CRow *pRow = NULL;
        POSITION pos = NULL;

        // Release all rows held by the data provider
        ReleaseRows();

        // Cleaup the memory that was used to hold the check
        // information in the CObList m_oblRows.
        while ((pos = m_oblRows.GetHeadPosition()) != NULL)
        {
            pRow = (CRow *)m_oblRows.GetAt(pos);
            m_oblRows.RemoveAt(pos);
            if (pRow != NULL)
                delete pRow;
        }

        // Release the rowset interface
        m_pIRowset->Release();
        m_pIRowset = NULL;
    }

    // Un-Initialize the DSO
    if (m_pIDBInitialize != NULL)
    {
        m_pIDBInitialize -> Uninitialize();
        m_pIDBInitialize->Release();
        m_pIDBInitialize = NULL;
    }
}

/////////////////////////////////////////////////////////////////////////////
// CCheckbookDoc diagnostics

#ifdef _DEBUG
void CCheckbookDoc::AssertValid() const
{
    CDocument::AssertValid();
}

void CCheckbookDoc::Dump(CDumpContext& dc) const
{
    CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CCheckbookDoc commands

BOOL CCheckbookDoc::OnOpenDocument(LPCTSTR lpszPathName) 
{
    HRESULT hr = S_OK;

    if (!CDocument::OnOpenDocument(lpszPathName))
        return FALSE;

    // We have the file name, lets get the CLSID for the Check Book Data
    // Provider so that an instance can be created.
    CLSID CLSID_OLEDBObject;
    if (CLSIDFromProgID(OLESTR("CHKBOOKDP.DSO.1"), &CLSID_OLEDBObject) != S_OK)
        {
        // Can't get CLSID for OLEDB object ProgID
        AfxMessageBox (IDS_FAILED_CLSIDProgID);
        return FALSE;
        }

    // Create an instance of the DSO, obtaining the IDBInitialize interface.
    if (CoCreateInstance(CLSID_OLEDBObject, NULL, CLSCTX_SERVER,
                         IID_IDBInitialize, (LPVOID *)&m_pIDBInitialize) !=S_OK)
        {
        // Could not instantiate the CheckBook DSO
        AfxMessageBox (IDS_FAILED_DSO_INSTANTIATE);
        return FALSE;
        }

    // The next step is to initialize the data provider.  During
    // initialization, the data provider will open or create
    // the checkbook file as necessary.
    if ((hr = InitializeDSO(lpszPathName)) == S_OK)
    {
        // Get a Rowset Interface from the data provider
        if ((hr = GetRowsetInterface()) == S_OK)
        {
            // Get column information used for the bindings
            if ((hr = GetColumnInfo()) == S_OK)
            {
                // Now that the data consumer has the column information,
                // set up the DBBINDING structure.
                if ((hr = SetupBindings()) == S_OK)
                {
                    // Create an Accessor for getting the check book
                    // data from the data provider.
                    if ((hr = CreateAccessor()) == S_OK)
                    {
                        // Fetch all of the data from the data provider
                        if ((hr = GetData ()) == S_OK)
                        {
                            // Update both the Check and Ledger views.
                            UpdateAllViews (NULL, VIEWHINT_GET_ALL_DATA);
                        }
                    }
                }
            }
        }
    }

    if (hr != S_OK)
    {
        // There was a failure, cleanup all interfaces and do not
        // create the document
        CleanUp();
        return FALSE;
    }
    
    return TRUE;
}

// initializes the Check Book data provider by
// providing the file name of the check book .chb file.
HRESULT CCheckbookDoc::InitializeDSO (LPCTSTR lpszPathName)
{
    HRESULT hr = S_OK;

    GUID rgOptionIDs[1];
    VARIANT rgOptionVals[1];
    VariantInit(&rgOptionVals[0]);

    rgOptionIDs[0] = DBINIT_OPT_NAME;
    rgOptionVals[0].vt = VT_BSTR;
    CString strPathName = lpszPathName;
    rgOptionVals[0].bstrVal = strPathName.AllocSysString();

    hr = m_pIDBInitialize->Initialize (1, rgOptionIDs, rgOptionVals);
    if (hr != S_OK)
        AfxMessageBox(IDS_FAILED_DSO_INITIALIZE);

    return hr;
}

// Obtain a Rowset interface from the check book data provider.
// The ulIndexset interface is saved for future uses such as
// retrieving,updating and adding new checks.
HRESULT CCheckbookDoc::GetRowsetInterface ()
{
    HRESULT hr = S_OK;

    // Create a DSSession object
    IDBCreateSession *pIDBCreateSession = NULL;
    hr = m_pIDBInitialize->QueryInterface(IID_IDBCreateSession, 
                                          (void **)&pIDBCreateSession);

    if (hr == S_OK)
    {
        // From the DBSession object, get the IOpenRowset interface
        IOpenRowset *pIOpenRowset = NULL;
        hr = pIDBCreateSession -> CreateSession(NULL, IID_IOpenRowset,
                                                (IUnknown **)&pIOpenRowset);
        pIDBCreateSession -> Release();

        if (hr == S_OK)
        {
            hr = pIOpenRowset -> OpenRowset(NULL, NULL, 0, NULL, IID_IRowset, 
                                            NULL, (IUnknown **)&m_pIRowset);
            pIOpenRowset -> Release();
        }
    }

    return hr;
}

// Retrieves an IAccessor interface and create an accessor that
// is used for reading and writing checks.
HRESULT CCheckbookDoc::CreateAccessor ()
{
    HRESULT hr = S_OK;
    
    hr = m_pIRowset -> QueryInterface(IID_IAccessor, (void **)&m_pIAccessor);
    if (hr == S_OK)
    {
        ULONG ulErrorBinding = (ULONG)-1L;
        hr = m_pIAccessor->CreateAccessor(DBACCESSOR_READWRITE |  
                                          DBACCESSOR_ROWDATA,
                                          m_ulBindings, m_prgBindings, 0, 
                                          &ulErrorBinding, &m_hAccessor);
    }
    return hr;
}

// Releases all of the HROWS from within the data provider.
HRESULT CCheckbookDoc::ReleaseRows()
{
    ASSERT (m_pIRowset);

    ULONG ulRows;
    HRESULT hr = S_OK;
    HROW *prghRows = NULL;

    ulRows = m_oblRows.GetCount();
    prghRows = (HROW *) CoTaskMemAlloc((sizeof (HROW *)) * ulRows);

    if (prghRows != NULL) 
    {
           HROW *pTempRow;
        pTempRow = prghRows;

        POSITION pos = NULL;
        CRow *pRow = NULL;
        for (ULONG ulIndex = 0; ulIndex < ulRows; ulIndex++)
        {
            pos = m_oblRows.FindIndex (ulIndex);
            pRow = (CRow *)m_oblRows.GetAt(pos);
            *pTempRow++ = pRow -> m_hRow;
        }

        hr = m_pIRowset -> ReleaseRows(ulRows, prghRows, NULL, NULL);
        CoTaskMemFree(prghRows);
    }
    else
        hr = E_OUTOFMEMORY;

    return hr;
}


// Retrieves a specific check from the CObList and returns the check
// in a CRow.
BOOL CCheckbookDoc::GetRow (ULONG ulIndex, CRow **pRow)
{
    POSITION pos = NULL;

    pos = m_oblRows.FindIndex (ulIndex);
    if (pos != NULL)
    {
        *pRow = (CRow *)m_oblRows.GetAt(pos);
        return TRUE;
    }
    return FALSE;
}

// Retrieves check information based on the index into the "checkbook".
BOOL CCheckbookDoc::GetCheck (ULONG ulIndex, UINT& nCheckNo, DWORD& dwCents, 
                              CString& strPayTo, CString& strDate, 
                              CString& strMemo)
{
    CRow *pRow;

    if (GetRow(ulIndex, &pRow))
    {
        COLUMNDATA *pColumn;
    
        ASSERT (offsetof(COLUMNDATA, dwLength) == 0);

        for (ULONG ulIndex=0; ulIndex < m_ulBindings; ulIndex++)
        {
            pColumn = (COLUMNDATA *) (pRow -> m_pData + 
                                      m_prgBindings[ulIndex].obLength);

            switch (m_prgBindings[ulIndex].iColumn) 
            {
                case CB_CHECKNO_ORDINAL:
                    nCheckNo = *(unsigned int *) pColumn->bData; 
                    break;

                case CB_AMOUNT_ORDINAL:
                    dwCents = *(unsigned long *) pColumn->bData; 
                    break;

                case CB_PAYTO_ORDINAL:
                    strPayTo = (const TCHAR *) &pColumn->bData;
                    break;

                case CB_DATE_ORDINAL:
                    strDate = (const TCHAR *) &pColumn->bData;
                    break;

                case CB_MEMO_ORDINAL:
                    strMemo = (const TCHAR *) &pColumn->bData;
                    break;
            }
        }
        return TRUE;
    }
    return FALSE;
}

// Updates the active check with the values passed into the member function
// by using the data providers ulIndexsetChange interface.
HRESULT CCheckbookDoc::UpdateData(CView* pSourceView, UINT nCheckNo,
                                  DWORD dwCents, LPCTSTR lpszPayTo, 
                                  LPCTSTR lpszDate, LPCTSTR lpszMemo)
{
    ASSERT (m_pIRowset);

    HRESULT hr = S_OK;

    IRowsetChange *pIRowsetChange = NULL;
    hr = m_pIRowset->QueryInterface(IID_IRowsetChange, 
                                    (void **)&pIRowsetChange);
    if (hr == S_OK)
    {
        CRow *pRow;
        if (GetRow(m_nActiveRecord, &pRow))
        {
            BYTE *pRowData = NULL;
            pRowData = (BYTE *) CoTaskMemAlloc (m_ulMaxRowSize);
            if (!pRowData)
                return E_OUTOFMEMORY;

            memcpy(pRowData, pRow -> m_pData, sizeof(*pRowData));

            if (S_OK == (CopyCheckDataIntoBuffer (pRowData, nCheckNo,
                                                  dwCents, lpszPayTo, 
                                                  lpszDate, lpszMemo)))
            {
                if ((hr = pIRowsetChange -> SetData (pRow -> m_hRow, 
                                  m_hAccessor, (const void *)pRowData)) == S_OK)
                {
                    memcpy((BYTE *)pRow -> m_pData, (BYTE *)pRowData,  
                           m_ulMaxRowSize);
                    UpdateAllViews(pSourceView, VIEWHINT_UPDATE_CHECK, NULL);
                }
            }
            CoTaskMemFree((void *) pRowData);
        }
    }

    pIRowsetChange -> Release();
    return hr;
}

// Function creates a new record by using the ulIndexsetNewRow interface
// supported by the data provider.  The new row is added to the CObList
// and the views are updated accordingly.
HRESULT CCheckbookDoc::AddNewCheck()
{
    BYTE *pRowData = NULL;
    HROW *prghRows = NULL;
    HRESULT hr = S_OK;

    ASSERT (m_pIRowset);
    ASSERT (m_hAccessor);
    ASSERT (m_pColumnInfo);
    ASSERT (m_ulMaxRowSize != 0);
    
    pRowData = (BYTE *) CoTaskMemAlloc (m_ulMaxRowSize);
    if (!pRowData)
        return E_OUTOFMEMORY;

    IRowsetNewRow *pIRowsetNewRow = NULL;
    hr = m_pIRowset->QueryInterface(IID_IRowsetNewRow, 
                                    (void **)&pIRowsetNewRow);
    if (hr == S_OK)
    {
        HROW phRow = NULL;

        if ((hr = pIRowsetNewRow -> SetNewData (NULL, m_hAccessor, pRowData,  
                                                &phRow)) == S_OK)
        {
            StoreRowData (phRow, pRowData);

            // Make this the active check and update the ledger view.
            m_nActiveRecord = m_oblRows.GetCount() - 1;
            UpdateAllViews(NULL, VIEWHINT_ADD_CHECK);
        }
        else
            CoTaskMemFree (pRowData);

        pIRowsetNewRow -> Release();
    }

    return S_OK;
}

BOOL CCheckbookDoc::MaybeCommitDirtyCheck()
{
    CView *pView;
    POSITION pos = GetFirstViewPosition();
    while (pos != NULL)
    {
        pView = GetNextView(pos);
        CCheckView *pCheckView = DYNAMIC_DOWNCAST(CCheckView, pView);
        if (pCheckView != NULL)
            return pCheckView->MaybeCommitDirtyCheck();
    }
    return TRUE;
}

// When the user changes the check selection in the ledger view, this member
// function is called to update the check view.
void CCheckbookDoc::ChangeSelectionToCheck(CLedgerView *pLedgerView, 
                                           ULONG ulIndex)
{
    if (!MaybeCommitDirtyCheck())
        return;

    m_nActiveRecord = ulIndex;
    UpdateAllViews (pLedgerView, NULL);
}

// When the user uses the toolbar or menu items to move through the checks,
// this member function is called to update the view appropriately.
void CCheckbookDoc::ChangeSelectionNextCheckNo(BOOL bNext)
{
    if (bNext)
    {
        if (m_nActiveRecord < (GetNumberOfRows() - 1))
        {
            if (!MaybeCommitDirtyCheck())
                return;
            ++m_nActiveRecord;
            UpdateAllViews(NULL, VIEWHINT_NEXT_CHECK, NULL);
        }
    }
    else
    {
        if (m_nActiveRecord > 0)
        {
            if (!MaybeCommitDirtyCheck())
                return;
            --m_nActiveRecord;
            UpdateAllViews(NULL, VIEWHINT_NEXT_CHECK, NULL);
        }
    }
}

////////////////////////////////////////////////////////
// Commands

void CCheckbookDoc::OnNewCheck() 
{
    // Before creating a new record, which will become the new selection,
    // ask the user whether he or she wants to commit data entered in the
    // check view for the previously selected check.

    if (!MaybeCommitDirtyCheck())
        return;

    AddNewCheck();
}

void CCheckbookDoc::OnNextCheck() 
{
    ChangeSelectionNextCheckNo(TRUE);
}

void CCheckbookDoc::OnUpdateNextCheck(CCmdUI* pCmdUI) 
{
    pCmdUI->Enable(m_nActiveRecord < (GetNumberOfRows() - 1));
}

void CCheckbookDoc::OnPrevCheck() 
{
    ChangeSelectionNextCheckNo(FALSE);
}

void CCheckbookDoc::OnUpdatePrevCheck(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_nActiveRecord > 0);
}

// Function retieves column information from the data provider.
HRESULT CCheckbookDoc::GetColumnInfo ()
{
    ASSERT (m_pIRowset);

    ULONG pcColumns;
    HRESULT hr = S_OK;
    DBCOLUMNINFO *prgInfo = NULL;
    WCHAR *ppStringsBuffer = NULL;

    // Get an interface pointer to IColumnsInfo
    IColumnsInfo *pIColumnsInfo = NULL;
    hr = m_pIRowset -> QueryInterface(IID_IColumnsInfo, 
                                      (void **)&pIColumnsInfo);

    if (hr == S_OK)
    {
        // Get the Column Information
        hr = pIColumnsInfo -> GetColumnInfo(&pcColumns, 
                                            (DBCOLUMNINFO **)&prgInfo, 
                                            (WCHAR **)&ppStringsBuffer);
        pIColumnsInfo -> Release();

        if (hr == S_OK)
        {
            m_cColumns = pcColumns;
            m_pColumnInfo = prgInfo;

            CoTaskMemFree (ppStringsBuffer);
            ppStringsBuffer = NULL;
        }
    }
    return hr;
}

// Function creates bindings that map the data in the rowset's columns
// to the check book data consumers buffer.
HRESULT CCheckbookDoc::SetupBindings ()
{
    ASSERT (m_pColumnInfo);

    UINT cBinding = 0;
    DWORD dwOffset = 0;

    // Since the check book data consumer displays all information
    // about a check, and that's all that the data provider provides,
    // get all of the data.
    for (ULONG ulIndex=0; ulIndex < m_cColumns; ulIndex++)
    {
        m_prgBindings[cBinding].dwPart = DBCOLUMNPART_VALUE | 
                                      DBCOLUMNPART_LENGTH | DBCOLUMNPART_STATUS;
        m_prgBindings[cBinding].eParamIO = DBPARAMIO_NOTPARAM;                              
        m_prgBindings[cBinding].iColumn = m_pColumnInfo[ulIndex].iNumber;
        m_prgBindings[cBinding].dwType = m_pColumnInfo[ulIndex].dwType;
        m_prgBindings[cBinding].pTypeInfo = m_pColumnInfo[ulIndex].pTypeInfo;
        m_prgBindings[cBinding].pNum = NULL;
        m_prgBindings[cBinding].obValue = dwOffset + offsetof(COLUMNDATA,bData);
        m_prgBindings[cBinding].obLength = dwOffset + offsetof(COLUMNDATA,
                                                               dwLength);
        m_prgBindings[cBinding].obStatus = dwOffset + offsetof(COLUMNDATA,
                                                               dwStatus);
        m_prgBindings[cBinding].cbMaxLen = 
            m_pColumnInfo[ulIndex].dwType ==  
                DBTYPE_STR ? m_pColumnInfo[ulIndex].cbMaxLength + sizeof(char) :                                  m_pColumnInfo[ulIndex].cbMaxLength;
        m_prgBindings[cBinding].pObject.pUnkOuter = NULL;
        m_prgBindings[cBinding].pObject.iid = IID_NULL;
        m_prgBindings[cBinding].pObject.pbc = NULL;
        dwOffset += m_prgBindings[cBinding].cbMaxLen + offsetof (COLUMNDATA,
                                                                 bData);
        cBinding++;
    }  
    
    m_ulBindings = cBinding;
    m_ulMaxRowSize = dwOffset;

    return S_OK;
}

HRESULT CCheckbookDoc::GetData ()
{
    HRESULT hr = S_OK;
    HROW *prghRows = NULL;
    BYTE *pRowData = NULL;
    ULONG ulRowsObtained;

    ASSERT (m_pIRowset);
    ASSERT (m_hAccessor);
    ASSERT (m_pColumnInfo);
    
    while (TRUE)
    {
        hr = m_pIRowset->GetNextRows(0, 0, 20, &ulRowsObtained, &prghRows);
        if (FAILED(hr))
            return hr;

        // check to see the data provider returned any rows
        if (ulRowsObtained == 0)
            break;

        // For each row, get the checkbook data from the ata provider
        for (ULONG ulIndex = 0; ulIndex < ulRowsObtained; ulIndex++ )
        {
            pRowData = (BYTE *) CoTaskMemAlloc(m_ulMaxRowSize);
            if (!pRowData)
                return E_OUTOFMEMORY;

            hr = m_pIRowset->GetData(prghRows[ulIndex], m_hAccessor, pRowData );
            if (FAILED(hr))
            {
                // Free the task memory allocated by the data provider
                CoTaskMemFree(prghRows);
                
                if (pRowData)
                    CoTaskMemFree(pRowData);
                return hr;
            }
            StoreRowData (prghRows[ulIndex], pRowData);
            m_nActiveRecord = m_oblRows.GetCount() - 1;
        }

        // Free the task memory allocated by the data provider
        CoTaskMemFree(prghRows);
        prghRows = NULL;
    }

    return S_OK;
}

HRESULT CCheckbookDoc::StoreRowData (HROW phRow, BYTE *pData)
{
    CRow *pRow = NULL;
    HRESULT hr = S_OK;
    COLUMNDATA*    pColumn;
    DWORD dwStatus, dwLength;
    
    ASSERT (offsetof(COLUMNDATA, dwLength) == 0);

    pRow = new CRow;
    if (pRow == NULL)
        return E_OUTOFMEMORY;

    // Do some coersion error checking first before storing the data
    for (ULONG ulIndex=0; ulIndex < m_ulBindings; ulIndex++)
    {
        pColumn = (COLUMNDATA *) (pData + m_prgBindings[ulIndex].obLength);

        dwStatus = pColumn->dwStatus;
        dwLength = pColumn->dwLength;

        if (dwStatus == DBCOLUMNSTATUS_CANTCOERCE)
            // Have a problem, therefore return an error
            hr = E_UNEXPECTED;
        else
        {
            switch (m_prgBindings[ulIndex].dwType) 
            {
                case DBTYPE_UI8:
                    // Make sure this is the Amount Column
                    if (!m_prgBindings[ulIndex].iColumn == CB_AMOUNT_ORDINAL)
                        hr = E_UNEXPECTED;
                    break;

                case DBTYPE_UI4:
                    // Make sure this is the Check Number Column
                    if (!m_prgBindings[ulIndex].iColumn == CB_CHECKNO_ORDINAL)
                        hr = E_UNEXPECTED;
                    break;

                case DBTYPE_STR:
                    // Process the string related columns
                    if (!(m_prgBindings[ulIndex].iColumn == CB_PAYTO_ORDINAL ||
                        m_prgBindings[ulIndex].iColumn == CB_DATE_ORDINAL ||
                        m_prgBindings[ulIndex].iColumn == CB_MEMO_ORDINAL))
                        hr = E_UNEXPECTED;
                    break;

                default:
                    hr = E_UNEXPECTED;
                    break;
            }

            if (hr != S_OK)
                break;
        }
    }

    if (hr != S_OK)
        delete pRow;
    else
    {
        // Save the check information in a CObList
        pRow -> m_hRow = phRow;
        pRow -> m_pData = pData;
        m_oblRows.AddTail (pRow);
    }
    return hr;
}

HRESULT CCheckbookDoc::CopyCheckDataIntoBuffer (BYTE *pData, UINT nCheckNo,
                                                DWORD dwCents, LPCTSTR lpszPayTo, 
                                                LPCTSTR lpszDate, 
                                                LPCTSTR lpszMemo)
{
    HRESULT hr = S_OK;
    COLUMNDATA*    pColumn;
    DWORD dwStatus, dwLength;
    
    ASSERT (offsetof(COLUMNDATA, dwLength) == 0);

    for (ULONG ulIndex=0; ulIndex < m_ulBindings; ulIndex++)
    {
        pColumn = (COLUMNDATA *) (pData + m_prgBindings[ulIndex].obLength);

        dwStatus = pColumn->dwStatus;
        dwLength = pColumn->dwLength;

        switch (m_prgBindings[ulIndex].dwType) 
        {
            case DBTYPE_UI8:
                if (m_prgBindings[ulIndex].iColumn == CB_AMOUNT_ORDINAL)
                    *(unsigned long *)pColumn->bData = dwCents;
                else
                    hr = E_UNEXPECTED;
                break;

            case DBTYPE_UI4:
                if (m_prgBindings[ulIndex].iColumn == CB_CHECKNO_ORDINAL)
                    *(unsigned int *)pColumn->bData = nCheckNo; 
                else
                    hr = E_UNEXPECTED;
                break;

            case DBTYPE_STR:
                if (m_prgBindings[ulIndex].iColumn == CB_PAYTO_ORDINAL)
                    _tcscpy((TCHAR *) &pColumn->bData, lpszPayTo);
                else if (m_prgBindings[ulIndex].iColumn == CB_DATE_ORDINAL)
                    _tcscpy((TCHAR *) &pColumn->bData, lpszDate);
                else if(m_prgBindings[ulIndex].iColumn == CB_MEMO_ORDINAL)
                    _tcscpy((TCHAR *) &pColumn->bData, lpszMemo);
                else
                    hr = E_UNEXPECTED;
                break;

            default:
                hr = E_UNEXPECTED;
                break;
        }
        if (hr != S_OK)
            break;
        
    }
    return hr;
}