{ *************** Generalized file I/O buffering ***************** The enclosed TP unit BUFFERS exports a new object BUFFERFILE. This object allows to define a variable number of buffers with a buffersize of up to $FFE0 bytes each. It exports a number of methods to tailor the behaviour of the buffer to a specific applications needs - See the following procedures for details in this area: - SETWRITEBIAS - SETREADBIAS - RESETBIAS - ENABLEINBOUND - ENABLEOUTBOUND - DISABLEINBOUND - DISABLEOUTBOUND The buffers may be allocated in expanded memory if desired. Performance will be somewhat affected by this fact. All methods use the same names as their counterparts in the system unit, the there should not be any problem implementing them. The only minor difference is the fact, that the READ and WRITE procedures do not accept the optional fourth parameter, which in the system unit will return the number of bytes actually read or written. This was done for performance reasons but should be very easy to change. The unit is implemented using some of Turbo Pascals object oriented language constructs (actually my second step in this area). Some of the object oriented stuff is not really very pure code - some access to the imported data areas is direct, etc. This was done as to achieve some decent performance. Last but not least a small example on how to use the code: Program Test; VAR BF : BufferFile; L : LongInt; begin BF.Init(16384,5,True); BF.SetWriteBias; {Purely optional - may improve performance} BF.Assign('TEST.FIL'); BF.Rewrite(4); For L:=1 to 20000 do BF.Write(L,1); BF.Done; end. The code is herbey given to the public domain. If you discover any errors, I would appreciate if you would let me know. Rolf Ernst 72311,254 } Unit Buffers; InterFace {*********************************************************************} {**** Written 1989 by Rolf Ernst ****} {**** ****} {**** Code requires Turbo Professional for the expanded memory ****} {**** access. The procedures used should not take more than a ****} {**** few lines to reproduce though. ****} {**** ****} {**** This code is hereby in the public domain. ****} {*********************************************************************} Uses Dos, TpEms; Type PtrRec = Record Ofs, Seg : Word; end; BigBlock = Array[0..1] Of Byte; BigBlockPtr = ^BigBlock; BufferPtr = ^BufferDesc; BufferDesc = object BufferAddr : BigBlockPtr; EmsHandle : Word; InEms : Boolean; Size : Word; Next : Pointer; Constructor Init(BufferSize : Word; UseEms : Boolean); Function Map(Offset, Length : Word) : BigBlockPtr; Virtual; Destructor Done; end; FileBufferPtr = ^FileBufferDesc; FileBufferDesc = Object(BufferDesc) PosBuffer : LongInt; BytesUsed : Word; Initialized : Boolean; Modified : Boolean; Constructor Init(BufferSize : Word; UseEms : Boolean); end; BufferChain = object NumberOfBuffers, BlockSize:Word; BufferHead, BufferTail : FileBufferPtr; Procedure Init(BufSize, BufNum : Word; UseEms : Boolean); Procedure ChainAtEnd(VAR B : FileBufferPtr); Function BuffersUnUsed:Word; Procedure Done; end; BufferFile=Object F : File; FSize : LongInt; CurrentPos : LongInt; RecordSize : Word; BlockSize : Word; BufferS : BufferChain; FlushAll : Boolean; ReadAll : Boolean; NoBufferReads : Boolean; NoBufferWrites : Boolean; NoBufferIng : Boolean; Procedure Init(BufSize, BufNum:Word; UseEms : Boolean); {Initialize BufNum buffers for the file, each being Bufsize bytes big - use Expanded memory if UseEms is TRUE} Procedure Flush; {Write all modified buffers to disk - does not cause DOS to flush its buffers} Function FreeBuffer : FileBufferPtr; {Find an available Buffer - Flush a buffer if necessary} Procedure Read(VAR A; NumRecs : Word); {Read a record buffered} Procedure DisableOutBound; {Disable buffering when writing to a file} Procedure Write(VAR A; NumRecs : Word); {Write a record buffered} Function Eof:Boolean; {Return true if the current position in the file is at its end} Procedure Seek(NewPos : LongInt); {Go to a new position in the file} Function FileSize:LongInt; {Returns the size of a buffered file taking any data in the buffers into consideration} Procedure Assign(Name : PathStr); {Assign a name to a buffered file} Function FilePos:LongInt; {Returns the current position in a buffered file} Procedure Rewrite(RecSize : Word); {Create a new file or overwrite an existing one} Procedure Reset(RecSize:Word); {Open an existing file} Procedure SetWriteBias; {Indicate, that the majority of the file operations will be sequential writes - when a buffer needs to be flushed ALL buffers will be flushed} Procedure SetReadBias; {Indicate, that the majority of the file operations will be sequential reads - when a buffer needs to be read ALL buffers will be read from disk} Procedure ResetBias; {Reset file access characteristics to its default values} Procedure DisableInBound; {Disable buffering when reading from a dataset} Procedure EnableInBound; {Enable buffering when reading from a dataset} Procedure EnableOutBound; {Enable buffering when writing to a dataset} Procedure Done; {Close the file and free all buffers} end; Implementation Procedure EmsError; begin Writeln('Severe Error in EMS handler'); readln; halt; end; Function MemToEms(BytesIn : LongInt) : Word; begin MemToEms:=(BytesIn+16383) shr 14; end; Procedure MapBuffer(Handle : Word; BytesInBuffer:Word); VAR I : Word; begin For I:=0 to Pred(MemToEms(BytesInBuffer)) do begin If Not MapEmsPage(Handle,i,i) then EmsError; end; end; Procedure BufferFile.SetWriteBias; begin FlushAll:=True; ReadAll:=False; end; Procedure BufferFile.DisableInBound; begin NoBufferReads:=True; end; Procedure BufferFile.EnableInBound; begin NoBufferReads:=false; end; Procedure BufferFile.DisableOutBound; begin Flush; NoBufferWrites:=True; end; Procedure BufferFile.EnableOutBound; begin NoBufferWrites:=False; end; Procedure BufferFile.ResetBias; begin FlushAll:=False; ReadAll:=False; NoBufferReads:=False; NoBufferWrites:=False; end; Procedure BufferFile.SetReadBias; begin FlushAll:=False; ReadAll:=True; end; Constructor BufferDesc.Init(BufferSize : Word; UseEms : Boolean); begin InEms:=UseEms and EmsInstalled and (EmsPagesAvail>=MemToEms(Buffersize)); Size:=BufferSize; If InEms then begin EmsHandle:=AllocateEMSPages(MemToEms(Size)); If EmsHandle=EmsErrorCode then EmsError; BufferAddr:=EmsPageFramePtr; end else GetMem(BufferAddr,Size); Next:=Nil; end; Function BufferDesc.Map(Offset, Length : Word) : BigBlockPtr; VAR HighOffset : Word; MyPointer : BigBlockPTr; begin MyPointer:=BufferAddr; Inc(PtrRec(MyPointer).Ofs,Offset); Map:=MyPointer; If InEms then begin HighOffset:=Pred(Offset+Length); Offset:=Offset Shr 14; HighOffset:=HighOffset shr 14; repeat If Not MapEmsPage(EMSHandle,Offset,Offset) then EmsError; INC(Offset); until Offset>HighOffset; end; end; Destructor BufferDesc.Done; begin IF InEms then begin If Not DeallocateEmsHandle(Emshandle) then EmsError; end else FreeMem(BufferAddr,Size); end; Constructor FileBufferDesc.Init(BufferSize : Word; UseEms : Boolean); begin BufferDesc.Init(BufferSize, UseEms); Initialized:=False; Modified:=False; end; Procedure BufferChain.Init(BufSize, BufNum : Word; UseEms : Boolean); VAR I : Word; begin NumberOfBuffers:=BufNum; BufferTail:=Nil; For i:=1 to BufNum do begin New(BufferHead,Init(BufSize,UseEms)); BufferHead^.Next:=BufferTail; BufferTail:=BufferHead; end; While BufferTail^.Next<>Nil do BufferTail:=BufferTail^.Next; end; Procedure BufferChain.ChainAtEnd(VAR B : FileBufferPtr); VAR BufPtr:FileBufferPtr; begin If (NumberOfBuffers>1) and (B<>BufferTail) then begin BufferTail^.Next:=B; BufferTail:=B; If B=BufferHead then begin BufferHead:=B^.Next; B^.Next:=Nil; end else begin Bufptr:=BufferHead; While BufPtr^.Next<>B do Bufptr:=BufPtr^.Next; BufPtr^.Next:=B^.Next; B^.Next:=Nil; end; end; end; Procedure BufferFile.Init(BufSize, BufNum:Word; UseEms : Boolean); VAR I : Word; begin If (BufSize=0) or (BufNum=0) then begin NoBufferIng:=True; exit; end; UseEms:=UseEms and EmsInstalled and (EmsPagesAvail>=BufNum * MemToEms(Bufsize)); Buffers.Init(BufSize, BufNum, USeEms); FlushAll:=False; ReadAll:=False; NoBufferReads:=False; NoBufferWrites:=False; NoBuffering:=False; BlockSize:=BufSize; end; Function BufferFile.FreeBuffer:FileBufferPtr; VAR BufPtr,SavePtr : FileBufferPtr; LowPos : LongInt; MyPointer : Pointer; begin BufPtr:=Buffers.BufferHead; LowPos:=$7fffffff; While BufPtr<>Nil do begin With BufPtr^ do begin If (Not Modified) or (Not initialized) then begin FreeBuffer:=BufPtr; Modified:=False; FreeBuffer:=BufPtr; Buffers.ChainAtEnd(BufPtr); Exit; end; If PosBufferNil do begin With BufPTr^ do begin If Modified then begin System.Seek(F,PosBuffer); MyPointer:=Map(0,BytesUsed); BlockWrite(F,BufferAddr^,BytesUsed); Modified:=False; end; BufPtr:=Next; end; end; end; Function BufferCHain.BuffersUnUsed:Word; VAR BufPtr : FileBufferPtr; Count : Word; begin Count:=0; BufPtr:=BufferHead; While BufPtr<>Nil do begin With BufPtr^ do begin If (Not Initialized) or (Not Modified) then Inc(Count); BufPtr:=Next; end; end; BuffersUnUsed:=Count; end; Function BufferFile.FileSize:LongInt; begin If NoBuffering then FileSize:=System.FIleSize(F) else FileSize:=Fsize div RecordSize; end; Function BufferFile.FilePos:LongInt; begin If NoBuffering then FilePos:=System.FilePos(F) else FilePos:=CurrentPos div RecordSize; end; Procedure BufferFile.Read(VAR A; NumRecs : Word); VAR I,J : Word; BufPtr : FileBufferPtr; TargetPtr : BigBlockPtr; More : Boolean; BaseBufferToGet : LongInt; MyPointer : Pointer; begin If NoBuffering then BlockRead(F,A,NuMRecs) else begin NumRecs:=NumRecs*RecordSize; TargetPtr:=@A; Repeat BaseBufferToGet:=CurrentPos-(CurrentPos Mod BlockSize); BufPtr:=Buffers.BufferHead; More:=True; While (BufPtr<>Nil) and More Do begin With BufPtr^ do begin If (PosBuffer=BaseBufferToGet) and Initialized then more:=False else BufPtr:=Next; end; end; If BufPtr=Nil then begin If NoBufferReads then begin System.Seek(F,CurrentPos); BlockRead(F,TargetPtr^,NumRecs); Inc(CurrentPos,NumRecs); exit; end; BufPtr:=FreeBuffer; With BufPtr^ do begin System.Seek(F,BaseBufferToGet); PosBuffer:=BaseBufferToGet; MyPointer:=Map(0,BlockSize); BlockRead(F,MyPointer^,BlockSize,BytesUsed); Initialized:=True; end; If ReadAll then begin J:=Buffers.BuffersUnUsed; If J>0 then Dec(j); I:=1; While (I<= J) and (BufPtr^.BytesUsed=BlockSize) do begin Inc(BaseBufferToGet,BlockSize); BufPtr:=FreeBuffer; With BufPtr^ do begin PosBuffer:=BaseBufferToGet; MyPointer:=Map(0,BlockSize); BlockRead(F,MyPointer^,BlockSize,BytesUsed); Initialized:=True; end; Inc(I); end; end; end else begin With BufPtr^ do begin J:=CurrentPos-PosBuffer; I:=BytesUsed-j; If I>NumRecs then I:=NumRecs; MyPointer:=Map(J,I); Move(MyPointer^,TargetPtr^,I); Inc(CurrentPos,I); Dec(NumRecs,I); Inc(PtrRec(TargetPtr).Ofs,I); end; end; until NumRecs=0; end; end; Procedure BufferFile.Write(VAR A; NumRecs : Word); VAR I,J : WOrd; BufPtr : FileBufferPtr; TargetPTr,MyPointer : Pointer; BaseBufferToGet : LongInt; BytesNeeded : LongInt; OK,More : Boolean; begin If NoBuffering then BlockWrite(F,A,NumRecs) else begin TargetPtr:=@A; NumRecs:=NumRecs*RecordSize; Repeat BaseBufferToGet:=CUrrentPos-(CurrentPos Mod BlockSize); BufPtr:=Buffers.BufferHead; More:=True; While (BufPtr<>Nil) and More Do begin With BufPtr^ do begin If (Initialized) and (BaseBufferToGet=PosBuffer) then begin BytesNeeded:=CurrentPos-PosBuffer+NumRecs; If BytesNeeded>BytesUsed then begin If BytesNeeded>BlockSize then BytesUsed:=BlockSize else BytesUsed:=BytesNeeded; Fsize:=BaseBufferToGet+BytesUsed; end; More:=False; end else BufPtr:=Next; end; end; If BufPtr=Nil then begin If NoBufferWrites then begin If BaseBufferToGet<>CurrentPos then begin System.Seek(F,CurrentPos); BlockWrite(F,A,NumRecs); Inc(CurrentPos,NumRecs); exit; end; end; BufPtr:=FreeBuffer; With BufPtr^ do begin System.Seek(F,BaseBufferToGet); PosBuffer:=BaseBufferToGet; If PosBufferNumRecs then I:=NumRecs; MyPointer:=Map(J,I); Move(TargetPtr^,MyPointer^,I); Modified:=True; Inc(CurrentPos,I); Dec(NumRecs,I); Inc(PtrRec(TargetPtr).Ofs,I); end; end; until NumRecs=0; end; end; Function BufferFile.Eof:Boolean; begin If NoBuffering then Eof:=System.Eof(F) else Eof:=CurrentPos=Fsize; end; Procedure BufferFile.Seek(NewPos : LongInt); begin If NoBuffering then System.Seek(F,Newpos) else CurrentPos:=NewPos*RecordSize; end; Procedure BufferFile.Assign(Name : PathStr); begin System.Assign(F,Name); end; Procedure BufferFile.Rewrite(RecSize:Word); begin RecordSize:=RecSize; If Not NoBuffering then Recsize:=1; System.Rewrite(F,RecSize); Fsize:=0; CurrentPos:=0; end; Procedure BufferFile.Reset(RecSize : Word); begin RecordSize:=RecSize; If Not NoBuffering then RecSize:=1; System.Reset(F,RecSize); Fsize:=System.FileSize(F); CurrentPos:=0; end; Procedure BufferChain.Done; begin repeat with BufferHead^ do begin BufferTail:=BufferHead^.Next; Dispose(BufferHead,Done); BufferHead:=BufferTail; end; until Bufferhead=Nil; end; Procedure BufferFile.Done; VAR BufferTail : BufferPtr; Ok : Boolean; begin Flush; Close(F); If Not NoBuffering then Buffers.Done; end; end.