// // MEMSTORE.CPP // // Source file for ArchiveLib 1.0 // // Copyright (c) Greenleaf Software, Inc. 1994 // All Rights Reserved // // CONTENTS // // ALMemory::operator new() // ALMemory::ALMemory() // ALMemory::~ALMemory() // ALMemory::LoadBuffer() // ALMemory::Delete() // ALMemory::Rename() // ALMemory::RenameToBackup() // ALMemory::UnRename() // ALMemory::Seek() // ALMemory::GrowUserBuffer() // ALMemory::FlushBuffer() // ALMemory::Close() // ALMemory::Create() // ALMemory::Open() // // DESCRIPTION // // This file contains all the C++ member functions to support the // ALMemory class. ALMemory is conceptually pretty simple, but it suffers // from a little bit of #ifdef'itis. The reason for this is that // things change just a little bit when we are using Windows memory // management. Not enough to create a new class, but enough to have // to make a lot of code conditional. // // The big difference between the Windows and DOS code shows up in two // areas. First, under Windows we have huge buffers that can support // up to either 16Mbytes or 4GBytes, depending. Under MS-DOS real mode, // out biggest buffer is 64Kb. Second, under MS-DOS we get new memory // with malloc/realloc/free. Under Windows we use LocalAlloc etc. // // Other than that, the whole class is pretty straightforward. Try not to // let the issue of naming the buffers throw you, it is basically // irrelevant. // // REVISION HISTORY // // May 24, 1994 1.0A : First release // // #include "arclib.h" #pragma hdrstop #include "memstore.h" #include // might be using malloc()! // // void * ALMemory::operator new( size_t size ) // // ARGUMENTS: // // size : The amount of storage that needs to be allocated for // this object. // // RETURNS // // A pointer to the storage. // // DESCRIPTION // // When using the DLL version of ArchiveLib, it is a good idea to // allocate the storage for objects from inside the DLL, since they // will be freed inside the DLL. If we don't have the new operator // for a class, its storage will be allocated from the EXE before // the constructor code is called. Then, when it is time to free // the storage, the delete operator will be called inside the DLL. // Not good, right? // // // Very important: this new operator is called to allocate the // storage for the ALMemory object itself. This has nothing to do // with the storage buffer that the memory object will be using // later on. In other words, this new operator is responsible for // no more than a couple of dozen bytes, not potentially hundreds // of Kbytes. // // REVISION HISTORY // // May 24, 1994 1.0A : First release // #if defined( AL_BUILDING_DLL ) void AL_DLL_FAR * AL_PROTO ALMemory::operator new( size_t size ) { return ::new char[ size ]; } #endif // WINDOWS version: // // ALMemory::ALMemory( const char *buffer_name = "", // char AL_HUGE *user_buffer = 0, // DWORD user_buffer_size = 0, // ALCase name_case = AL_MIXED ) // // MS-DOS real mode version : // // ALMemory::ALMemory( const char *buffer_name = "", // char *user_buffer = 0, // int user_buffer_size = 0, // ALCase name_case = AL_MIXED ) // // ARGUMENTS: // // buffer_name : An arbitrary name assigned to the buffer. Buffer // names don't have to be unique, because buffers aren't // named at the operating system level. But if you are // going to insert the storage object into an archive, the // name needs to be unique so that you will be able to // extract it properly. // // user_buffer : If you want the ALMemory class to automatically allocate // a buffer for you, and grow it as necessary, just leave // this pointer set to 0. If you want to use your own buffer, // which won't have the ability to grow, pass a pointer to // it in this parameter. Note that under Windows 16 this // is a huge pointer, meaning it can span segments, and // access potentially 16 Mbytes of memory. // // user_buffer_size : If you are passing a pointer to your own buffer, // you need to indicate how large it is here. Under // Windows this is a DWORD instead of a size_t. // // name_case : This decides whether you want the file name to be // case sensitive when making comparisons. MS-DOS // file names are case-insensitive. You can make memory // buffers either mixed case, forced upper, or forced // lower. The default of mixed case means that comparisons // will be case sensitive, which is fine. // RETURNS // // Nothing, it is a constructor. // // DESCRIPTION // // This constructor calls the base class constructor in an initializer // list, which takes care of most of the dirty work right away. After that // is done, all the constructor has to do is initialize a few data members. // That should be self-explanatory. Remember that if the user doesn't // supply a buffer, we are going to allocate it for her, but not until // there is actually a demand for memory. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // #if defined( AL_WINDOWS_MEMORY ) // // The Windows and MS-DOS constructors are nearly identical. // AL_PROTO ALMemory::ALMemory( const char AL_DLL_FAR *buffer_name /* = "" */, char AL_HUGE *user_buffer /* = 0 */, DWORD user_buffer_size /* = 0 */, ALCase name_case /* = AL_MIXED */) : ALStorage( buffer_name, 4096, AL_MEMORY_OBJECT, name_case ) { if ( user_buffer != 0 ) { mpcUserBuffer = user_buffer; mfUserOwnsBuffer = 1; muUserBufferSize = user_buffer_size; } else { mfUserOwnsBuffer = 0; mpcUserBuffer = 0; muUserBufferSize = 0; } mhUserMemoryHandle = 0; } #else // #if defined( AL_WINDOWS_MEMORY ) AL_PROTO ALMemory::ALMemory( const char AL_DLL_FAR *buffer_name /* = "" */, char AL_DLL_FAR *user_buffer /* = 0 */ , int user_buffer_size /* = 0 */, ALCase name_case /* = AL_MIXED */ ) : ALStorage( buffer_name, 4096, AL_MEMORY_OBJECT, name_case ) { if ( user_buffer != 0 ) { mpcUserBuffer = user_buffer; mfUserOwnsBuffer = 1; muUserBufferSize = user_buffer_size; } else { mfUserOwnsBuffer = 0; mpcUserBuffer = 0; muUserBufferSize = 0; } } #endif // // ALMemory::~ALMemory() // // ARGUMENTS: // // None, you don't get any for a destructor. // // RETURNS // // Nothing. // // DESCRIPTION // // The destructor has just one thing it has to do before this object // goes away. If the buffer that it has been using all along doesn't // belong to the user, then it is the class's responsibility to get // rid of it. We do so here, using one of two methods, depending on // whether we are under MS-DOS or Windows. // // Note also that we check the GoodTag() function when in Debug mode. // That will help catch really bad mistakes, such as trying to delete // an object that is not even an ALMemory object, maybe a beer can. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // AL_PROTO ALMemory::~ALMemory() { AL_ASSERT( GoodTag(), "~ALMemory: attempting to delete invalid object" ); if ( !mfUserOwnsBuffer ) { if ( mpcUserBuffer ) { #if defined( AL_WINDOWS_MEMORY ) GlobalUnlock( (HGLOBAL) mhUserMemoryHandle ); GlobalFree( (HGLOBAL) mhUserMemoryHandle ); mhUserMemoryHandle= 0; #else free( mpcUserBuffer ); #endif mpcUserBuffer = 0; } } AL_ASSERT( GoodTag(), "~ALMemory: attempting to delete invalid object" ); } // // int ALMemory::LoadBuffer( long address ) // // ARGUMENTS: // // address : An offset that we need to load data from. // // RETURNS // // AL_SEEK_ERROR if we try to read past the end of file. AL_END_OF_FILE // if we just run out of data. Otherwise an int indicating how many bytes // are now in the buffer. // // DESCRIPTION // // This is a virtual function the ALStorage functions rely on when reading // data. Anytime someone tries to do a ReadChar() or ReadBuffer(), and // it turns out that the I/O buffer has been exhausted, this function // is called. // // The simple job of this function is to read as many bytes as possible out // of the giant memory block allocated for the ALMemory object, and stick // it into the I/O buffer, which caches it for calls to ReadChar() // and friends. // // This works fine unless you try to go past the end of the buffer, // since there is nothing there we flag that as an error. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::LoadBuffer( long address ) { if ( mStatus < AL_SUCCESS ) return mStatus; if ( mlFilePointer != address ) { if ( mlFilePointer > (long) muUserBufferSize ) return mStatus.SetError( AL_SEEK_ERROR, "Attempt to read past end of the " "buffer in ALMemory %s", mName.GetName() ); } long load = muUserBufferSize - address; if ( load > (long) muBufferSize ) muBufferValidData = muBufferSize; else muBufferValidData = (size_t) load; if ( muBufferValidData <= 0 ) return AL_END_OF_FILE; #if defined( AL_WINDOWS_MEMORY ) // // Some problems passing huge arrays to memcpy, got to do it inline instead // I think Microsoft says memcpy() will work with huge pointers as long // as you don't try to use the inline optimizations, but I say why take // chances... // // Another note: AL_HUGE is _huge for win16, but blank for win32. // char AL_HUGE *temp = mpcUserBuffer + address; for ( unsigned i = 0 ; i < muBufferValidData ; i++ ) mpcBuffer[ i ] = *temp++; // memcpy( mpcBuffer, mpcUserBuffer + address, muBufferValidData ); #else memcpy( mpcBuffer, mpcUserBuffer + (size_t) address, muBufferValidData ); #endif if ( miUpdateCrcFlag ) UpdateCrc( muBufferValidData ); muReadIndex = 0; //Reading can resume at this location mlFilePointer += muBufferValidData; YieldTime(); return muBufferValidData; } // // int ALMemory::Delete() // // ARGUMENTS: // // None. // // RETURNS // // Always returns AL_SUCCESS. // // DESCRIPTION // // This function is supposed to delete the underlying physical object. // This makes a lot of sense with files, because you are essentially // emulating the MS-DOS command line DEL function. With memory // objects things aren't quite as clear. So we destroy the buffer, // and that's that. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::Delete() { if ( !mfUserOwnsBuffer ) { #if defined( AL_WINDOWS_MEMORY ) GlobalUnlock( (HGLOBAL) mhUserMemoryHandle ); GlobalFree( (HGLOBAL) mhUserMemoryHandle ); mhUserMemoryHandle= 0; #else free( mpcUserBuffer ); #endif mpcUserBuffer = 0; } return AL_SUCCESS; } // // int ALMemory::Rename( const char *new_name /* = 0 */, // int /* delete_on_clash = 1 */ ) // // ARGUMENTS: // // new_name : The new name of the buffer. // // delete_on_clash : This argument makes sense with files. What it says // is that if you try to rename BOB.DAT to BOB.BAK, // and it turns out that there is another BOB.BAK, should // you delete the other one? With memory buffers, // there is no clash, cause the OS doesn't care about // unique names. So we ignore this parm. // // RETURNS // // Always returns AL_SUCCESS. // // DESCRIPTION // // This function is supposed to rename the underlying physical object. // But in the case of memory buffers, the underlying physical object // doesn't actually have a name, so this is really just a local rename. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::Rename( const char AL_DLL_FAR *new_name /* = 0 */, int /* delete_on_clash = 1 */ ) { if ( new_name ) mName = new_name; return AL_SUCCESS; } // // int ALMemory::RenameToBackup( int delete_on_clash /* = 1 */ ) // // ARGUMENTS: // // delete_on_clash : This argument makes sense with files. What it says // is that if you try to rename BOB.DAT to BOB.BAK, // and it turns out that there is another BOB.BAK, should // you delete the other one? With memory buffers, // there is no clash, cause the OS doesn't care about // unique names. So it doesn't matter what value you // pass to the Rename() function, it is going to be // ignored. // // RETURNS // // Always returns AL_SUCCESS. // // DESCRIPTION // // This function renames an object. But instead of making you sweat in // order to come up with a new name, it just uses the default name // that we use to assign a backup name. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::RenameToBackup( int delete_on_clash /* = 1 */ ) { mName.ChangeExtension(); return Rename( 0, delete_on_clash ); } // // int ALMemory::UnRename( int /* delete_on_clash = 1 */) // // ARGUMENTS: // // delete_on_clash : This argument makes sense with files. What it says // is that if you try to rename BOB.DAT to BOB.BAK, // and it turns out that there is another BOB.BAK, should // you delete the other one? With memory buffers, // there is no clash, cause the OS doesn't care about // unique names. So we just ignore it here. // // RETURNS // // Always returns AL_SUCCESS. // // DESCRIPTION // // If you decide you didn't really want to rename an object after all, you // can call this function to get the old name back! // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::UnRename( int /* delete_on_clash = 1 */) { AL_ASSERT( mName.GetName() != 0, "UnRename: trying to rename with a null name" ); AL_ASSERT( mName.GetOldName() != 0, "UnRename: trying to rename with a null old name" ); AL_ASSERT( strlen( mName ) > 0, "UnRename: trying to rename with a zero length name" ); AL_ASSERT( strlen( mName.GetOldName() ) > 0, "UnRename: trying to rename with a zero length old name" ); ALStorage::mName = mName.GetOldName(); return AL_SUCCESS; } // // int ALMemory::Seek( long address ) // // ARGUMENTS: // // address : The address in the memory object to go to. The read and // write pointers will now be repositioned to this point. // // RETURNS // // AL_SEEK_ERROR if we can't get to that point in the buffer. Otherwise // AL_SUCCESS. // // DESCRIPTION // // This function acts just like the seek() function in the C runtime // library. It flushes the current I/O buffers, and then moves the file // read and write pointers to a new spot, specified by the address. if // there is no memory there, you will get an error. Note that this // makes it not quite like the C run time library, since it can create // new space with a seek(). But I don't think we need that ability yet. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::Seek( long address ) { FlushBuffer(); if ( mStatus < 0 ) return mStatus; if ( mlFilePointer != address ) { if ( mlFilePointer > (long) muUserBufferSize ) return mStatus.SetError( AL_SEEK_ERROR, "Attempt to read past end of the " "buffer in ALMemory %s", mName.GetName() ); } mlFilePointer = address; return AL_SUCCESS; } // // int ALMemory::GrowUserBuffer( long minimum_new_size ) // // ARGUMENTS: // // minimum_new_size : The absolute minimum new size you need the buffer // to grow to. This amount is usually determined by // a pending I/O request. For example, if the current // size of the buffer is 1000, and you have a 1 byte // data block to write at 1000, the minimum new size // will be 1001. // // RETURNS // // AL_CANT_ALLOCATE_MEMORY, if we just can't get it. AL_SUCCESS if we can. // // DESCRIPTION // // When you are trying to write to the memory object, and you have hit // the end of the currently allocated area, it would seem like a good // time to allocate more. When that situation occurs, this function is // called. If the user owns the buffer, we don't have the option of asking // the O/S or RTL for more memory, because we don't even know if the user // memory is on the heap or what. But if we own the memory we know how // to ask for more. // // The strategy for asking for more memory is pretty simple. Normally, // we ask for another 16K. If that fails, we fall back to asking for // just enough memory to cover our current I/O request. Asking for // this memory is sufficiently different under real mode dos and protected // mode windows that we have two completely different routines, separated // only by #ifdefs. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // #if defined( AL_WINDOWS_MEMORY ) int AL_PROTO ALMemory::GrowUserBuffer( long minimum_new_size ) { if ( mStatus < AL_SUCCESS ) return mStatus; if ( mfUserOwnsBuffer ) return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Attempt to write past the end of a " "user owned buffer for ALMemory " "%s", mName.GetSafeName() ); long trial_size = muUserBufferSize + 16384; GlobalUnlock( (HGLOBAL) mhUserMemoryHandle ); HGLOBAL new_handle = GlobalReAlloc( (HGLOBAL) mhUserMemoryHandle, trial_size, GMEM_MOVEABLE ); if ( new_handle == 0 ) { trial_size = minimum_new_size; new_handle = GlobalReAlloc( (HGLOBAL) mhUserMemoryHandle, trial_size, GMEM_MOVEABLE ); } if ( new_handle == 0 ) { mpcUserBuffer = (char AL_HUGE *) GlobalLock( (HGLOBAL) mhUserMemoryHandle ); return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Allocation failure when attempting to " "allocate a buffer " "of %ld bytes for ALMemory " "%s", minimum_new_size, mName.GetSafeName() ); } mpcUserBuffer = (char AL_HUGE *) GlobalLock( new_handle ); mhUserMemoryHandle = new_handle; muUserBufferSize = trial_size; return AL_SUCCESS; } #else // #ifdef AL_WINDOWS_MEMORY int AL_PROTO ALMemory::GrowUserBuffer( long minimum_new_size ) { if ( mStatus < AL_SUCCESS ) return mStatus; if ( mfUserOwnsBuffer ) return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Attempt to write past the end of a " "user owned buffer for ALMemory " "%s", mName.GetSafeName() ); if ( minimum_new_size >= 65535L ) return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Attempt to allocate a huge buffer " "of %ld bytes for ALMemory " "%s", minimum_new_size, mName.GetSafeName() ); long trial_size = muUserBufferSize + 16384; if ( trial_size >= 65000U ) trial_size = 65000U; if ( trial_size >= minimum_new_size ) { char *new_buf = (char *) realloc( mpcUserBuffer, (size_t) trial_size ); if ( new_buf ) { mpcUserBuffer = new_buf; muUserBufferSize = (size_t) trial_size; return AL_SUCCESS; } } char *new_buf = (char *) realloc( mpcUserBuffer, (size_t) minimum_new_size ); if ( new_buf ) { mpcUserBuffer = new_buf; muUserBufferSize = (size_t) trial_size; return AL_SUCCESS; } return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Allocation failure when attempting to " "allocate a buffer " "of %ld bytes for ALMemory " "%s", minimum_new_size, mName.GetSafeName() ); } #endif // // int ALMemory::FlushBuffer() // // ARGUMENTS: // // None. // // RETURNS // // AL_CANT_ALLOCATE_MEMORY, if we run out. Otherwise, AL_SUCCESS. // // DESCRIPTION // // This routine is called when the I/O buffer is filled up. It means // you have filled up the cache with what is usually 4K bytes of data. // This routine is also called if you have hot data in the I/O buffer // and you decide to do a seek(), or a read(). // // All we have to do here is take the hot data in the I/O buffer and // write it out to our massive memory object. The big complication is // that sometimes the memory object isn't big enough, so while we are // all busy trying to do this, we have to ask for more data at the // same time. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::FlushBuffer() { if ( mStatus < 0 ) return mStatus; // // If the write index is 0, we can skip all this stuff, because there // is nothing in the buffer to flush out. // if ( muWriteIndex != 0 ) { if ( miUpdateCrcFlag ) UpdateCrc( muWriteIndex ); if ( ( muWriteIndex + mlFilePointer ) > (long)muUserBufferSize ) if ( GrowUserBuffer( muWriteIndex + mlFilePointer ) < 0 ) return mStatus; #if defined( AL_WINDOWS_MEMORY ) // // Can't use memcpy with huge pointers, at least not with the optimized // versions. // char AL_HUGE *temp = mpcUserBuffer + mlFilePointer; for ( unsigned int i = 0 ; i < muWriteIndex ; i++ ) *temp++ = mpcBuffer[ i ]; // memcpy( mpcUserBuffer + mlFilePointer, mpcBuffer, muWriteIndex ); #else memcpy( mpcUserBuffer + (size_t) mlFilePointer, mpcBuffer, muWriteIndex ); #endif mlFilePointer += muWriteIndex; muWriteIndex = 0; if ( mlSize < mlFilePointer ) mlSize = mlFilePointer; } muReadIndex = 0; muBufferValidData = 0; YieldTime(); return AL_SUCCESS; } // // int ALMemory::Close() // // ARGUMENTS: // // None. // // RETURNS // // AL_SUCCESS, or various error codes that filter on down from other // routines. // // DESCRIPTION // // Close() is supposed to do the same thing as fclose() in the run // time library. The most important thing we are concerned about is // that the I/O buffer gets freed up by the base class, so this suddenly // might not be a giant heavyweight object any more. // // After freeing things up in the base class, we check to see if // we have allocated more space than we really need. If so, we do // a realloc() of some sort to give space back to the O/S. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::Close() { if ( mpcBuffer == 0 ) return mStatus; FlushBuffer(); ALStorage::Close(); // // If we aren't using all our space, give back the extra. // if ( mlSize < (long) muUserBufferSize ) { #if defined( AL_WINDOWS_MEMORY ) GlobalUnlock( (HGLOBAL) mhUserMemoryHandle ); HGLOBAL new_handle = GlobalReAlloc( (HGLOBAL) mhUserMemoryHandle, mlSize, GMEM_MOVEABLE ); if ( new_handle != 0 ) mhUserMemoryHandle = new_handle; mpcUserBuffer = (char AL_HUGE *) GlobalLock( (HGLOBAL) mhUserMemoryHandle ); muUserBufferSize = mlSize; #else char *new_buf = (char *) realloc( mpcUserBuffer, (size_t) mlSize ); if ( new_buf ) mpcUserBuffer = new_buf; muUserBufferSize = (size_t) mlSize; #endif } return mStatus; } // // int ALMemory::Create() // // ARGUMENTS: // // None. // // RETURNS // // AL_SUCCESS, AL_CANT_ALLOCATE_MEMORY, or various error codes that // filter on down from other routines. // // DESCRIPTION // // This is like creating a new file. If there isn't a memory buffer // already assigned to this object, we create one, with an initial // allocation of 16Kbytes. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::Create() { ALStorage::Create(); if ( mStatus < AL_SUCCESS ) return mStatus; if ( (char *) mName == 0 || strlen( mName ) == 0 ) mName = "AL.TMP"; if ( mfUserOwnsBuffer ) return AL_SUCCESS; //If the user supplied the buffer, we take what's available #if defined( AL_WINDOWS_MEMORY ) mhUserMemoryHandle = GlobalAlloc( GMEM_MOVEABLE, 16384 ); if ( mhUserMemoryHandle ) { mpcUserBuffer = (char AL_HUGE *) GlobalLock( (HGLOBAL) mhUserMemoryHandle ); muUserBufferSize = 16384; } else { mpcUserBuffer = 0; return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Allocation failure when attempting to " "create a buffer " "of %ld bytes for ALMemory " "%s in Create()", 16384, mName.GetSafeName() ); } #else mpcUserBuffer = (char *) malloc( 16384 ); muUserBufferSize = 16384; if ( mpcUserBuffer == 0 ) return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Allocation failure when attempting to " "create a buffer " "of %ld bytes for ALMemory " "%s in Create()", 16384, mName.GetSafeName() ); #endif return AL_SUCCESS; } // // int ALMemory::Open() // // ARGUMENTS: // // None. // // RETURNS // // AL_SUCCESS, AL_CANT_OPEN_FILE, or various error codes that // filter on down from other routines. // // DESCRIPTION // // This is like opening an existing file. Since there is supposed to be // an existing memory buffer already, we gripe if we can't find one. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // int AL_PROTO ALMemory::Open() { ALStorage::Open(); if ( mStatus < AL_SUCCESS ) return mStatus; if ( mpcUserBuffer == 0 ) return mStatus.SetError( AL_CANT_OPEN_FILE, "Attempt to open ALMemory %s " "with no buffer allocated", mName.GetSafeName() ); else mlSize = (long) muUserBufferSize; return AL_SUCCESS; }