perfsrv/memspy/Engine/Source/Helpers/MemSpyEngineHelperFbServ.cpp
author hgs
Tue, 26 Oct 2010 16:20:32 +0300
changeset 62 1c2bb2fc7c87
parent 51 98307c651589
permissions -rw-r--r--
201043

/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

#include <memspy/engine/memspyenginehelperfbserv.h>

// System includes
#include <s32mem.h>
#include <f32file.h>
#include <e32rom.h>

// Driver includes
#include <memspy/driver/memspydriverclient.h>

// User includes
#include <memspy/engine/memspyengine.h>
#include <memspy/engine/memspyengineutils.h>
#include <memspy/engine/memspyengineoutputsink.h>
#include <memspy/engine/memspyengineobjectprocess.h>
#include <memspy/engine/memspyengineobjectthread.h>
#include <memspy/engine/memspyengineobjectcontainer.h>

// Literal constants
_LIT( KMemSpyEngineFBServComma, ", " );
_LIT( KMemSpyEngineFBServExportFileName, "Image_%08x_%02d_bpp_(%d x %d).bmp");
_LIT( KMemSpyEngineFBSLargeChunkName, "FbsLargeChunk" );
_LIT( KMemSpyEngineFBSSharedChunkName, "FbsSharedChunk" );


CMemSpyEngineHelperFbServ::CMemSpyEngineHelperFbServ( CMemSpyEngine& aEngine )
:   iEngine( aEngine )
    {
    }


CMemSpyEngineHelperFbServ::~CMemSpyEngineHelperFbServ()
    {
    iEngine.Driver().WalkHeapClose();
    }


void CMemSpyEngineHelperFbServ::ConstructL()
    {
    }


CMemSpyEngineHelperFbServ* CMemSpyEngineHelperFbServ::NewL( CMemSpyEngine& aEngine )
    {
    CMemSpyEngineHelperFbServ* self = new(ELeave) CMemSpyEngineHelperFbServ( aEngine );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );
    return self;
    }


TBool CMemSpyEngineHelperFbServ::IsLargeBitmapChunk( const TMemSpyDriverChunkInfo& aChunkInfo )
    {
    TBool ret = EFalse;
    //
    if  ( aChunkInfo.iType == EMemSpyDriverChunkTypeGlobal )
        {
        ret = ( aChunkInfo.iName.FindF( KMemSpyEngineFBSLargeChunkName ) >= 0 );
        }
    //
    return ret;
    }


TBool CMemSpyEngineHelperFbServ::IsSharedBitmapChunk( const TMemSpyDriverChunkInfo& aChunkInfo )
    {
    TBool ret = EFalse;
    //
    if  ( aChunkInfo.iType == EMemSpyDriverChunkTypeGlobal )
        {
        ret = ( aChunkInfo.iName.FindF( KMemSpyEngineFBSSharedChunkName ) >= 0 );
        }
    //
    return ret;
    }


EXPORT_C void CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL( RArray<TInt>& aHandles )
    {
    TInt fontConCount = 0;
    TInt bitmapConCount =0;
    //RArray<TInt> dirtyHandles;
    //CleanupClosePushL( dirtyHandles );
    GetArrayOfBitmapHandlesL( aHandles, bitmapConCount, fontConCount );
    //CleanupStack::PopAndDestroy( &dirtyHandles );
    }


void CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL( RArray<TInt>& aHandles, TInt& aBitmapConCount, TInt& aFontConCount )
    {
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - START") );
    CMemSpyThread& fbServThread = IdentifyFbServThreadL();

    // Suspend the process
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - got FBServ thread, suspending it..."));
    iEngine.ProcessSuspendLC( fbServThread.Process().Id() );

    // Get the heap cell data for the correct object (CFbTop)
    TAny* cellAddress = NULL;
    HBufC8* cellData = LocateCFbTopHeapCellDataLC( fbServThread, cellAddress );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - got CFbTop cell data: %d bytes", cellData->Length()));

    // Work out the offset to the bitmap address handles array
    const TUint offsetBitmapCon = OffsetToCObjectConBitmapCon();
    const TUint offsetFontCon = OffsetToCObjectConFontCon();
    const TUint offsetHandleArray = OffsetToBitmapHandleArray();
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - offsetBitmapCon:   %d", offsetBitmapCon ));
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - offsetFontCon:     %d", offsetFontCon ));
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - offsetHandleArray: %d", offsetHandleArray ));

    // Create stream for reading heap data (makes life a bit easier).
    RDesReadStream stream( *cellData );
    CleanupClosePushL( stream );
    
    // We need to skip over the preamble to the start of the actual data.
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - skipping %d bytes [to start of bitmap con]...", offsetBitmapCon ));
    (void) stream.ReadL( offsetBitmapCon ); // skip this much

    // Read CFbTop::iBitmapCon cell address
    TAny* cellAddresBitmapCon = reinterpret_cast<TAny*>( stream.ReadUint32L() );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - cellAddresBitmapCon: 0x%08x", cellAddresBitmapCon ));
    TInt bitmapConAllocated = 0;
    RArray<TAny*> bitmapConAddresses;
    CleanupClosePushL( bitmapConAddresses );
    ReadCObjectConInfoL( cellAddresBitmapCon, bitmapConAddresses, aBitmapConCount, bitmapConAllocated );
    CleanupStack::PopAndDestroy( &bitmapConAddresses );

    // Read CFbTop::iFontCon cell address
    TAny* cellAddresFontCon = reinterpret_cast<TAny*>( stream.ReadUint32L() );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - cellAddresFontCon: 0x%08x", cellAddresFontCon ));
    TInt fontConAllocated = 0;
    RArray<TAny*> fontConAddresses;
    CleanupClosePushL( fontConAddresses );
    ReadCObjectConInfoL( cellAddresFontCon, fontConAddresses, aFontConCount, fontConAllocated );
    CleanupStack::PopAndDestroy( &fontConAddresses );

    // Skip forwards to the bitmap handle array info
    const TUint skipRemaining = ( offsetHandleArray - offsetFontCon );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - skipRemaining: %d", skipRemaining ));
    (void) stream.ReadL( skipRemaining ); // skip this much

    // The layout of RPointerArrayBase is as follows:
    //
    //  TInt iCount;
	//  TAny** iEntries;
	//  TInt iAllocated;
	//  TInt iGranularity;
	//  TInt iSpare1;
	//  TInt iSpare2;

    // Now read the next 4 bytes. This is the number of allocated array entries.
    const TInt arrayEntryCount = stream.ReadInt32L();
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - arrayEntryCount: %d", arrayEntryCount ));

    // The next four bytes are the address of the cell we are interested in.
    cellAddress = reinterpret_cast<TAny*>( stream.ReadUint32L() );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - cellAddress: 0x%08x", cellAddress ));
    
    // The next four bytes are the allocated count
    const TInt arrayAlloctedCount = stream.ReadInt32L();
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - arrayAlloctedCount: %d", arrayAlloctedCount ));
    
    // Validate
    if  ( arrayEntryCount > arrayAlloctedCount || arrayEntryCount < 0 || arrayAlloctedCount < 0 )
        {
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - invalid array details => Leave with KErrCorrupt"));
        User::Leave( KErrCorrupt );
        }
    
    // Clean up - don't need this data anymore. Real data is in another cell
    CleanupStack::PopAndDestroy( 2, cellData );  // stream & cellData

    // Now obtain the heap cell data for the actual array itself.
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - trying to find bitmap array data..."));
    cellData = LocateBitmapArrayHeapCellDataLC( cellAddress, arrayAlloctedCount );
    
    // Parse the cell data in order to obtain the bitmap handles.
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - trying to extract bitmap handles..."));
    ParseCellDataAndExtractHandlesL( *cellData, aHandles, arrayEntryCount );
    
    // Tidy up
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - resuming process..."));
    CleanupStack::PopAndDestroy( cellData );
 
    // Close heap walker & resume process
    iEngine.Driver().WalkHeapClose();
    CleanupStack::PopAndDestroy();
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetArrayOfBitmapHandlesL() - END"));
    }


void CMemSpyEngineHelperFbServ::GetBitmapInfoL( TInt aHandle, TMemSpyEngineFBServBitmapInfo& aInfo )
    {
    CFbsBitmap* bitmap = GetBitmapInfoLC( aHandle, aInfo );
    CleanupStack::PopAndDestroy( bitmap );
    }
 

CFbsBitmap* CMemSpyEngineHelperFbServ::GetBitmapInfoLC( TInt aHandle, TMemSpyEngineFBServBitmapInfo& aInfo )
    {
    CFbsBitmap* bitmap = new(ELeave) CFbsBitmap();
    CleanupStack::PushL( bitmap );
    const TInt error = bitmap->Duplicate( aHandle );
    User::LeaveIfError( error );

    // Basic info from bitmap itself
    aInfo.iHandle = aHandle;
    aInfo.iDisplayMode = bitmap->DisplayMode();

    // Extended info comes from header
    const SEpocBitmapHeader header( bitmap->Header() );
    
    aInfo.iSizeInPixels = header.iSizeInPixels;
    aInfo.iCompressionType = header.iCompression;
    aInfo.iColor = header.iColor;
    aInfo.iBitsPerPixel = header.iBitsPerPixel;
    aInfo.iSizeInBytes = header.iBitmapSize;

    // Flags
    aInfo.iFlags = TMemSpyEngineFBServBitmapInfo::EFlagsNone;
    if  ( bitmap->IsRomBitmap() )
        {
        aInfo.iFlags |= TMemSpyEngineFBServBitmapInfo::EFlagsIsRomBitmap;
        }
     if  ( bitmap->IsLargeBitmap() )
        {
        aInfo.iFlags |= TMemSpyEngineFBServBitmapInfo::EFlagsIsLarge;
        }
    if  ( bitmap->IsCompressedInRAM() )
        {
        aInfo.iFlags |= TMemSpyEngineFBServBitmapInfo::EFlagsIsCompressedInRam;
        }
    else
        {
        // Can only do this if not compressed
        if  ( bitmap->IsMonochrome() )
             {
             aInfo.iFlags |= TMemSpyEngineFBServBitmapInfo::EFlagsIsMonochrome;
             }
        }
 
    return bitmap;
    }


CMemSpyThread& CMemSpyEngineHelperFbServ::IdentifyFbServThreadL()
    {
    _LIT(KFbServThreadName, "FbServ::!Fontbitmapserver");
    //
    CMemSpyThread* thread = NULL;
    CMemSpyProcess* process = NULL;

    // Get the fbserv thread object
    CMemSpyEngineObjectContainer& container = iEngine.Container();
    User::LeaveIfError( container.ProcessAndThreadByFullName( KFbServThreadName, process, thread ) );
    //
    return *thread;
    }


HBufC8* CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC( CMemSpyThread& aFbServThread, TAny*& aCellAddress )
    {
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC() - START"));

    // We'll start looking for the CFbTop object at cell index 0, then we'll
    // continue to a maximum of cell index 10. These are not actual allocation
    // numbers, they are just cell indicies within FBServ's heap.
    // Realistically, the cell should be the 7th.
    const TInt KFbServWillingToStartAtCellIndex = 0;
    const TInt KFbServWillingToSearchUntilCellIndex = 10;

    // We know the size of the cell is pretty big, since it contains a large 
    // hash-map object (4096 elements by default, 4 bytes big => 16k). We'll
    // not try to retrieve more than 18k of data.
    const TInt KFbServExpectedMinimumCellSize = 14*1024;
    const TInt KFbServExpectedMaximumCellSize = 18*1024;

    // This is what we'll return, if we find it...
    aCellAddress = NULL;
    HBufC8* heapCellData = NULL;

    // Get the heap info - we need this for verification purposes
    TMemSpyHeapInfo info;
    TInt err = iEngine.Driver().GetHeapInfoUser( info, aFbServThread.Id() );
    if ( err == KErrNone && info.Type() == TMemSpyHeapInfo::ETypeUnknown )
        {
        err = KErrNotSupported;
        }
    User::LeaveIfError( err );

    // Now walk the heap!
    err = iEngine.Driver().WalkHeapInit( aFbServThread.Id() );
    if  ( err == KErrNone )
        {
        TMemSpyDriverCellType cellType;
        TAny* cellAddress;
        TInt cellLength;
        TInt cellNestingLevel;
        TInt cellAllocationNumber;
        TInt cellHeaderSize;
        TAny* cellPayloadAddress;
        //
        for( TInt cellIndex = KFbServWillingToStartAtCellIndex; err == KErrNone && cellIndex < KFbServWillingToSearchUntilCellIndex; cellIndex++ )
            {
            err = iEngine.Driver().WalkHeapNextCell( aFbServThread.Id(), cellType, cellAddress, cellLength, cellNestingLevel, cellAllocationNumber, cellHeaderSize, cellPayloadAddress );
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC() - cellIndex[%d] err: %d, cellLength: %d, cellAllocationNumber: %d, cellType: %d", cellIndex, err, cellLength, cellAllocationNumber, cellType));

            if  ( err == KErrNone && (cellType & EMemSpyDriverAllocatedCellMask))
                {
                // We know we are looking for a relatively large *allocated* cell.
                if  ( cellLength >= KFbServExpectedMinimumCellSize && cellLength <= KFbServExpectedMaximumCellSize )
                    {
                    const TInt payloadLength = cellLength;

                    // This is *probably* the right cell. Let's get the data and check.
                    HBufC8* data = HBufC8::NewLC( payloadLength );
                    TPtr8 pData( data->Des() );
                    //
                    err = iEngine.Driver().WalkHeapReadCellData( cellAddress, pData, payloadLength );
                    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC() - data fetch returned error: %d", err));

                    if  ( err == KErrNone )
                        {
                        //_LIT(KHeapDumpDataFormat, "%S");
                        //iEngine.Sink().OutputBinaryDataL( KHeapDumpDataFormat, pData.Ptr(), (const TUint8*) cellAddress, pData.Length() );
                    
                        // Check the data
                        const TUint heapMaxSize = info.AsRHeap().MetaData().iMaxHeapSize;
                        const TUint heapBaseAddress = (TUint) info.AsRHeap().MetaData().ChunkBaseAddress();
                        const TBool correctHeapCellLocated = VerifyCorrectHeapCellL( *data, cellAddress, cellPayloadAddress, heapBaseAddress, heapMaxSize );
                        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC() - verified: %d", correctHeapCellLocated));

                        if  ( correctHeapCellLocated )
                            {
                            aCellAddress = cellAddress;
                            heapCellData = data;
                            CleanupStack::Pop( data );
                            break;
                            }
                        else
                            {
                            // Not the right cell. Keep going...
                            CleanupStack::PopAndDestroy( data );
                            }
                        }
                    }
                }
            }

        // NB: don't do this here - 
        // iEngine.Driver().WalkHeapClose();
        // It gets done after all reading is complete
        }

    if  ( heapCellData == NULL )
        {
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC() - END - didn't find the right cell => KErrNotFound"));
        User::Leave( KErrNotFound );
        }
    //
    CleanupStack::PushL( heapCellData );

    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC() - END - everything okay, cell is: 0x%08x", aCellAddress));
    return heapCellData;
    }


void CMemSpyEngineHelperFbServ::ReadCObjectConInfoL( TAny* aCellAddress, RArray<TAny*>& aContainerObjects, TInt& aCount, TInt& aAllocated )
    {
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - START - trying cell: 0x%08x", aCellAddress ));

    // What we will return
    HBufC8* cellData = NULL;
    
    // Try to get info about real cell
    TMemSpyDriverCellType cellType;
    TInt cellLength;
    TInt cellNestingLevel;
    TInt cellAllocationNumber;
    TInt cellHeaderSize;
    TAny* cellPayloadAddress;

    // NOTE: this call may change the value of 'cellAddress'
    TInt err = iEngine.Driver().WalkHeapGetCellInfo( aCellAddress, cellType, cellLength, cellNestingLevel, cellAllocationNumber, cellHeaderSize, cellPayloadAddress );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - err: %d, cellAddress: 0x%08x, cellLength: %d, cellAllocationNumber: %d, cellType: %d", err, aCellAddress, cellLength, cellAllocationNumber, cellType));

	if (err == KErrNone && (cellType & EMemSpyDriverAllocatedCellMask))
        {
        // Check that the cell size meets our expectations - it should be a CObjectCon cell.
        const TInt expectedCellSize = sizeof(CObjectCon*) + cellHeaderSize;
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - expectedCellSize: %d, actual: %d, address: 0x%08x", expectedCellSize, cellLength, aCellAddress));

        if  ( expectedCellSize <= cellLength )
            {
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - cell lengths are good, trying to get cell data..."));

            // Try to get real cell data        
            cellData = HBufC8::NewLC( cellLength );
            TPtr8 pData( cellData->Des() );
            //
            err = iEngine.Driver().WalkHeapReadCellData( aCellAddress, pData, cellLength );
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - reading cell data returned error: %d", err));
            }
        else
            {
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - not enough data -> KErrCorrupt"));
            err = KErrCorrupt;
            }
        }
    else
        {
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - END - didn't find cell data => KErrNotFound"));
        User::Leave( KErrNotFound );
        }

    CObjectCon* con = CObjectCon::NewL();
    const TUint32 KExpectedObjectConVTable = *((TUint32*) con);
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - KExpectedObjectConVTable: 0x%08x", KExpectedObjectConVTable ));
    delete con;

    // We should have the valid CObjectCon data now.
    if( cellData )
    	{
		RDesReadStream stream( *cellData );
		CleanupClosePushL( stream );
    
		// Read vtable
		const TUint32 vTable = stream.ReadUint32L();
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - actual vTable: 0x%08x", vTable ));
		if  ( vTable != KExpectedObjectConVTable )
			{
			User::Leave( KErrNotFound );
			}

		const TInt uniqueId = stream.ReadInt32L();
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - uniqueId: %d", uniqueId ));

		aCount = stream.ReadInt32L();
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - aCount: %d", aCount ));

		aAllocated = stream.ReadInt32L();
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - aAllocated: %d", aAllocated ));

		// This is actually CObject's iObject, i.e. CObject** iObjects.
		TAny* pObjects = reinterpret_cast< TAny*>( stream.ReadUint32L() );
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - pObjects: 0x%08x", pObjects ));

    	CleanupStack::PopAndDestroy( 2, cellData ); // cellData & stream
    
		// Now fetch the cell containing the CObject pointers...
		err = iEngine.Driver().WalkHeapGetCellInfo( pObjects, cellType, cellLength, cellNestingLevel, cellAllocationNumber, cellHeaderSize, cellPayloadAddress );
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - array of CObject* cell fetch err: %d, cellAddress: 0x%08x, cellLength: %d, cellAllocationNumber: %d, cellType: %d", err, aCellAddress, cellLength, cellAllocationNumber, cellType));
		User::LeaveIfError( err );

		const TInt expectedSize = ( aAllocated * sizeof(CObject*) ) + cellHeaderSize;
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - expectedSize: %d, actual size: %d", expectedSize, cellLength ));
		if  ( cellLength < expectedSize )
			{
			User::Leave( KErrUnderflow );
			}
    
		// Get the data
		cellData = HBufC8::NewLC( cellLength );
		TPtr8 pData( cellData->Des() );
		err = iEngine.Driver().WalkHeapReadCellData( pObjects, pData, cellLength );
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - reading cell data returned error: %d", err));
		User::LeaveIfError( err );

		// Open stream
		stream.Open( *cellData );
		CleanupClosePushL( stream );

		// Extract array of pointers
		for( TInt i=0; i<aCount; i++ )
			{
			TAny* objectAddress = reinterpret_cast< TAny*>( stream.ReadUint32L() );
			TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - object[%04d]: 0x%08x", i, objectAddress ));
			aContainerObjects.AppendL( objectAddress );
			}		
	    CleanupStack::PopAndDestroy( 2, cellData ); // cellData & stream
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ReadCObjectConInfoL() - END" ));		
    	}
    }


HBufC8* CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC( TAny*& aArrayCellAddress, TInt aArrayAllocCount )
    {
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC() - START - trying cell: 0x%08x, aArrayAllocCount: %d", aArrayCellAddress, aArrayAllocCount));

    // What we will return
    HBufC8* cellData = NULL;
    
    // Try to get info about real cell
    TMemSpyDriverCellType cellType;
    TInt cellLength;
    TInt cellNestingLevel;
    TInt cellAllocationNumber;
    TInt cellHeaderSize;
    TAny* cellPayloadAddress;

    // NOTE: this call may change the value of 'cellAddress'
    TInt err = iEngine.Driver().WalkHeapGetCellInfo( aArrayCellAddress, cellType, cellLength, cellNestingLevel, cellAllocationNumber, cellHeaderSize, cellPayloadAddress );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateCFbTopHeapCellDataLC() - err: %d, cellAddress: 0x%08x, cellLength: %d, cellAllocationNumber: %d, cellType: %d", err, aArrayCellAddress, cellLength, cellAllocationNumber, cellType));

    if (err == KErrNone && (cellType & EMemSpyDriverAllocatedCellMask))
        {
        // Check that the cell size meets our expectations. 
        // The cell should be a very specific length
        const TInt expectedCellSize = (sizeof(CBase*) * aArrayAllocCount) + cellHeaderSize;
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC() - expectedCellSize: %d, actual: %d, address: 0x%08x", expectedCellSize, cellLength, aArrayCellAddress));

        if  ( expectedCellSize <= cellLength )
            {
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC() - cell lengths are good, trying to get cell data..."));

            // Try to get real cell data        
            cellData = HBufC8::NewLC( cellLength );
            TPtr8 pData( cellData->Des() );
            //
            err = iEngine.Driver().WalkHeapReadCellData( aArrayCellAddress, pData, cellLength );
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC() - reading cell data returned error: %d", err));
            }
        else
            {
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC() - not enough data -> KErrCorrupt"));
            err = KErrCorrupt;
            }
        }
    //
    if  ( cellData == NULL )
        {
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC() - END - didn't find cell data => KErrNotFound"));
        err = KErrNotFound;
        }
    User::LeaveIfError( err );
    //
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::LocateBitmapArrayHeapCellDataLC() - END - ok! - got %d bytes", cellData->Length() ) );
    return cellData;
    }


void CMemSpyEngineHelperFbServ::ParseCellDataAndExtractHandlesL( const TDesC8& aData, RArray<TInt>& aHandles, TInt aArrayEntryCount )
    {
    aHandles.Reset();
    
    // Create read stream
    RDesReadStream stream( aData );
    CleanupClosePushL( stream );
    
    for( TInt i=0; i<aArrayEntryCount; i++ )
        {
        const TInt handle = stream.ReadInt32L();
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::ParseCellDataAndExtractHandlesL() - handle[%4d] = 0x%08x", i, handle));
        aHandles.AppendL( handle );
        }
    
    // Tidy up
    CleanupStack::PopAndDestroy( &stream );
    }


TBool CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL( const TDesC8& aData, TAny* aCellAddress, TAny* aPayloadAddress, TUint aHeapStartingAddress, TUint aHeapMaxSize )
    {
    (void) aPayloadAddress;
    (void) aCellAddress;
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - START - aDataLen: %d, aCellAddress: 0x%08x, aPayloadAddress: 0x%08x, aHeapStartingAddress: 0x%08x, aHeapSize: %d", aData.Length(), aCellAddress, aPayloadAddress, aHeapStartingAddress, aHeapMaxSize ));

    const TUint KFbServHeapCeilingAddress = aHeapStartingAddress + aHeapMaxSize;

    // Whether we can use this cell's data...
    TBool correctCell = EFalse;

    // We can use certain knowledge of the layout of the cell to work out
    // if we've found the right one. 
    // 
    // This is fragile because of the font and bitmap server heap layout changes 
    // significantly, then this can break.
    RDesReadStream reader( aData );
    CleanupClosePushL( reader );

    // Get vtable address
    TUint address = reader.ReadUint32L();
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - suspected vtable: 0x%08x", address));

#ifdef __EPOC32__
    TBool isROMAddress = EFalse;
    TInt err = User::IsRomAddress( isROMAddress, (TAny*) address );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - vtable (0x%08x) is in ROM: %d (error: %d)", address, isROMAddress, err));
    User::LeaveIfError( err );

    if  ( !isROMAddress )
        {
        // Close stream
        CleanupStack::PopAndDestroy( &reader );

        // VTable value should be in the ROM range.
        correctCell = EFalse;
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - END - suspected CFbTop vTable isn't in ROM address range! - correctCell: %d", correctCell));
        return correctCell;
        }
#else
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - skipping vtable: 0x%08x", address));
#endif

    // First real item is an RFs object instance. We can't check this
    // very easily because its an embedded object-> We must skip it
    __ASSERT_ALWAYS( sizeof(RFs) == 4, User::Invariant() );
    address = reader.ReadUint32L();
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - skipping RFs: 0x%08x", address));

    // Next object is an address (CFontStore* iFontStore). We can validate this against
    // the method arguments.
    address = reader.ReadUint32L();
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - iFontStore: 0x%08x", address));

    if  ( address > aHeapStartingAddress && address < KFbServHeapCeilingAddress )
        {
        // Next comes another pointer (CObjectCon* iBitmapCon)
        address = reader.ReadUint32L();
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - iBitmapCon: 0x%08x", address));

        if  ( address > aHeapStartingAddress && address < KFbServHeapCeilingAddress )
            {
            // Next another pointer (CObjectCon* iFontCon)
            address = reader.ReadUint32L();
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - iFontCon: 0x%08x", address));

            if  ( address > aHeapStartingAddress && address < KFbServHeapCeilingAddress )
                {
                // And finally, yet another pointer (RHeap* iHeap)
                address = reader.ReadUint32L();
                TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - iHeap: 0x%08x", address));

                if  ( address > aHeapStartingAddress && address < KFbServHeapCeilingAddress )
                    {
                    // That'll do.
                    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - data seems okay!"));
                    correctCell = ETrue;
                    }
                }
            }
        }

    CleanupStack::PopAndDestroy( &reader );
    //
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::VerifyCorrectHeapCellL() - END - correctCell: %d", correctCell));
    return correctCell;
    }


TUint CMemSpyEngineHelperFbServ::OffsetToCObjectConBitmapCon()
    {
    TUint ret = 0;

    // VTable
    ret += 4;

    // RFs
    ret += sizeof(RFs);

	// CFontStore* iFontStore;
    ret += sizeof(CBase*); // best effort

    return ret;
    }


TUint CMemSpyEngineHelperFbServ::OffsetToCObjectConFontCon()
    {
    TUint ret = OffsetToCObjectConBitmapCon();

	// CObjectCon* iBitmapCon;
    ret += sizeof(CObjectCon*);

    return ret;
    }


TUint CMemSpyEngineHelperFbServ::OffsetToBitmapHandleArray()
    {
    TUint ret = OffsetToCObjectConFontCon();

	// CObjectConIx* iConIx;
    ret += sizeof(CObjectConIx*);

	// RHeap* iHeap;
    ret += sizeof(RHeap*);

	// RChunk iChunk
    ret += sizeof(RChunk);

	// RChunk iLargeBitmapChunk
    ret += sizeof(RChunk);

	// RMutex iLargeBitmapAccess
    ret += sizeof(RMutex);

	// CChunkPile* iPile;
    ret += sizeof(CBase*); // best effort

	// RPointerArray<HBufC> iFontNameAlias;
    ret += sizeof(RPointerArray<HBufC>);
	
    return ret;
    }


CMemSpyEngineHelperFbServ::CBitmapObject* CMemSpyEngineHelperFbServ::GetBitmapObjectLC( TAny* aCellAddress )
    {
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - START - aCellAddress: 0x%08x", aCellAddress ));

    // What we will return
    CBitmapObject* object = new(ELeave) CBitmapObject();
    CleanupStack::PushL( object );

    // The data we will read
    HBufC8* cellData = NULL;
    
    // Try to get info about real cell
    TMemSpyDriverCellType cellType;
    TInt cellLength;
    TInt cellNestingLevel;
    TInt cellAllocationNumber;
    TInt cellHeaderSize;
    TAny* cellPayloadAddress;

    // NOTE: this call may change the value of 'cellAddress'
    TInt err = iEngine.Driver().WalkHeapGetCellInfo( aCellAddress, cellType, cellLength, cellNestingLevel, cellAllocationNumber, cellHeaderSize, cellPayloadAddress );
    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - err: %d, cellAddress: 0x%08x, cellLength: %d, cellAllocationNumber: %d, cellType: %d", err, aCellAddress, cellLength, cellAllocationNumber, cellType));

    if (err == KErrNone && (cellType & EMemSpyDriverAllocatedCellMask))
        {
        // Check that the cell size meets our expectations - it should be a CBitmapObject, but without the additional "this" pointer
        // which we have tacked onto the object.
        const TInt expectedCellSize = sizeof( CBitmapObject ) + cellHeaderSize - sizeof( CBitmapObject* );
        TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - expectedCellSize: %d, actual: %d, address: 0x%08x", expectedCellSize, cellLength, aCellAddress));

        if  ( expectedCellSize <= cellLength )
            {
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - cell lengths are good, trying to get cell data..."));

            // Try to get real cell data        
            cellData = HBufC8::NewLC( cellLength );
            TPtr8 pData( cellData->Des() );
            //
            err = iEngine.Driver().WalkHeapReadCellData( aCellAddress, pData, cellLength );
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - reading cell data returned error: %d", err));
            }
        else
            {
            TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - not enough data -> KErrCorrupt"));
            err = KErrCorrupt;
            }
        }
    else
        {
        User::Leave( KErrNotFound );
        }

    if( cellData )
    	{
		RDesReadStream stream( *cellData );
		CleanupClosePushL( stream );
    	    
		// Read vtable
		const TUint32 vTable = stream.ReadUint32L();
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - actual vTable: 0x%08x", vTable ));
		TBool isROMAddress = EFalse;
		err = User::IsRomAddress( isROMAddress, (TAny*) vTable );
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - vtable (0x%08x) is in ROM: %d (error: %d)", vTable, isROMAddress, err));
		User::LeaveIfError( err );

		// Skip CObject members
		(void) stream.ReadL( sizeof( CObject ) ); // skip this much
		object->iThisPointer = reinterpret_cast< CBitmapObject* >( cellPayloadAddress );;
		object->iTop = reinterpret_cast< CBase* >( stream.ReadUint32L() );
		object->iAddressPointer = reinterpret_cast< CBitwiseBitmap* >( stream.ReadUint32L() );
		object->iHandle = stream.ReadInt32L();
		object->iCleanBitmap = reinterpret_cast< CBitmapObject* >( stream.ReadUint32L() );

		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - object->iThisPointer:    0x%08x", object->iThisPointer ));
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - object->iTop:            0x%08x", object->iTop ));
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - object->iAddressPointer: 0x%08x", object->iAddressPointer ));
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - object->iHandle:         0x%08x", object->iHandle ));
		TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - object->iCleanBitmap:    0x%08x", object->iCleanBitmap ));
    
		// Clean up - don't need this data anymore. Real data is in another cell
    	CleanupStack::PopAndDestroy( 2, cellData );  // stream & cellData
    	}

    TRACE( RDebug::Printf("CMemSpyEngineHelperFbServ::GetBitmapObjectLC() - END - aCellAddress: 0x%08x", aCellAddress ));
    return object;
    }



























CMemSpyEngineFbServBitmapArray::CMemSpyEngineFbServBitmapArray( const RArray<TInt>& aBitmapHandles )
:   CActive( CActive::EPriorityIdle ), iBitmapHandles( aBitmapHandles ), iGranularity( 0 )
    {
    CActiveScheduler::Add( this );
    }


CMemSpyEngineFbServBitmapArray::CMemSpyEngineFbServBitmapArray( TInt aPriority, const RArray<TInt>& aBitmapHandles, MMemSpyEngineFbSerbBitmapArrayObserver& aObserver, TInt aGranularity )
:   CActive( aPriority ), iBitmapHandles( aBitmapHandles ), iObserver( &aObserver ), iGranularity( aGranularity )
    {
    CActiveScheduler::Add( this );
    CompleteSelf();
    }


EXPORT_C CMemSpyEngineFbServBitmapArray::~CMemSpyEngineFbServBitmapArray()
    {
    Cancel();
    //
    iBitmaps.ResetAndDestroy();
    iBitmaps.Close();
    }


void CMemSpyEngineFbServBitmapArray::ConstructL()
    {
    if  ( !IsActive() )
        {
        // Sync construction...
        const TInt count = iBitmapHandles.Count();
        for(TInt i=0; i<count; i++ )
            {
            const TInt handle = iBitmapHandles[ i ];
            //
            TRAP_IGNORE( CreateBitmapL( handle ) );
            }
    
        SortBySizeL();
        }
    }


EXPORT_C CMemSpyEngineFbServBitmapArray* CMemSpyEngineFbServBitmapArray::NewL( const RArray<TInt>& aBitmapHandles )
    {
    CMemSpyEngineFbServBitmapArray* self = new(ELeave) CMemSpyEngineFbServBitmapArray( aBitmapHandles );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );
    return self;
    }


EXPORT_C CMemSpyEngineFbServBitmapArray* CMemSpyEngineFbServBitmapArray::NewL( TInt aPriority, const RArray<TInt>& aBitmapHandles, MMemSpyEngineFbSerbBitmapArrayObserver& aObserver, TInt aGranularity )
    {
    CMemSpyEngineFbServBitmapArray* self = new(ELeave) CMemSpyEngineFbServBitmapArray( aPriority, aBitmapHandles, aObserver, aGranularity );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );
    return self;
    }


EXPORT_C TInt CMemSpyEngineFbServBitmapArray::Count() const
    {
    return iBitmaps.Count();
    }


EXPORT_C CMemSpyEngineFbServBitmap& CMemSpyEngineFbServBitmapArray::At( TInt aIndex )
    {
    return *iBitmaps[ aIndex ];
    }


EXPORT_C const CMemSpyEngineFbServBitmap& CMemSpyEngineFbServBitmapArray::At( TInt aIndex ) const
    {
    return *iBitmaps[ aIndex ];
    }


EXPORT_C CMemSpyEngineFbServBitmap& CMemSpyEngineFbServBitmapArray::BitmapByHandleL( TInt aHandle )
    {
    const TInt index = BitmapIndexByHandle( aHandle );
    User::LeaveIfError( index );
    CMemSpyEngineFbServBitmap& ret = At( index );
    return ret;
    }


EXPORT_C TInt CMemSpyEngineFbServBitmapArray::BitmapIndexByHandle( TInt aHandle ) const
    {
    TInt ret = KErrNotFound;
    //
    const TInt count = Count();
    for( TInt i=0; i<count; i++ )
        {
        const CMemSpyEngineFbServBitmap& bitmap = At( i );
        //
        if  ( bitmap.Handle() == aHandle )
            {
            ret = i;
            break;
            }
        }
    //
    return ret;
    }


EXPORT_C TInt CMemSpyEngineFbServBitmapArray::MdcaCount() const
    {
    return Count();
    }


EXPORT_C TPtrC CMemSpyEngineFbServBitmapArray::MdcaPoint( TInt aIndex ) const
    {
    const CMemSpyEngineFbServBitmap& bitmap = At( aIndex );
    return TPtrC( bitmap.Caption() );
    }


void CMemSpyEngineFbServBitmapArray::RunL()
    {
    const TInt bitmapHandleCount = iBitmapHandles.Count();
    const TInt endIndex = Min( iIndex + iGranularity, bitmapHandleCount );
    //
    for( ; iIndex < endIndex; iIndex++ )
        {
        const TInt handle = iBitmapHandles[ iIndex ];
        //
        TRAP_IGNORE( CreateBitmapL( handle ) );
        }
    
    // Report progress & queue for next iteration
    MMemSpyEngineFbSerbBitmapArrayObserver::TEvent event = MMemSpyEngineFbSerbBitmapArrayObserver::EBitmapArrayConstructionComplete;
    if  ( endIndex < bitmapHandleCount )
        {
        CompleteSelf();
        event = MMemSpyEngineFbSerbBitmapArrayObserver::EBitmapItemsCreated;
        }
    else
        {
        SortBySizeL();
        }

    if  ( iObserver )
        {
        iObserver->HandleFbServBitmapArrayEventL( event );
        }
    }


void CMemSpyEngineFbServBitmapArray::DoCancel()
    {
    // Nothing to do
    }


TInt CMemSpyEngineFbServBitmapArray::RunError( TInt /*aError*/ )
    {
    return KErrNone;
    }


void CMemSpyEngineFbServBitmapArray::CompleteSelf()
    {
    TRequestStatus* status = &iStatus;
    User::RequestComplete( status, KErrNone );
    SetActive();
    }


void CMemSpyEngineFbServBitmapArray::CreateBitmapL( TInt aHandle )
    {
    CMemSpyEngineFbServBitmap* bitmap = CMemSpyEngineFbServBitmap::NewLC( aHandle );
    iBitmaps.AppendL( bitmap );
    CleanupStack::Pop( bitmap );
    }


void CMemSpyEngineFbServBitmapArray::SortBySizeL()
    {
    TLinearOrder< CMemSpyEngineFbServBitmap > comparer( CompareBySize );
    iBitmaps.Sort( comparer );
    }


TInt CMemSpyEngineFbServBitmapArray::CompareBySize( const CMemSpyEngineFbServBitmap& aLeft, const CMemSpyEngineFbServBitmap& aRight )
    {
    TInt ret = 1;
    //
    if  ( aLeft.BitmapSizeInBytes() > aRight.BitmapSizeInBytes() )
        {
        ret = -1;
        }
    else if ( aLeft.BitmapSizeInBytes() == aRight.BitmapSizeInBytes() )
        {
        ret = 0;
        }
    //
    return ret;
    }






















CMemSpyEngineFbServBitmap::CMemSpyEngineFbServBitmap()
:   CDesCArrayFlat( 5 )
    {
    }


EXPORT_C CMemSpyEngineFbServBitmap::~CMemSpyEngineFbServBitmap()
    {
    delete iBitmap;
    delete iCaption;
    }


void CMemSpyEngineFbServBitmap::ConstructL( TInt aHandle )
    {
    iBitmap = new(ELeave) CFbsBitmap();
    const TInt error = iBitmap->Duplicate( aHandle );
    User::LeaveIfError( error );
    const SEpocBitmapHeader header( iBitmap->Header() );

    // Prepare caption
    _LIT( KCaptionFormat, "\t%2d bpp, %S\t\t%4d x %4d pixels" );
    iCaption = HBufC::NewL( 128 );
    TPtr pCaption( iCaption->Des() );
    
    // Create size string
    const TMemSpySizeText size( MemSpyEngineUtils::FormatSizeText( header.iBitmapSize ) );
    pCaption.AppendFormat( KCaptionFormat, header.iBitsPerPixel, &size, header.iSizeInPixels.iWidth, header.iSizeInPixels.iHeight );

    // Prepare items
    PrepareItemsL();
    }


EXPORT_C CMemSpyEngineFbServBitmap* CMemSpyEngineFbServBitmap::NewLC( TInt aHandle )
    {
    CMemSpyEngineFbServBitmap* self = new(ELeave) CMemSpyEngineFbServBitmap();
    CleanupStack::PushL( self );
    self->ConstructL( aHandle );
    return self;
    }


TInt CMemSpyEngineFbServBitmap::BitmapSizeInBytes() const
    {
    const SEpocBitmapHeader header( iBitmap->Header() );
    return header.iBitmapSize;
    }


EXPORT_C const TDesC& CMemSpyEngineFbServBitmap::Caption() const
    {
    return *iCaption;
    }


EXPORT_C CFbsBitmap& CMemSpyEngineFbServBitmap::Bitmap()
    {
    return *iBitmap;
    }


EXPORT_C const CFbsBitmap& CMemSpyEngineFbServBitmap::Bitmap() const
    {
    return *iBitmap;
    }


EXPORT_C TInt CMemSpyEngineFbServBitmap::Handle() const
    {
    return iBitmap->Handle();
    }


EXPORT_C void CMemSpyEngineFbServBitmap::OutputDataL( CMemSpyEngine& aEngine ) const
    {
    const SEpocBitmapHeader header( iBitmap->Header() );
    //
    _LIT(KMemSpyFBServHex, "0x%08x");
    _LIT(KMemSpyFBServDecimal, "%d");
    //
    HBufC* columns = HBufC::NewLC( 1024 );
    TPtr pColumns( columns->Des() );
    //
    pColumns.AppendFormat( KMemSpyFBServHex, Handle() );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    pColumns.AppendFormat( KMemSpyFBServDecimal, header.iBitmapSize );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    pColumns.AppendFormat( KMemSpyFBServDecimal, header.iSizeInPixels.iWidth );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    pColumns.AppendFormat( KMemSpyFBServDecimal, header.iSizeInPixels.iHeight );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    pColumns.AppendFormat( KMemSpyFBServDecimal, header.iBitsPerPixel );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    pColumns.AppendFormat( KMemSpyFBServDecimal, header.iColor );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    TBuf<24> fileCompressionType;
    GetFileCompressionTypeAsString( header.iCompression, fileCompressionType );
    pColumns.Append( fileCompressionType );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    GetExportableFileNameL( pColumns );
    //
    aEngine.Sink().OutputLineL( pColumns );
    CleanupStack::PopAndDestroy( columns );
    }


EXPORT_C void CMemSpyEngineFbServBitmap::OutputDataColumnsL( CMemSpyEngine& aEngine )
    {
    HBufC* columns = HBufC::NewLC( 1024 );
    TPtr pColumns( columns->Des() );
    //
    _LIT(KCol1, "Handle");
    pColumns.Append( KCol1 );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    _LIT(KCol2, "Uncompressed Size (Bytes)");
    pColumns.Append( KCol2 );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    _LIT(KCol3, "Width");
    pColumns.Append( KCol3 );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    _LIT(KCol4, "Height");
    pColumns.Append( KCol4 );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    _LIT(KCol5, "BPP");
    pColumns.Append( KCol5 );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    _LIT(KCol6, "Color");
    pColumns.Append( KCol6 );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    _LIT(KCol7, "Compression");
    pColumns.Append( KCol7 );
    pColumns.Append( KMemSpyEngineFBServComma );
    //
    _LIT(KCol8, "File Name");
    pColumns.Append( KCol8 );
    //
    aEngine.Sink().OutputLineL( pColumns );
    CleanupStack::PopAndDestroy( columns );
    }


EXPORT_C void CMemSpyEngineFbServBitmap::GetExportableFileNameL( TDes& aAppendName ) const
    {
    const SEpocBitmapHeader header( iBitmap->Header() );

    // Create a filename
    aAppendName.AppendFormat( KMemSpyEngineFBServExportFileName, Handle(), header.iBitsPerPixel, header.iSizeInPixels.iWidth, header.iSizeInPixels.iHeight );
    }


void CMemSpyEngineFbServBitmap::GetExportableFileNameL( const TMemSpyEngineFBServBitmapInfo& aInfo, TDes& aAppendName )
    {
    aAppendName.AppendFormat( KMemSpyEngineFBServExportFileName, aInfo.iHandle, aInfo.iBitsPerPixel, aInfo.iSizeInPixels.iWidth, aInfo.iSizeInPixels.iHeight );
    }


void CMemSpyEngineFbServBitmap::PrepareItemsL()
    {
    const SEpocBitmapHeader header( iBitmap->Header() );
    TBuf<256> item;

    // View bitmap (ugly, but needed by UI)
    _LIT(KCaptionMinus2, "\tView Bitmap\t\t");
    AppendL( KCaptionMinus2 );

    // Dimensions
    _LIT(KCaptionMinus1, "\tDimensions\t\t%5d x %5d");
    item.Format( KCaptionMinus1, header.iSizeInPixels.iWidth, header.iSizeInPixels.iHeight );
    AppendL( item );

    // Handle
    _LIT(KCaption0, "\tHandle\t\t0x%08x");
    item.Format( KCaption0, iBitmap->Handle() );
    AppendL( item );

    // In Rom?
    _LIT(KCaption1, "\tIn ROM?\t\t%S");
    _LIT(KIsRomBitmap, "Yes");
    _LIT(KNotRomBitmap, "No");
    if  ( iBitmap->IsRomBitmap() )
        {
        item.Format( KCaption1, &KIsRomBitmap );
        }
    else
        {
        item.Format( KCaption1, &KNotRomBitmap );
        }
    AppendL( item );

    // Bits per pixel
    _LIT(KCaption2, "\tBits Per Pixel\t\t%2d");
    item.Format( KCaption2, header.iBitsPerPixel );
    AppendL( item );

    // Size of uncompressed bitmap data
    _LIT(KCaption3, "\tUncompressed data size\t\t%S");
    const TMemSpySizeText sizeStringUncompressed( MemSpyEngineUtils::FormatSizeText( header.iBitmapSize ) );
    item.Format( KCaption3, &sizeStringUncompressed );
    AppendL( item );

    // Palette entry count
    _LIT(KCaption4, "\tNumber of palette entries\t\t%d");
    item.Format( KCaption4, header.iPaletteEntries );
    AppendL( item );

    // Colour
    _LIT(KCaption5, "\tColour\t\t%d");
    item.Format( KCaption5, header.iColor);
    AppendL( item );

    // Palette entry count
    _LIT(KCaption6, "\tFile compression type\t\t%S");
    TBuf<24> fileCompressionType;
    GetFileCompressionTypeAsString( header.iCompression, fileCompressionType );
    item.Format( KCaption6, &fileCompressionType );
    AppendL( item );
    }


void CMemSpyEngineFbServBitmap::GetFileCompressionTypeAsString( TBitmapfileCompression aType, TDes& aString )
    {
    _LIT(KUnknown, "Unknown");
    _LIT(KNoCompression, "None");
    _LIT(KRLE, "RLE");
    _LIT(KTwelveBitRLE, "12-bit RLE");
    _LIT(KSixteenBitRLE, "16-bit RLE");
    _LIT(KTwentyFourBitRLE, "24-bit RLE");
    _LIT(KThirtyTwoBitRLE, "32-bit RLE");
    //
    switch( aType )
        {
    case ENoBitmapCompression:
        aString = KNoCompression;
        break;
    case EByteRLECompression:
        aString = KRLE;
        break;
    case ETwelveBitRLECompression:
        aString = KTwelveBitRLE;
        break;
    case ESixteenBitRLECompression:
        aString = KSixteenBitRLE;
        break;
    case ETwentyFourBitRLECompression:
        aString = KTwentyFourBitRLE;
        break;
    case EThirtyTwoUBitRLECompression:
        aString = KThirtyTwoBitRLE;
        break;
    default:
    case ERLECompressionLast:
        aString = KUnknown;
        break;
        }
    }