/************************************************************************* * @doc SHROOM EXTERNAL API * * * * RSIMP.CPP * * * * Copyright (C) Microsoft Corporation 1997 * * All Rights reserved. * * * * This file contains the implementation of the result set object * * * * * ************************************************************************** * * * Written By : Erin Foxford * * Current Owner: erinfox * * * **************************************************************************/ #include #ifdef _DEBUG static char s_aszModule[] = __FILE__; /* For error report */ #endif #include <_mvutil.h> #include // Includes for ATL #include #include #include #include "rsimp.h" #include "prop.h" #include "plist.h" #define THEORETICAL_MAX_PROP 30 // This would be a lot of properties - Assert only const unsigned int CHUNK_SIZE = 512; // How many "chunks" of rows that can be allocated const unsigned int ROW_CHUNK = 1024; // Number of rows allocated per chunk const unsigned int ROW_CHUNK_LESS1 = ROW_CHUNK - 1; // Number of rows minus 1 // optimization for Real % ROW_CHUNK #define RealToLogical(Real, Logical) (Logical = Real & ROW_CHUNK_LESS1) static unsigned int g_iAlloc = 0; static unsigned int g_iFreed = 0; CITResultSet::CITResultSet() : m_PageMap(NULL), m_hResultSet(NULL), m_AppendRow(0), m_cProp(0), m_NumberOfPages(0), m_fInit(FALSE), m_RowsReserved(0), m_Chunk(-1) { // Allocate memory for data pool - if allocation fails, // how do we notify user? m_pMemPool = BlockInitiate((DWORD)16384, 0, 0, 0); ITASSERT (NULL != m_pMemPool); // Allocate an array of DWORD pointers. These pointers will point to the virtual memory // space of the result set m_hResultSet = _GLOBALALLOC(GMEM_MOVEABLE | GMEM_ZEROINIT, CHUNK_SIZE*sizeof(DWORD*)); ITASSERT (NULL != m_hResultSet); if (m_hResultSet) m_ResultSet = (DWORD_PTR**)_GLOBALLOCK(m_hResultSet); // The number of chunks allocated in one shot m_NumChunks = CHUNK_SIZE; } CITResultSet::~CITResultSet() { // Free up virtual memory Clear(); // Free memory pool if (m_pMemPool) { BlockFree(m_pMemPool); m_pMemPool = NULL; } // Free result set array if (m_hResultSet) { _GLOBALUNLOCK(m_hResultSet); _GLOBALFREE(m_hResultSet); m_hResultSet = NULL; } } // We wrap VirtualAlloc(.... MEM_RESERVE) so in the future we can // make this nothing on the Mac // // NOTE: Uses #define PAGE_SIZE, which is defined in mvopsys.h. // Currently it's 8k for Alpha, 4k for everything else // HRESULT WINAPI CITResultSet::Reserve() { #ifdef _DEBUG SYSTEM_INFO si; GetSystemInfo(&si); ITASSERT(PAGE_SIZE == si.dwPageSize); #endif m_Chunk++; // we have to realloc... if (m_Chunk == m_NumChunks) { // We're allocating a BUNCH of memory here. If we hit this code often, we need // to redesign our allocation scheme. ITASSERT(0); _GLOBALUNLOCK(m_hResultSet); m_NumChunks += CHUNK_SIZE; m_hResultSet = _GLOBALREALLOC(m_hResultSet, m_NumChunks*sizeof(DWORD*), GMEM_MOVEABLE | GMEM_ZEROINIT); if (m_hResultSet) { m_ResultSet = (DWORD_PTR**) _GLOBALLOCK(m_hResultSet); } else return SetErrReturn(E_OUTOFMEMORY); } // Calculate some info we'll need later on if (!m_fInit) { // how many rows fit into a page of memory? m_RowsPerPage = PAGE_SIZE/(m_cProp* sizeof(DWORD *)); // the number of pages in a chunk of rows m_NumberOfPages = (m_cProp * ROW_CHUNK * sizeof(DWORD *))/PAGE_SIZE + 1; // allocation size in bytes. m_BytesReserved = m_NumberOfPages*PAGE_SIZE; if (NULL == (m_PageMap = new BOOL[m_NumberOfPages])) return SetErrReturn(E_OUTOFMEMORY); m_fInit = TRUE; } // set page map to 0 - this keeps track of which pages are allocated MEMSET(m_PageMap, 0, m_NumberOfPages*sizeof(BOOL)); // reserve memory for an entire chunk - this call shouldn't happen very frequently. if (NULL == (m_ResultSet[m_Chunk] = (DWORD_PTR *) VirtualAlloc(NULL, m_BytesReserved, MEM_RESERVE, PAGE_READWRITE))) { #ifdef _DEBUG int Error = GetLastError( ); DPF1("CITResultSet::Reserve: MEM_RESERVE faild: %u", Error); #endif // _DEBUG return SetErrReturn(E_OUTOFMEMORY); } #ifdef _DEBUG MEMORY_BASIC_INFORMATION MemInfo; VirtualQuery(m_ResultSet[m_Chunk], &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)); #endif // _DEBUG m_RowsReserved += ROW_CHUNK; return S_OK; } // Wrapper for VirtualAlloc(... MEM_COMMIT...) // One possible optimization: replace w/ SEH instead // of maintaining a page map HRESULT WINAPI CITResultSet::Commit(LONG RowNum) { LONG LogicalRowNum; LONG PageNum; RealToLogical(RowNum, LogicalRowNum); PageNum = LogicalRowNum/m_RowsPerPage; // Only allocate if page hasn't been committed before // Note that allocated memory gets zero'd if (FALSE == m_PageMap[PageNum]) { // this will allocate a full page if (NULL == VirtualAlloc(&m_ResultSet[m_Chunk][ROW_CHUNK * PageNum], PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE) ) { // TODO: map relevant Win32 error to HRESULT... for now I'm assuming // we ran out of memory #ifdef _DEBUG int Error = GetLastError( ); DPF1("CITResultSet::Reserve: MEM_COMMIT faild: %u", Error); #endif // _DEBUG return SetErrReturn(E_OUTOFMEMORY); } g_iAlloc++; m_PageMap[PageNum] = TRUE; } return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Add | * Adds columns to result set, given a header containing pairs * of property ID followed by property type * * @parm LPVOID | lpvHdr | Buffer containing property ID/property pairs * * @rvalue S_OK | The columns were successfully added * * @xcomm * The format of lpvHdr is identical to the output format of * PorpertyList::SaveHeader. If PL:SaveHeader changes we * may need to modify this code to maintain compatability. * ********************************************************************/ STDMETHODIMP CITResultSet::Add(LPVOID lpvHdr) { LPBYTE pCurHdr = (LPBYTE) lpvHdr; DWORD dwPropID; DWORD dwType; DWORD dwProps; DWORD dwMax; DWORD iProp; // loop index m_cs.Lock(); // Number of properties in header MEMCPY(&dwProps, pCurHdr, sizeof(DWORD)); pCurHdr += sizeof(DWORD); dwMax = m_cProp + dwProps; if (dwMax >= MAX_COLUMNS) { m_cs.Unlock(); return (SetErrReturn(E_TOOMANYCOLUMNS)); } // For each property in header, create a column for (iProp = m_cProp; iProp < dwMax; iProp++) { // clear header MEMSET(&m_Header[iProp], NULL, sizeof(CHeader)); MEMCPY(&dwPropID, pCurHdr, sizeof(DWORD)); pCurHdr += sizeof(DWORD); MEMCPY(&dwType, pCurHdr, sizeof(DWORD)); pCurHdr += sizeof(DWORD); m_Header[iProp].dwPropID = dwPropID; m_Header[iProp].lpvData = NULL; m_Header[iProp].dwType = dwType; m_Header[iProp].Priority = PRIORITY_NORMAL; // default priority } // end for over properties in header m_cProp += dwProps; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Add | * Adds a column to the result set * * @parm PROPID | PropID | Property ID associated with column * @parm DWORD | dwDefautData | Default data value * @parm PRIORITY | Priority | Download priority of column (PRIORITY_LOW, PRIORITY_NORMAL, * or PRIORITY_HIGH) * * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_OK | The column was successfully added * * @comm This method is used to add a column for numerical properties. ********************************************************************/ STDMETHODIMP CITResultSet::Add(PROPID PropID, DWORD dwDefaultData, PRIORITY Priority) { if (m_cProp >= MAX_COLUMNS) return (SetErrReturn(E_TOOMANYCOLUMNS)); m_cs.Lock(); // clear header MEMSET(&m_Header[m_cProp], NULL, sizeof(CHeader)); // copy data m_Header[m_cProp].dwPropID = PropID; m_Header[m_cProp].dwValue = dwDefaultData; m_Header[m_cProp].dwType = TYPE_VALUE; m_Header[m_cProp].Priority = Priority; // default priority m_cProp++; // Wow! This is a lot of properties! ITASSERT (m_cProp < THEORETICAL_MAX_PROP); m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Add | * Adds a column to the result set * * @parm PROPID | PropID | Property ID associated with column * @parm LPCWSTR | lpDefaultData | Default value of string * @parm PRIORITY | Priority | Download priority of column (PRIORITY_LOW, PRIORITY_NORMAL, * or PRIORITY_HIGH) * * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_OK | The column was successfully added * * @comm This method is used to add a column for string properties. ********************************************************************/ STDMETHODIMP CITResultSet::Add(PROPID PropID, LPCWSTR lpszwDefault, PRIORITY Priority) { if (m_cProp >= MAX_COLUMNS) return (SetErrReturn(E_TOOMANYCOLUMNS)); m_cs.Lock(); // clear header MEMSET(&m_Header[m_cProp], NULL, sizeof(CHeader)); // copy data if (lpszwDefault) { LPBYTE pBuffer; DWORD cbData = (DWORD)(WSTRLEN(lpszwDefault) + 1) * sizeof(WCHAR); if (NULL == (pBuffer = (LPBYTE) BlockCopy(m_pMemPool, NULL, cbData + 4, 0))) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } MEMCPY(pBuffer + sizeof (DWORD), lpszwDefault, cbData); *(LPDWORD)pBuffer = cbData; m_Header[m_cProp].lpvData = pBuffer; } else m_Header[m_cProp].lpvData = NULL; m_Header[m_cProp].dwPropID = PropID; m_Header[m_cProp].dwType = TYPE_STRING; m_Header[m_cProp].Priority = Priority; // default priority m_cProp++; // Wow! This is a lot of properties! ITASSERT (m_cProp < THEORETICAL_MAX_PROP); m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Add | * Adds a column to the result set * * @parm PROPID | PropID | Property ID associated with column * @parm LPVOID | lpvDefaultData | Buffer containing default value of data * @parm DWORD | cbData | Size of buffer * @parm PRIORITY | Priority | Download priority of column (PRIORITY_LOW, PRIORITY_NORMAL, * or PRIORITY_HIGH) * * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_OK | The column was successfully added * * @comm This method is used to add a column for pointer properties. ********************************************************************/ STDMETHODIMP CITResultSet::Add(PROPID PropID, LPVOID lpvDefaultData, DWORD cbData, PRIORITY Priority) { if (m_cProp >= MAX_COLUMNS) return (SetErrReturn(E_TOOMANYCOLUMNS)); m_cs.Lock(); // clear header MEMSET(&m_Header[m_cProp], NULL, sizeof(CHeader)); // copy data if (lpvDefaultData) { LPBYTE pBuffer; if (NULL == (pBuffer = (LPBYTE) BlockCopy(m_pMemPool, NULL, cbData + 4, 0))) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } MEMCPY(pBuffer + 4, lpvDefaultData, cbData); *(LPDWORD)pBuffer = cbData; m_Header[m_cProp].lpvData = pBuffer; } else m_Header[m_cProp].lpvData = NULL; m_Header[m_cProp].dwPropID = PropID; m_Header[m_cProp].dwType = TYPE_POINTER; m_Header[m_cProp].Priority = Priority; // default priority m_cProp++; // Wow! This is a lot of properties! ITASSERT (m_cProp < THEORETICAL_MAX_PROP); m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | SetColumnPriority | * Sets the download priority for the given column in the result set * * @parm LONG | lColumnIndex | Index of column to set * @parm PRIORITY | Priority | Priority, which can be one of the following: * * @flag PRIORITY_LOW | Low priority * @flag PRIORITY_NORMAL | Normal priority * @flag PRIORITY_HIGH | High priority * * @rvalue E_NOTEXIST | Column does not exist * @rvalue S_OK | The priority was successfully set ********************************************************************/ STDMETHODIMP CITResultSet::SetColumnPriority(LONG lColumnIndex, PRIORITY ColumnPriority) { if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); m_cs.Lock(); m_Header[lColumnIndex].Priority = ColumnPriority; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | SetColumnHeap | * Sets the heap which DWORD values in this column point into. * * @parm LONG | lColumnIndex | Index of column to set * @parm LPVOID | lpvHeap | Pointer to the heap. * @parm PFNCOLHEAPFREE | pfnColHeapFree | * Pointer to a function which can be called to free the heap * when the result set is cleared or freed. * * * @rvalue E_NOTEXIST | Column does not exist * @rvalue S_OK | The heap was successfully set ********************************************************************/ STDMETHODIMP CITResultSet::SetColumnHeap(LONG lColumnIndex, LPVOID lpvHeap, PFNCOLHEAPFREE pfnColHeapFree) { HRESULT hr = S_OK; if (lColumnIndex < 0) return SetErrReturn(E_INVALIDARG); m_cs.Lock(); if (lColumnIndex >= m_cProp) hr = E_NOTEXIST; else if (m_Header[lColumnIndex].lpvHeap != NULL) hr = E_ALREADYINIT; if (SUCCEEDED(hr)) { m_Header[lColumnIndex].lpvHeap = lpvHeap; m_Header[lColumnIndex].pfnHeapFree = pfnColHeapFree; } m_cs.Unlock(); return (hr); } /******************************************************************** * @method STDMETHODIMP | IITResultSet | GetColumnPriority | * Gets the download priority for the given column in the result set * * @parm LONG | lColumnIndex | Index of column to get * @parm PRIORITY& | Priority | Priority, which can be one of the following: * * @flag PRIORITY_LOW | Low priority * @flag PRIORITY_NORMAL | Normal priority * @flag PRIORITY_HIGH | High priority * * @rvalue E_NOTEXIST | Column does not exist * @rvalue S_OK | The priority was successfully retrieved ********************************************************************/ STDMETHODIMP CITResultSet::GetColumnPriority(LONG lColumnIndex, PRIORITY& ColumnPriority) { if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); ColumnPriority = m_Header[lColumnIndex].Priority; return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | SetKeyProp | * Sets the property to be used as the key. * * @parm PROPID | KeyPropID | Property ID * * @rvalue S_OK | The key property was successfully set * ********************************************************************/ inline STDMETHODIMP CITResultSet::SetKeyProp(PROPID KeyPropID) { m_cs.Lock(); m_dwKeyProp = KeyPropID; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | GetKeyProp | * Retrieves the property used as the key * * @parm PROPID | KeyPropID | Property ID * * @rvalue S_OK | The key property was successfully retrieved * ********************************************************************/ inline STDMETHODIMP CITResultSet::GetKeyProp(PROPID& KeyPropID) { KeyPropID = m_dwKeyProp; return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Append | * Given header (prop ID and type) and data, this function forms a * row and appends it to the current result set * * @parm LPVOID | lpvHdr | Pointer to buffer containing header * @parm LPVOID | lpvData | Pointer to buffer containing data * * @rvalue E_INVALIDARG | Exceeded maximum number of rows * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_FALSE | No columns were found to set, but no failure occurred * @rvalue S_OK | The row was successfully filled in * * @xcomm * The format of lpvHdr is identical to the output format of * PorpertyList::SaveData. If PL:Savedata changes we may * need to modify this code to maintain compatability. * ********************************************************************/ STDMETHODIMP CITResultSet::Append(LPVOID lpvHdr, LPVOID lpvData) { return Set(m_AppendRow, lpvHdr, lpvData); } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Set | * Given header (prop ID and type) and data, this function puts * the information into the specified row in the result set * * @parm LONG | lRowIndex | Index of row in result set * @parm LPVOID | lpvHdr | Pointer to buffer containing header * @parm LPVOID | lpvData | Pointer to buffer containing data * * @rvalue E_INVALIDARG | Exceeded maximum number of rows * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_FALSE | No columns were found to set, but no failure occurred * @rvalue S_OK | The row was successfully filled in * * @xcomm * The format of lpvHdr is identical to the output format of * PorpertyList::SaveData. If PL:Savedata changes we may * need to modify this code to maintain compatability. * ********************************************************************/ STDMETHODIMP CITResultSet::Set(LONG lRowIndex, LPVOID lpvHdr, LPVOID lpvData) { LPBYTE pCurHdr = (LPBYTE) lpvHdr; LPBYTE pCurData = (LPBYTE) lpvData; LPBYTE pBitField = (LPBYTE) lpvData; DWORD dwProps; DWORD dwPropID; DWORD dwType; LPBYTE pBuffer; DWORD cbSize; LONG lColumn; HRESULT hr = S_FALSE; m_cs.Lock(); // Reserve memory if necessary if (lRowIndex >= m_RowsReserved) { if ( FAILED(Reserve()) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } } // Commit memory if ( FAILED(Commit(lRowIndex)) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } LONG LogicalRow; RealToLogical(lRowIndex, LogicalRow); LONG nRow = LogicalRow * m_cProp; LONG nChunk = lRowIndex/ROW_CHUNK; // Number of properties in header MEMCPY(&dwProps, pCurHdr, sizeof(DWORD)); pCurHdr += sizeof(DWORD); // Shift pCurData up to leave room for the property bit field pCurData += (dwProps/8 + 1); // For each property in header, look for matching result set column for (DWORD iProp = 0; iProp < dwProps; iProp++) { MEMCPY(&dwPropID, pCurHdr, sizeof(DWORD)); pCurHdr += sizeof(DWORD); MEMCPY(&dwType, pCurHdr, sizeof(DWORD)); pCurHdr += sizeof(DWORD); // UNDONE: Figure out an optimization. If columns match up w/ header, // then by the time we get to the end of the header, we'll have to loop // over ALL the columns just to find which the column that matches the // property ID. The problem is, it's not guaranteed that the columns // match up exactly; the user could set them in any order. // Is there data for this property? BYTE WhichBit = (0x80 >> (iProp % 8)); int BitSet = pBitField[iProp/8] & WhichBit; HRESULT hrFound = GetColumnFromPropID(dwPropID, lColumn); if (SUCCEEDED(hrFound)) hr = S_OK; // found at least one column if (BitSet) { if (TYPE_VALUE == dwType) { // copy data value if (SUCCEEDED(hrFound)) m_ResultSet[nChunk][nRow + lColumn] = *(LPDWORD) pCurData; // if user didn't specify column, we still need to skip the data for // this property pCurData += sizeof(DWORD); } else { // get size MEMCPY(&cbSize, pCurData, sizeof(DWORD)); pCurData += sizeof(DWORD); if (SUCCEEDED(hrFound)) { if (NULL == (pBuffer = (LPBYTE) BlockCopy(m_pMemPool, NULL, cbSize + 4, 0))) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } // append size to data MEMCPY(pBuffer + 4, pCurData, cbSize); *(LPDWORD)pBuffer = cbSize; m_ResultSet[nChunk][nRow + lColumn] = (DWORD_PTR) pBuffer; } pCurData += cbSize; } } else { // No, so use default if (SUCCEEDED(hrFound)) m_ResultSet[nChunk][nRow + lColumn] = (DWORD_PTR) m_Header[lColumn].lpvData; } } // end for over properties in header // Always maintain one past the last row as the append row // unless we didn't put anything in the result set if ( (lRowIndex >= m_AppendRow) && (S_OK == hr)) m_AppendRow = lRowIndex + 1; m_cs.Unlock(); return hr; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Set | * Sets the property in the specified row to the property value. * * @parm LONG | lRowIndex | Row in which property belongs * @parm LONG | lColumnIndex | Column in which property belongs * @parm DWORD | dwData | Data to set * * @rvalue E_INVALIDARG | Exceed maximum row count * @rvalue E_NOTEXIST | Column does not exist * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_OK | The row was successfully set ********************************************************************/ STDMETHODIMP CITResultSet::Set(LONG lRowIndex, LONG lColumnIndex, DWORD dwData) { if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); m_cs.Lock(); // Reserve memory if necessary if (lRowIndex >= m_RowsReserved) { if ( FAILED(Reserve()) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } } // Commit memory if ( FAILED(Commit(lRowIndex)) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } LONG LogicalRow; RealToLogical(lRowIndex, LogicalRow); LONG nRow = LogicalRow * m_cProp; LONG nChunk = lRowIndex/ROW_CHUNK; m_ResultSet[nChunk][nRow + lColumnIndex] = dwData; // always maintain one past the last row as the append row if (lRowIndex >= m_AppendRow) m_AppendRow = lRowIndex + 1; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Set | * Sets the property in the specified row to the property value. * * @parm LONG | lRowIndex | Row in which property belongs * @parm LONG | lColumnIndex | Column in which property belongs * @parm DWORD | dwData | Data to set * * @rvalue E_INVALIDARG | Exceed maximum row count * @rvalue E_NOTEXIST | Column does not exist * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_OK | The row was successfully set ********************************************************************/ STDMETHODIMP CITResultSet::Set(LONG lRowIndex, LONG lColumnIndex, DWORD_PTR dwData) { if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); m_cs.Lock(); // Reserve memory if necessary if (lRowIndex >= m_RowsReserved) { if ( FAILED(Reserve()) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } } // Commit memory if ( FAILED(Commit(lRowIndex)) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } LONG LogicalRow; RealToLogical(lRowIndex, LogicalRow); LONG nRow = LogicalRow * m_cProp; LONG nChunk = lRowIndex/ROW_CHUNK; m_ResultSet[nChunk][nRow + lColumnIndex] = dwData; // always maintain one past the last row as the append row if (lRowIndex >= m_AppendRow) m_AppendRow = lRowIndex + 1; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Set | * Sets the property in the specified row to the property value. * * @parm LONG | lRowIndex | Row in which property belongs * @parm LPCWSTR | lpszString | Data to set * * @rvalue E_INVALIDARG | Exceed maximum row count * @rvalue E_NOTEXIST | Column does not exist * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_OK | The row was successfully set ********************************************************************/ STDMETHODIMP CITResultSet::Set(LONG lRowIndex, LONG lColumnIndex, LPCWSTR lpszString) { if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); m_cs.Lock(); // Reserve memory if necessary if (lRowIndex >= m_RowsReserved) { if ( FAILED(Reserve()) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } } // Commit memory if ( FAILED(Commit(lRowIndex)) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } LONG LogicalRow; RealToLogical(lRowIndex, LogicalRow); LONG nRow = LogicalRow * m_cProp; LONG nChunk = lRowIndex/ROW_CHUNK; DWORD cbData = 0; if (lpszString) cbData = (DWORD) (2*(WSTRLEN(lpszString) + 1)); LPBYTE pBuffer = (LPBYTE) BlockCopy(m_pMemPool, NULL, cbData + sizeof (DWORD), 0); if (NULL == pBuffer) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } MEMCPY(pBuffer + 4, lpszString, cbData); *(LPDWORD)pBuffer = cbData; m_ResultSet[nChunk][nRow + lColumnIndex] = (DWORD_PTR) pBuffer; // always maintain one past the last row as the append row if (lRowIndex >= m_AppendRow) m_AppendRow = lRowIndex + 1; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Set | * Sets the property in the specified row to the property value. * * @parm LONG | lRowIndex | Row in which property belongs * @parm DWORD | dwData | Data to set * * @rvalue E_INVALIDARG | Exceed maximum row count * @rvalue E_NOTEXIST | Column does not exist * @rvalue E_OUTOFMEMORY | Memory allocation failed * @rvalue S_OK | The row was successfully set ********************************************************************/ STDMETHODIMP CITResultSet::Set(LONG lRowIndex, LONG lColumnIndex, LPVOID lpvData, DWORD cbData) { if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); m_cs.Lock(); // Reserve memory if necessary if (lRowIndex >= m_RowsReserved) { if ( FAILED(Reserve()) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } } // Commit memory if ( FAILED(Commit(lRowIndex)) ) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } LONG LogicalRow; RealToLogical(lRowIndex, LogicalRow); LONG nRow = LogicalRow * m_cProp; LONG nChunk = lRowIndex/ROW_CHUNK; LPBYTE pBuffer = (LPBYTE) BlockCopy(m_pMemPool, NULL, cbData + sizeof (DWORD), 0); if (NULL == pBuffer) { m_cs.Unlock(); return SetErrReturn(E_OUTOFMEMORY); } *(LPDWORD)pBuffer = cbData; MEMCPY(pBuffer + sizeof (DWORD), lpvData, cbData); m_ResultSet[nChunk][nRow + lColumnIndex] = (DWORD_PTR) pBuffer; // always maintain one past the last row as the append row if (lRowIndex >= m_AppendRow) m_AppendRow = lRowIndex + 1; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Get | * Gets the property in the specified row and column and fills the given * property object. * * @parm LONG | lRowIndex | Row in which property belongs * @parm LONG | lColumnIndex | Column in which property belongs * @parm CProperty& | Prop | Property object to fill * * @rvalue E_NOTEXIST | The row or column does not exist in the row set * @rvalue S_OK | The row was successfully retrieved * ********************************************************************/ STDMETHODIMP CITResultSet::Get(LONG lRowIndex, LONG lColumnIndex, CProperty& Prop) { if (lRowIndex >= m_AppendRow || lColumnIndex >= m_cProp) return SetErrReturn(E_NOTEXIST); LONG LogicalRow; RealToLogical(lRowIndex, LogicalRow); LONG nRow = LogicalRow * m_cProp; LONG nChunk = lRowIndex/ROW_CHUNK; Prop.dwPropID = m_Header[lColumnIndex].dwPropID; if (TYPE_VALUE == (Prop.dwType = m_Header[lColumnIndex].dwType)) { // For data types, we have no way of knowing how to return // the default, so we just return 0 if this cell was never filled in Prop.lpvData = (LPVOID) m_ResultSet[nChunk][nRow+lColumnIndex]; Prop.dwValue = (DWORD) m_ResultSet[nChunk][nRow+lColumnIndex]; Prop.cbData = sizeof(DWORD); } else { LPBYTE pBuffer = (LPBYTE) m_ResultSet[nChunk][nRow+lColumnIndex]; if (pBuffer) { Prop.cbData = *(LPDWORD)pBuffer; Prop.lpvData = pBuffer + sizeof (DWORD); } else { // there's nothing there, so return default if (m_Header[lColumnIndex].lpvData) { Prop.cbData = *(LPDWORD) m_Header[lColumnIndex].lpvData; Prop.lpvData = (LPDWORD)(m_Header[lColumnIndex].lpvData) + 1; } else { // default was specified as NULL, so we make sure we // return that Prop.cbData = 0; Prop.lpvData = NULL; } } } return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | GetColumnCount | * Gets number of columns in result set * * @parm LONG& | lNumberOfColumns | Number of columns * * @rvalue S_OK | The number of columns was successfully retrieved * ********************************************************************/ inline STDMETHODIMP CITResultSet::GetColumnCount(LONG& lNumberOfColumns) { lNumberOfColumns = m_cProp; return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | GetRowCount | * Gets number of rows in result set * * @parm LONG& | lNumberOfRows | Number of rows * * @rvalue S_OK | The number of rows was successfully retrieved * ********************************************************************/ inline STDMETHODIMP CITResultSet::GetRowCount(LONG& lNumberOfRows) { lNumberOfRows = m_AppendRow; return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | GetColumn| * Gets property ID and default value associated with a column. * * @parm LONG | lColumnIndex | Column number * @parm PROPID | PropID | Property ID * @parm DWORD | dwType | Property Type (TYPE_VALUE, TYPE_POINTER, TYPE_STRING) * @parm LPVOID& | lpvDefaultValue | Default value * @parm DWORD& | cbSize |Length of data (in bytes) * @parm PRIORITY& | Priority | Column priority * * @rvalue E_NOTEXIST | Column does not exist * @rvalue S_OK | The column was successfully retrieved * ********************************************************************/ STDMETHODIMP CITResultSet::GetColumn(LONG lColumnIndex, PROPID& PropID, DWORD& dwType, LPVOID& lpvDefaultValue, DWORD& cbSize, PRIORITY& Priority) { // check against invalid lColumnIndex if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); PropID = m_Header[lColumnIndex].dwPropID; dwType = m_Header[lColumnIndex].dwType; // it could be NULL if (m_Header[lColumnIndex].lpvData) { lpvDefaultValue = (LPBYTE) m_Header[lColumnIndex].lpvData + sizeof (DWORD); if (TYPE_VALUE == dwType) cbSize = sizeof(DWORD); else cbSize = *((LPDWORD)m_Header[lColumnIndex].lpvData); } else { lpvDefaultValue = NULL; cbSize = 0; } Priority = m_Header[lColumnIndex].Priority; return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | GetColumn| * Gets property ID for a given column index. * * @parm LONG | lColumnIndex | Column number * @parm PROPID | PropID | Property ID * * @rvalue E_NOTEXIST | Column does not exist * @rvalue S_OK | The column was successfully retrieved * ********************************************************************/ STDMETHODIMP CITResultSet::GetColumn(LONG lColumnIndex, PROPID& PropID) { // check against invalid lColumnIndex if (lColumnIndex >= m_cProp || lColumnIndex < 0) return SetErrReturn(E_NOTEXIST); PropID = m_Header[lColumnIndex].dwPropID; return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | GetColumnFromPropID | * Gets column index for which a property ID is associated * * @parm PROPID | PropID | Property ID * @parm LONG& | lColumnIndex | Column index * * @rvalue E_NOTEXIST | The column does not exist * @rvalue S_OK | The column index was successfully returned * ********************************************************************/ STDMETHODIMP CITResultSet::GetColumnFromPropID(PROPID PropID, LONG& lColumnIndex) { // Loop over all columns, looking for match for (LONG iIndex = 0; iIndex < m_cProp; iIndex++) { if (PropID == m_Header[iIndex].dwPropID) { // Found it lColumnIndex = iIndex; return S_OK; } } return SetErrReturn(E_NOTEXIST); } /******************************************************************** * @method STDMETHODIMP | IITResultSet | AppendRows| * Appends rows from the given source resultset * * @parm IITResultSet* | pResSrc | Source resultset * @parm LONG | lRowSrcFirst | Source row number to start the copy * @parm LONG | cSrcRows | Number of rows to append * @parm LONG& | lRowFirstDest | First destination row number appended * * @rvalue E_OUTOFMEMORY | Not enough memory to append to the destination * @rvalue S_OK | All rows were successfully appended * ********************************************************************/ // UNDONE: this is terribly inefficient. We need to fix resultsets so that rows can // be copied more easily STDMETHODIMP CITResultSet::AppendRows(IITResultSet* pResSrc, LONG lRowSrcFirst, LONG cSrcRows, LONG& lRowDestFirst) { LONG lColumn; HRESULT hr = E_NOTEXIST; PROPID PropID; LONG lDestColumn; CProperty Prop; lRowDestFirst = m_AppendRow; m_cs.Lock(); pResSrc->GetColumnCount(lColumn); // Loop over columns in source result set for (LONG iProp = 0; iProp < lColumn; iProp++) { // get column in dest result set pResSrc->GetColumn(iProp, PropID); if (FAILED(GetColumnFromPropID(PropID, lDestColumn))) continue; hr = S_OK; // there's at least one matching column // Loop over rows in input result set LONG iRow; // loop index switch( m_Header[lDestColumn].dwType ) { case TYPE_VALUE: for (iRow = 0; iRow < cSrcRows; iRow++) { pResSrc->Get(lRowSrcFirst + iRow, iProp, Prop); Set(lRowDestFirst + iRow, lDestColumn, Prop.dwValue); } break; case TYPE_STRING: for (iRow = 0; iRow < cSrcRows; iRow++) { pResSrc->Get(lRowSrcFirst + iRow, iProp, Prop); Set(lRowDestFirst + iRow, lDestColumn, Prop.lpszwData); } break; case TYPE_POINTER: for (iRow = 0; iRow < cSrcRows; iRow++) { pResSrc->Get(lRowSrcFirst + iRow, iProp, Prop); Set(lRowDestFirst + iRow, lDestColumn, Prop.lpvData, Prop.cbData); } break; } } m_cs.Unlock(); return hr; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Copy | * Copies the rows associated with the columns set in the * given result set. This method can be used to take a larger result * set and reduce it Passing an empty resultset will cause all columns * and rows to be added and copied from the source resultset. * * @parm IITResultSet* | pRSCopy | Result set object containing copied * rows. * * @rvalue E_NOTEXIST | No columns match the input result set * * @rvalue S_OK | The rows were successfully copied. * ********************************************************************/ STDMETHODIMP CITResultSet::Copy(IITResultSet* pRSCopy) { LONG lColumn; HRESULT hr; pRSCopy->GetColumnCount(lColumn); if (0L == lColumn) { LONG cCols; LONG iCol; // add all columns from the source to the dest GetColumnCount(cCols); for (iCol = 0; iCol < cCols; iCol++) { PROPID pid; DWORD dwType; LPVOID lpv; DWORD cbSize; PRIORITY pri; // UNDONE: get rid of this dwType stuff // UNDONE: simpler place to put column info (i.e. struct/class) GetColumn(iCol, pid, dwType, lpv, cbSize, pri); if (dwType == TYPE_VALUE) { if (FAILED(hr = pRSCopy->Add(pid, (DWORD)0, PRIORITY_NORMAL))) return hr; } else { if (FAILED(hr= pRSCopy->Add(pid, (LPWSTR)0, PRIORITY_NORMAL))) return hr; } } lColumn = cCols; } PROPID PropID; LONG lInputColumn; CProperty Prop; m_cs.Lock(); hr = E_NOTEXIST; // Loop over columns in output result set for (LONG iProp = 0; iProp < lColumn; iProp++) { // get column in input result set pRSCopy->GetColumn(iProp, PropID); if (FAILED(GetColumnFromPropID(PropID, lInputColumn))) continue; hr = S_OK; // there's at least one matching column // Loop over rows in input result set LONG iRow; // loop index switch( m_Header[lInputColumn].dwType ) { case TYPE_VALUE: for (iRow = 0; iRow < m_AppendRow; iRow++) { Get(iRow, lInputColumn, Prop); pRSCopy->Set(iRow, iProp, Prop.dwValue); } break; case TYPE_STRING: for (iRow = 0; iRow < m_AppendRow; iRow++) { Get(iRow, lInputColumn, Prop); pRSCopy->Set(iRow, iProp, Prop.lpszwData); } break; case TYPE_POINTER: for (iRow = 0; iRow < m_AppendRow; iRow++) { Get(iRow, lInputColumn, Prop); pRSCopy->Set(iRow, iProp, Prop.lpvData, Prop.cbData); } break; } } m_cs.Unlock(); return hr; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | Clear | * Frees all memory associated with a result set * * @rvalue S_OK | The result set was successfully cleared * * @comm This method can be called to clear a result set without * requiring the set to be destroyed before being used again. ********************************************************************/ STDMETHODIMP CITResultSet::Clear() { LONG iProp; m_cs.Lock(); // Free any column heaps which may have been set. for (iProp = 0; iProp < m_cProp; iProp++) { LPVOID lpvHeap; PFNCOLHEAPFREE pfnHeapFree; if ((lpvHeap = m_Header[iProp].lpvHeap) != NULL && (pfnHeapFree = m_Header[iProp].pfnHeapFree) != NULL) { (*pfnHeapFree)(lpvHeap); } } ClearRows(); m_cProp = 0; m_cs.Unlock(); return S_OK; } /******************************************************************** * @method STDMETHODIMP | IITResultSet | ClearRows | * Frees all memory associated with a result set, without * resetting column information * * @rvalue S_OK | The result set was successfully cleared * * @comm This method can be called to clear a result set without * requiring the set to be destroyed before being used again. ********************************************************************/ STDMETHODIMP CITResultSet::ClearRows() { m_cs.Lock(); // Free page map if (m_PageMap) { delete m_PageMap; m_PageMap = NULL; } // Decommit and release virtual memory FreeMem(); // Reset memory pool if (m_pMemPool) BlockReset(m_pMemPool); // Reset member data m_NumberOfPages = 0; m_fInit = FALSE; m_RowsReserved = 0; m_AppendRow = 0; m_Chunk = -1; m_cs.Unlock(); return S_OK; } // Wrapper for VirtualFree(MEM_DECOMMIT) and VirtualFree(MEM_RELEASE) HRESULT WINAPI CITResultSet::FreeMem() { BOOL fRet; int iLoop; // Decommit result set - remember that it's an array of chunks (of rows) // so we have to loop over all the chunks // erinfox: bug fix, iLoop <= m_Chunk because m_Chunk is numbered from 0! for (iLoop = 0; iLoop <= m_Chunk; iLoop++) { g_iFreed++; fRet = VirtualFree(m_ResultSet[iLoop], m_BytesReserved, MEM_DECOMMIT); ITASSERT(TRUE == fRet); } // Then release it for (iLoop = 0; iLoop <= m_Chunk; iLoop++) { fRet = VirtualFree(m_ResultSet[iLoop], 0, MEM_RELEASE); ITASSERT(TRUE == fRet); } return S_OK; } STDMETHODIMP CITResultSet::Free() { return E_NOTIMPL; } //////////////////////// Asynchronous ////////////////////////////////////// STDMETHODIMP CITResultSet::IsCompleted() { return E_NOTIMPL; } STDMETHODIMP CITResultSet::Cancel() { return E_NOTIMPL; } STDMETHODIMP CITResultSet::Pause(BOOL fPause) { return E_NOTIMPL; } STDMETHODIMP CITResultSet::GetRowStatus(LONG lRowFirst, LONG cRows, LPROWSTATUS lpRowStatus) { return E_NOTIMPL; } STDMETHODIMP CITResultSet::GetColumnStatus(LPCOLUMNSTATUS lpColStatus) { return E_NOTIMPL; }