To allocate and work with a custom block of memory, you have to use the GetMem and FreeMem procedures. Both procedures take two parameters: the pointer that is to be associated with the allocated block of memory and an integer that specifies how many bytes of memory to allocate. You don't have to pass the second parameter when you call FreeMem. If you do, you have to make sure that you pass the value that matches the number of bytes allocated with the GetMem call.
The following example shows how to use the BlockRead procedure to read an entire text file into a dynamically allocated block of memory.
Listing 9-3: Loading a file into a dynamically allocated memory block
program Project1; {$APPTYPE CONSOLE} uses SysUtils; procedure ReadFile(var P: Pointer; const AFileName: string); var Src: file; BytesRead: Integer; BufferPos: Pointer; begin AssignFile(Src, AFileName); {$I-} Reset(Src, 1); {$I+} if IOResult = 0 then begin if P <> nil then FreeMem(P); { release old data } GetMem(P, FileSize(Src)); { allocate memory for the file } BytesRead := -1; BufferPos := P; while BytesRead <> 0 do begin BlockRead(Src, BufferPos^, 1024, BytesRead); Inc(Integer(BufferPos), BytesRead); end; CloseFile(Src); end; // if IOResult end; procedure RemoveFile(var P: Pointer); begin FreeMem(P); P := nil; end; var FilePtr: Pointer; begin ReadFile(FilePtr, 'c:\data.txt'); WriteLn(string(FilePtr)); RemoveFile(FilePtr); ReadLn; end.
The code for loading the file into memory is located in the ReadFile procedure. Before allocating a new memory block, the procedure has to determine if the pointer passed as the P parameter already points to a memory block. If it does, we have to release the old memory block. If we forget to release the old file from memory, we will have a huge memory leak.
if P <> nil then FreeMem(P); { release old data }
The GetMem procedure uses the FileSize function to determine the exact number of bytes needed to hold the entire file in memory. After the memory is allocated, we can start reading the file into memory.
Probably the strangest part of the procedure is the local BufferPos pointer. Before we start reading the file in the while loop, we assign the P pointer to the BufferPos pointer.
BufferPos := P;
The BufferPos pointer points to the exact location in memory where the BlockRead procedure has to store the 1,024 bytes it reads from the file.
BlockRead(Src, BufferPos^, 1024, BytesRead);
After the BlockRead procedure stores the 1,024 bytes to the memory block, we have to update the BufferPos pointer. If we forget to update the pointer, the BlockRead procedure will read the entire file but will also overwrite the old data with the latest 1,024 bytes from the file.
When the BlockRead procedure reads 1,024 bytes, we have to tell the BufferPos pointer to point to the following 1,024 bytes in memory. This is done by typecasting the BufferPos pointer to Integer and by incrementing the address of the pointer with the number of bytes read from the file.
Inc(Integer(BufferPos), BytesRead);
Without the BufferPos pointer, this procedure wouldn't work. BufferPos enables us to do something that we mustn't do with the variable pointer parameter — modify the location to where the pointer points. If we were to modify the location of the original pointer, each call to BlockRead would leak 1,024 bytes of memory, because the following line would tell the original pointer to point 1,024 farther into the memory block.
To display the entire text file, simply typecast the memory block to a string:
WriteLn(string(FilePtr));
Figure 9-1: Typecast memory block