Let's take a look at the definition of the TMemoryStream class in the System.Classes.pas file:
TMemoryStream = class(TCustomMemoryStream) private FCapacity: Longint; procedure SetCapacity(NewCapacity: Longint); protected function Realloc(var NewCapacity: Longint): Pointer; virtual; property Capacity: Longint read FCapacity write SetCapacity; public destructor Destroy; override; procedure Clear; procedure LoadFromStream(Stream: TStream); procedure LoadFromFile(const FileName: string); procedure SetSize(const NewSize: Int64); override; procedure SetSize(NewSize: Longint); override; function Write(const Buffer; Count: Longint): Longint; override; function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override; end;
We note right away that variables of the longint type are used for memory addressing, which naturally explains the impossibility of using more than 2 GB. Moreover, if you look at the Read methods (there are two of them) of the immediate ancestor of TMemoryStream - TCustomMemoryStream :
function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint; begin if (FPosition >= 0) and (Count >= 0) then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count; Move((PByte(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end; function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint; begin if (FPosition >= 0) and (Count >= 0) then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count; Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end;
, then we note that the result may be counted incorrectly.
Therefore, the solution was: replace the type of variables inside TMemoryStream with NativeInt and Int64, including local variables, and rewrite the TCustomMemoryStream .Read methods, for example, to:
function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint; var diff: Int64; begin if (FPosition >= 0) and (Count >= 0) then begin diff := FSize - FPosition; if diff > 0 then begin if diff > Count then Result := Count else Result := diff; Move((PByte(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end; function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint; var diff: Int64; begin if (FPosition >= 0) and (Count >= 0) then begin diff := FSize - FPosition; if diff > 0 then begin if diff > Count then Result := Count else Result := diff; Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end;
This allows you to work with memory sizes of more than 2 GB (the test example quietly processed a 5-gigabyte file, completely copying it into the stream.
For those who want to make changes in their System.Classes, I remind you that you must first save a backup copy of this file - just in case :)
Update 1
For those who for some reason do not want to follow the options suggested above, I can offer the TSegmentedMemoryStream class - it is available for download and works (tested!).