Contributor: NFI EXPERIMENTAL PROGRAMMING {-----------------------------------------------------------------------------} { A dynamic variable length record and array class for Delphi 2.0 } { Copyright 1996,97 NFI Experimental Programming. All Rights Reserved. } { This component can be freely used and distributed in commercial and private } { environments, provided this notice is not modified in any way and there is } { no charge for it other than nomial handling fees. Contact NFI directly for } { modifications to this agreement. } {-----------------------------------------------------------------------------} { All correspondance concerning this file should be directed to } { NFI Experimental Programming, E-Mail } { nfi@post1.com } {-----------------------------------------------------------------------------} { Date last modified: March 4, 1997 } {-----------------------------------------------------------------------------} {-----------------------------------------------------------------------------} { SORRY FOR THE LACK OF COMMENTING, but as this file was initially designed } { and documented "in-house" minimal commenting was deemed necessary. It was } { only after this file was requested by several individuals that we decided } { on releasing in to the general public. Thus commenting has escaped the } { grasp of this source. } {-----------------------------------------------------------------------------} { ----------------------------------------------------------------------------} { TNFIRecordList v2.1 } { ----------------------------------------------------------------------------} { Description: } { The TNFIRecordList class operates similarly to the traditional Pascal } { TCollection object. It creates a list consisting of TNFIRecordItem } { objects capable of storing whatever information you require. } {-----------------------------------------------------------------------------} { ----------------------------------------------------------------------------} { TNFIVarRec v2.1 } { ----------------------------------------------------------------------------} { Description: } { NOT TO BE CONFUSED WITH BORLANDS TVARREC type for variants!!! TNFIVarRec } { is a variable record class that allows easy data access. It does not } { store the internal makeup of information you have stored, so if you store } { an integer and two strings then you must remember that! } { } { Storing and retrieving this information from TNFIVarRec is relatively } { easy, take for example: } { } { I want to store an integer and two strings. How do I do this? } { STORING: } { NFIVarRec.vInteger := AnInteger; } { NFIVarRec.vString := AString1; } { NFIVarRec.vString := AString2; } { RETRIEVAL: { NFIVarRec.ResetPointer; // Point to the start of the record. Do NOT } { // call "Reset" as this will remove all data } { // contained within TNFIVarRec! } { AnInteger := NFIVarRec.vInteger; } { AString1 := NFIVarRec.vString; } { AString2 := NFIVarRec.vString; } { } { Note that you must read this information in exactly the same order as you } { stored it! } {-----------------------------------------------------------------------------} { ----------------------------------------------------------------------------} { TLongArray 2.1 } { ----------------------------------------------------------------------------} { Description: } { TLongArray is a dynamic LongInt array object. Information can be accessed } { in the same manner as an array, for example LongArray[x] but supports } { item-index removal. If you remove the first item then all other items are } { brought down one index automatically. } { } { The biggest problem associated with this object is the fact that it } { ONE based, not Zero as is traditional with C/C++ or Delphi. This is } { because all in-house work using TLongArray is one based. If someone would } { like to post a fix, feel free to contact us and let us know! } { ----------------------------------------------------------------------------} unit NFILists; interface uses SysUtils, Classes, Windows; {=======================================================================} { RECORD OBJECT TYPES } {=======================================================================} { TNFIRecordList } { This object is the generic record storage object. It maintains a } { maximum list of "MaxInt" records of type TNFIRecordItem. } type TNFIVarRec = class; TNFIRecordItem = class(TObject) private Buffer: Pointer; RecordSize: Integer; public constructor Create(ARecord: Pointer; ASize: Integer); destructor Destroy; override; published property Data: Pointer read Buffer; property Size: Integer read RecordSize; end; TNFIRecordList = class(TObject) private FItems: TList; RecordCount: Integer; public constructor Create; destructor Destroy; override; procedure AddRecord(ARecord: Pointer; Size: Integer); procedure AddTNFIVarRec(ARecord: TNFIVarRec); procedure InsertRecord(ARecord: Pointer; AtPos, Size: Integer); procedure RemoveRecord(ARecNum: Integer); function GetRecord(ARecNum: Integer): Pointer; function GetTNFIVarRec(ARecNum: Integer; var ARecord: TNFIVarRec): Boolean; function GetRecordSize(ARecNum: Integer): Integer; procedure Clear; published property Items: TList read FItems; {property Size: Integer read RecordSize;} property Count: Integer read RecordCount; end; TNFIVarRec = class(TObject) private Memory: TMemoryStream; MemorySize: LongInt; Ident: Integer; { Ident is used as a "Contents Information" identifier } { -- As in "What in the heck did I store in this record ?" } procedure SetMemorySize(ASize: LongInt); function GetMemory: Pointer; function GetSize: LongInt; procedure SetByte(AByte: Byte); procedure SetShortInt(AShortInt: ShortInt); procedure SetInteger(AInteger: Integer); procedure SetWord(AWord: Word); procedure SetLongInt(ALongInt: LongInt); procedure SetString(AString: String); procedure SetTimeStamp(ATime: TDateTime); function GetByte: Byte; function GetShortInt: ShortInt; function GetInteger: Integer; function GetWord: Word; function GetLongInt: LongInt; function GetString: String; function GetTimeStamp: TDateTime; public constructor Create; destructor Destroy; override; procedure ResetPointer; procedure Reset; procedure Move(Source: Pointer; ASize: LongInt); { MOVE: Move converts the buffer to SOURCE, and over-writes existing information } procedure MoveItem(AnItem: TNFIRecordItem); { As with the move above, but uses a TNFIRecordItem object as the source. } function Append(Source: Pointer; ASize: LongInt): Boolean; { APPEND: Appends SOURCE to the end of the current information } { THESE BLOBS ARE NOT TRADITIONAL DELPHI DATABASE BLOB OBJECTS. BLOB REFERS TO ANY GENERIC } { BINARY OBJECT THAT CAN ONLY BE READ AND WRITTEN FROM MEMORY! IF YOU NEED TO STORE A } { BITMAP (FOR INSTANCE), YOU CAN READ AND WRITE IT USING APPENDBLOB AND READBLOBEX! NOTE } { THAT TNFIVARREC AUTOMATICALLY STORES THE BLOB SIZE SO WHEN READ THE EQUIVALENT AMOUNT } { OF MEMORY IS ALLOCATED. MAXIMUM BLOB SIZE IS 2 GB. } function AppendBlob(Source: Pointer; ASize: LongInt): Boolean; { APPENDBLOB: Appends SOURCE to the end of the current information storing the size of the BLOB} function ReadBlob(var Buffer: Pointer): LongInt; { READBLOB: Reads a BLOB record from the current position within the file. } { Buffer SHOULD NOT be assigned as ReadBlob will change it's location !!! } { Buffer is in fact set to point to the internal storage area, so prior } { to making any changes make sure that you have copied this information } { out! } function ReadBlobEx(var Buffer: Pointer): LongInt; { READBLOBEX: Creates a new memory buffer of the appropriate size and copies the } { contents of the BLOB into this new buffer. This allows the programmer } { to directly perform read/write operations on the buffer, unlike READBLOB } function AppendPChar(Source: PChar): Boolean; procedure ReadPChar(Buffer: PChar); function LoadFromFile(AFileName: String): Boolean; { Clears out all information prior to loading and points to START! } procedure LoadFromStream(AStream: TStream); procedure SaveToFile(AFileName: String); procedure SaveToStream(var AStream: TStream); property vByte: Byte read GetByte write SetByte; property vShortInt: ShortInt read GetShortInt write SetShortInt; property vInteger: Integer read GetInteger write SetInteger; property vWord: Word read GetWord write SetWord; property vLongInt: LongInt read GetLongInt write SetLongInt; property vLong: LongInt read GetLongInt write SetLongInt; property vString: String read GetString write SetString; property vTime: TDateTime read GetTimeStamp write SetTimeStamp; property Data: Pointer read GetMemory; property Size: LongInt read GetSize; property Capacity: LongInt read MemorySize write SetMemorySize; property ID: Integer read Ident write Ident default 0; // Set to zero anyway, end; // but hey... // TLongArray is one based! TLongArray = class private Buffer: Pointer; BufferSize, DataSize: LongInt; procedure Grow; // Conducts a 4k increment procedure Truncate; // Truncates the buffer at datasize function GetCount: LongInt; function GetAtPos(Index: Integer): LongInt; procedure ReplacePos(Index: Integer; AValue: LongInt); public constructor Create; destructor Destroy; override; procedure Reset; procedure Add(ALongInt: LongInt); procedure Remove(AtPos: LongInt); procedure Insert(AtPos: LongInt; ALongInt: LongInt); procedure Replace(AtPos: LongInt; ALongInt: LongInt); procedure Inc(AtPos, ANum: LongInt); procedure Dec(AtPos, ANum: LongInt); property At[Index: Integer]: LongInt read GetAtPos write ReplacePos; default; property Size: LongInt read DataSize; property Data: Pointer read Buffer; property Count: LongInt read GetCount; end; implementation {=======================================================================} { ** TNFIRECORDITEM CODE } {=======================================================================} constructor TNFIRecordItem.Create(ARecord: Pointer; ASize: Integer); begin RecordSize := ASize; GetMem(Buffer, ASize); Move(ARecord^, Buffer^, RecordSize); end; destructor TNFIRecordItem.Destroy; begin FreeMem(Buffer, RecordSize); end; {=======================================================================} { ** TNFIRECORDLIST CODE } {=======================================================================} constructor TNFIRecordList.Create; begin RecordCount := 0; FItems := TList.Create; end; destructor TNFIRecordList.Destroy; begin if FItems <> nil then Clear; FItems.Free; end; procedure TNFIRecordList.AddRecord(ARecord: Pointer; Size: Integer); var NewRecord: TNFIRecordItem; P: Pointer; begin NewRecord := TNFIRecordItem.Create(ARecord, Size); FItems.Add(NewRecord); Inc(RecordCount); end; procedure TNFIRecordList.AddTNFIVarRec(ARecord: TNFIVarRec); begin If Assigned(ARecord) Then AddRecord(ARecord.Data, ARecord.Size); end; // Returns a pointer to the data held by a TNFIRecordItem, not a pointer to the // object itself. { The procedure itself uses "ARecord" because without it, the compiler gave } { me an error it informs me I shouldn't have. Anyway, it works now so... } function TNFIRecordList.GetRecord(ARecNum: Integer): Pointer; var ARecord: TNFIRecordItem; begin If (RecordCount = 0) or (ARecNum < 1) or (ARecNum > RecordCount) Then ARecord := nil Else ARecord := FItems[ARecNum - 1]; If ARecord <> nil Then GetRecord := ARecord.Data Else GetRecord := nil; end; function TNFIRecordList.GetTNFIVarRec(ARecNum: Integer; var ARecord: TNFIVarRec): Boolean; var P: Pointer; begin GetTNFIVarRec := False; If Assigned(ARecord) Then begin P := GetRecord(ARecNum); ARecord.Move(P, GetRecordSize(ARecNum)); GetTNFIVarRec := True; end; end; function TNFIRecordList.GetRecordSize(ARecNum: Integer): Integer; begin If (RecordCount = 0) or (ARecNum < 1) or (ARecNum > RecordCount) Then GetRecordSize := -1 Else GetRecordSize := TNFIRecordItem(FItems[ARecNum - 1]).Size; end; procedure TNFIRecordList.InsertRecord(ARecord: Pointer; AtPos, Size: Integer); var NewRecord: TNFIRecordItem; begin NewRecord := TNFIRecordItem.Create(ARecord, Size); FItems.Insert(AtPos - 1, NewRecord); // As this is a 1 based array, subtract Inc(RecordCount); // one from it as TList is zero-based. end; procedure TNFIRecordList.RemoveRecord(ARecNum: Integer); var ARecord: TNFIRecordItem; begin If (RecordCount > 0) and (ARecNum > 0) and (ARecNum <= RecordCount) Then begin ARecord := FItems[ARecNum - 1]; FItems.Delete(ARecNum - 1); ARecord.Free; Dec(RecordCount); end; end; procedure TNFIRecordList.Clear; var i: Integer; begin If RecordCount > 0 Then For i := RecordCount downto 1 Do RemoveRecord(i); end; {=======================================================================} { ** TNFIVarRec CODE } {=======================================================================} constructor TNFIVarRec.Create; begin MemorySize := 8192; try Memory := TMemoryStream.Create; except Abort; end; end; destructor TNFIVarRec.Destroy; begin If Assigned(Memory) Then Memory.Free; end; procedure TNFIVarRec.ResetPointer; begin Memory.Position := 0; end; procedure TNFIVarRec.Reset; begin ResetPointer; Memory.Clear; end; procedure TNFIVarRec.SetMemorySize(ASize: LongInt); begin If Assigned(Memory) Then Memory.SetSize(ASize); end; procedure TNFIVarRec.Move(Source: Pointer; ASize: LongInt); begin Reset; Memory.WriteBuffer(Source^, ASize); end; procedure TNFIVarRec.MoveItem(AnItem: TNFIRecordItem); begin If Assigned(AnItem) Then Move(AnItem.Data, AnItem.Size); end; function TNFIVarRec.Append(Source: Pointer; ASize: LongInt): Boolean; begin try Memory.WriteBuffer(Source^, ASize); Append := True; except Append := False; end; end; { THESE BLOBS ARE NOT TRADITIONAL DELPHI DATABASE BLOB OBJECTS. BLOB REFERS TO ANY GENERIC } { BINARY OBJECT THAT CAN ONLY BE READ AND WRITTEN FROM MEMORY! IF YOU NEED TO STORE A } { BITMAP (FOR INSTANCE), YOU CAN READ AND WRITE IT USING APPENDBLOB AND READBLOB! NOTE } { THAT TNFIVARREC AUTOMATICALLY STORES THE BLOB SIZE SO WHEN READ THE EQUIVALENT AMOUNT } { OF MEMORY IS ALLOCATED. MAXIMUM BLOB SIZE IS 2 GB. } function TNFIVarRec.AppendBlob(Source: Pointer; ASize: LongInt): Boolean; begin try Memory.WriteBuffer(ASize, 4); Memory.WriteBuffer(Source^, ASize); AppendBlob := True; except AppendBlob := False; end; end; { Please read the associated notes located at the class definition } function TNFIVarRec.ReadBlob(var Buffer: Pointer): LongInt; var BlobSize: LongInt; begin try Memory.ReadBuffer(BlobSize, 4); Buffer := Pointer(LongInt(Memory.Memory) + Memory.Position); Memory.Position := Memory.Position + BlobSize; ReadBlob := BlobSize; except Buffer := nil; ReadBlob := -1; end; end; { Please read the associated notes located at the class definition } function TNFIVarRec.ReadBlobEx(var Buffer: Pointer): LongInt; var BlobSize: LongInt; P: Pointer; begin try Memory.ReadBuffer(BlobSize, 4); P := Pointer(LongInt(Memory.Memory) + Memory.Position); Buffer := AllocMem(BlobSize); System.Move(P^, Buffer^, BlobSize); Memory.Position := Memory.Position + BlobSize; ReadBlobEx := BlobSize; except Buffer := nil; ReadBlobEx := -1; end; end; function TNFIVarRec.LoadFromFile(AFileName: String): Boolean; var FileStream: TFileStream; begin LoadFromFile := False; try FileStream := TFileStream.Create(AFilename, fmOpenRead); Reset; Memory.CopyFrom(FileStream, FileStream.Size); Memory.Position := 0; LoadFromFile := True; finally If Assigned(FileStream) Then FileStream.Free; end; end; procedure TNFIVarRec.LoadFromStream(AStream: TStream); begin Reset; Memory.CopyFrom(AStream, AStream.Size); Memory.Position := 0; end; procedure TNFIVarRec.SaveToFile(AFileName: String); var FileStream: TFileStream; begin try FileStream := TFileStream.Create(AFilename, fmCreate); ResetPointer; // NOT RESET! We want to keep all information! FileStream.CopyFrom(Memory, Memory.Size); finally If Assigned(FileStream) Then FileStream.Free; end; end; procedure TNFIVarRec.SaveToStream(var AStream: TStream); begin ResetPointer; AStream.CopyFrom(Memory, Memory.Size); end; function TNFIVarRec.GetMemory: Pointer; begin GetMemory := Memory.Memory; end; function TNFIVarRec.GetSize: LongInt; begin GetSize := Memory.Size; end; procedure TNFIVarRec.SetByte(AByte: Byte); begin Memory.WriteBuffer(AByte, 1); end; function TNFIVarRec.GetByte: Byte; var AResult: Byte; begin Memory.ReadBuffer(AResult, 1); GetByte := AResult; end; procedure TNFIVarRec.SetShortInt(AShortInt: ShortInt); begin SetByte(AShortInt); end; function TNFIVarRec.GetShortInt: ShortInt; begin GetShortInt := GetByte; end; procedure TNFIVarRec.SetWord(AWord: Word); begin Memory.WriteBuffer(AWord, 2); end; procedure TNFIVarRec.SetInteger(AInteger: Integer); begin SetWord(AInteger); end; function TNFIVarRec.GetInteger: Integer; var AResult: Integer; begin Memory.ReadBuffer(AResult, 2); GetInteger := AResult; end; function TNFIVarRec.GetWord: Word; begin GetWord := GetInteger; end; procedure TNFIVarRec.SetLongInt(ALongInt: LongInt); begin Memory.WriteBuffer(ALongInt, 4); end; function TNFIVarRec.GetLongInt: LongInt; var AResult: LongInt; begin Memory.ReadBuffer(AResult, 4); GetLongInt := AResult; end; procedure TNFIVarRec.SetString(AString: String); var P: Pointer; begin GetMem(P, Length(AString) + 1); StrPCopy(P, AString); Memory.WriteBuffer(P^, Length(AString) + 1); FreeMem(P); end; function TNFIVarRec.GetString: String; var S: String; C: Char; begin S := ''; Repeat Memory.ReadBuffer(C, 1); If C <> #0 Then S := S + C; Until C = #0; GetString := S; end; procedure TNFIVarRec.SetTimeStamp(ATime: TDateTime); begin Memory.WriteBuffer(ATime, SizeOf(TDateTime)); end; function TNFIVarRec.GetTimeStamp: TDateTime; var AResult: TDateTime; begin Memory.ReadBuffer(AResult, SizeOf(TDateTime)); GetTimeStamp := AResult; end; function TNFIVarRec.AppendPChar(Source: PChar): Boolean; begin try Memory.WriteBuffer(Source^, StrLen(Source) + 1); { Include the terminating #0 } AppendPChar := True; except AppendPChar := False; end; end; procedure TNFIVarRec.ReadPChar(Buffer: PChar); begin try StrCopy(Buffer, Pointer(LongInt(Memory.Memory) + Memory.Position)); except Buffer := nil; end; end; {=======================================================================} { ** TLONGARRAY CODE } {=======================================================================} constructor TLongArray.Create; begin Reset; end; destructor TLongArray.Destroy; begin If Assigned(Buffer) Then FreeMem(Buffer); end; procedure TLongArray.Reset; begin If Assigned(Buffer) Then FreeMem(Buffer); BufferSize := 8192; Buffer := AllocMem(BufferSize); DataSize := 0; end; procedure TLongArray.Grow; { Add 4 extra kb to the end of the buffer } begin System.Inc(BufferSize, 4096); ReAllocMem(Buffer, BufferSize); end; procedure TLongArray.Truncate; begin ReAllocMem(Buffer, DataSize); BufferSize := DataSize; end; procedure TLongArray.Add(ALongInt: LongInt); var P: ^LongInt; begin If DataSize = BufferSize Then Grow; P := Buffer; System.Inc(LongInt(P), DataSize); P^ := ALongInt; System.Inc(DataSize, 4); end; procedure TLongArray.Remove(AtPos: LongInt); var P, Q: Pointer; CopyAmount: LongInt; begin If (DataSize > 0) and (AtPos <= (DataSize div 4)) Then begin P := Buffer; System.Inc(LongInt(P), AtPos * 4); // Point it past the record we are deleting Q := Buffer; System.Inc(Longint(Q), (AtPos - 1) * 4); // Point it to the one we are deleting CopyAmount := DataSize - (AtPos * 4); If CopyAmount > 0 Then System.Move(P^, Q^, CopyAmount); System.Dec(DataSize, 4); If DataSize > 8192 Then Truncate; end; end; procedure TLongArray.Insert(AtPos: LongInt; ALongInt: LongInt); var P, TempBuffer: Pointer; Q: ^LongInt; CopyAmount: LongInt; begin If (AtPos > 0) and ((AtPos - 1 ) * 4 <= DataSize) Then begin If BufferSize = DataSize Then Grow; P := Buffer; System.Inc(LongInt(P), (AtPos - 1) * 4); Q := P; CopyAmount := DataSize - ((AtPos - 1) * 4); If CopyAmount > 0 Then begin GetMem(TempBuffer, CopyAmount); System.Move(P^, TempBuffer^, CopyAmount); System.Inc(LongInt(P), 4); System.Move(TempBuffer^, P^, CopyAmount); FreeMem(TempBuffer); end; Q^ := ALongInt; System.Inc(DataSize, 4); end; end; function TLongArray.GetAtPos(Index: Integer): LongInt; var P: ^LongInt; begin GetAtPos := 0; If (Index > 0) and ((Index - 1) * 4 <= DataSize) Then begin P := Buffer; System.Inc(LongInt(P), (Index - 1) * 4); GetAtPos := P^; end; end; function TLongArray.GetCount: LongInt; begin If DataSize = 0 Then GetCount := 0 Else GetCount := DataSize div 4; end; procedure TLongArray.Replace(AtPos: LongInt; ALongInt: LongInt); var P: ^LongInt; begin If (AtPos > 0) and ((AtPos - 1) * 4 <= DataSize) Then begin P := Buffer; System.Inc(LongInt(P), (AtPos - 1) * 4); P^ := ALongInt; end; end; // Used for setting the "write" property of "At" procedure TLongArray.ReplacePos(Index: Integer; AValue: LongInt); begin Replace(Index, AValue); end; procedure TLongArray.Inc(AtPos, ANum: LongInt); var i: LongInt; begin i := At[AtPos] + ANum; Replace(AtPos, i); end; procedure TLongArray.Dec(AtPos, ANum: LongInt); var i: LongInt; begin i := At[AtPos] - ANum; Replace(AtPos, i); end; end.