An understanding of pointers is important for successfully using the Win32 API. This tutorial provides a quick introduction to the concept of pointers in the Object Pascal programming language. A sample program is developed to aid in understanding.
var
myString: string;
myCharPtr : PChar;
i : Integer;
begin
// Create a string of Char's
myString:= 'Hello World';
// Point to the first character in the string
i := 1;
myCharPtr :=
Addr(myString[i]);
// Display all characters in the string
while i <= Length(myString) do
begin
ShowMessage(myCharPtr^);// Display the
string characters one by one
Inc(i);
Inc(myCharPtr);
end;
end;
There are two things to note here. First the use of
Addr function to
get the address of the string. You could equally use the @ operator.
Pointers always work with addresses - the address of a variable here, or a block
of acquired memory. Here we point the PChar value to the first character in the
string.
Secondly, now that we have a pointer, we use the ^ character at the end
of the pointer name to refer to what the pointer points to. In this case,
a character.
A PChar^ will always give us a character. A PInt64^, for example, will give us
an Int64 value. This is where the typing comes in.
Record pointersYou can define a pointer to any data type using a
different technique:
var
myRecordPtr : ^TMyRecord;
Here, the ^ symbol is used to dereference the type - we are saying that
we do not have a TMyRecord type, but a pointer to one. Note that this is a
prefix use of ^.
Let us create a full record example :
>type
TMyRecord = Record
name : String[20];
age: Integer;
end;
var
myRecord: TMyRecord;
myRecordPtr : ^TMyRecord;
begin
myRecord.name := 'Fred Bloggs';
myRecord.age:= 23;
When we simpy refer to the record field name, without a ^, Delphi is in
fact adding one for us - it recognises what we are doing, and helps us make for
more readable code.
A full memory handling exampleIn this example, we'll build a new class
that is a limited number list equivalent to the
TStringList
class. This class will allow you to keep adding numbers to a list of numbers.
The class uses pointers to help store the numbers in a block of memory,
reallocating this block when it is all used up.
First off, we will look at the constructor :
var
msCount: Integer; // Count of numbers in
the list
maxCount : Integer; // Maximum numbers that
can fit into current storage
memStart : Pointer; // Start of the memory
holding the list
nextSlot : PInt64;// Points to the next
free slot in memory
const
ALLOCATE_SIZE = 20; // How many numbers to
store in first memory block
//
Constructor - initialise everything
constructor TNumberList.Create;
begin
msCount:= 0;// No numbers in the list yet
// Allocate space for a limited number
of numbers
GetMem(memStart,
ALLOCATE_SIZE * SizeOf(Int64));
// Indicate how many numbers that we can add
before acquiring more memory
maxCount := ALLOCATE_SIZE;
// And point to the next free memory slot - the
first!
nextSlot := memStart;
end;
The role of the constructor is to initialise the class. The key part of this is
to allocate a block of memory that can hold 20 numbers. We'll use Int64 numbers
(for some reason, Delphi does not provide an Integer pointer).
The GetMem call allocates storage of the desired size, setting the
memStart generalised Pointer variable to the starting address of the memory
allocated. Note that GetMem insists on a Pointer variable.
We'll add a routine to add a value to the memory :
Add a number to the list
procedure TNumberList.Add(const number : Int64);
begin
// Store the number at the next slot in our
memory block
nextSlot^ := number;
// And update things to suit
Inc(msCount);
Inc(nextSlot);
end;
The passed number is stored in the next Int64 slot in our memory block, and this
nextSlot pointer incremented. Note that this adds SizeOf(Int64)
bytes to the address value in this pointer, because the Inc call knows the type
of this pointer.
And here is a routine for retrieving a value :
Get the number at the index position (starting at 0)
function TNumberList.GetValue(index : Integer): Int64;
var
numberPtr : PInt64;
begin
// Simply get the value at the given Int64
index position
numberPtr := memStart;
Inc(numberPtr, index); // Point to the
index'th Int64 number in storage
Result := numberPtr^;// And get the Int64
number it points to
end;
Here we use Inc to add index Int64 size bytes to the start of memory to
get to the slot of the required memory.
However, we have not yet covered the situation where the memory we allocate is
all used up. We will extend the Add routine to do just this :
//
Add a number to the list
procedure TNumberList.Add(const number : Int64);
var
newMemoryStart : Pointer;
oldPtr, newPtr : PInt64;
i : Integer;
begin
// if we do not have enough space
to add the number, then get more space!
if msCount = maxCount then
begin
// First allocate a bigger memory space
GetMem(newMemoryStart, (maxCount + ALLOCATE_SIZE) * SizeOf(Int64));
// Copy the data from the old memory here
oldPtr := memStart;
newPtr := newMemoryStart;
for i := 1 to maxCount do
begin
// Copy one number at a time
newPtr^ := oldPtr^;
Inc(oldPtr);
Inc(newPtr);
end;
// Free the old memory
FreeMem(memStart);
// And now refer to the new memory
memStart := newMemoryStart;
nextSlot := memStart;
Inc(nextSlot, maxCount);
Inc(maxCount, ALLOCATE_SIZE);
end;
// Now we can safely add the number to the list
nextSlot^ := number;
// And update things to suit
Inc(msCount);
Inc(nextSlot);
end;
Here we abandon our old memory block (Delphi cannot let us extend the size of
it), and create a bigger one. Having allocated it, we must copy the old memory
contents to it. Here we see a new concept - assigning the value referred by one
pointer to the contents of memory pointed to by another. Delphi knows to copy
the whole Int64 value rather than just one byte because these are PInt64
pointers.
Below is the full code of the class :
unit NumberList;
interface
type
TNumberList = class
private
msCount: Integer; // Count of numbers in
the list
maxCount : Integer; // Maximum numbers that
can fit into current storage
memStart : Pointer; // Start of the memory
holding the list
nextSlot : PInt64;// Points to the next
free slot in memory
functionGetValue(index : Integer) : Int64;
public
propertyItems[index : Integer] : Int64
readGetValue; default; // Default
means we can use the list[i]
published
constructor Create;
destructorDestroy; override;
procedure Add(const number : Int64);
propertyCount : Integer
readmsCount;
end;
implementation
const
ALLOCATE_SIZE = 20; // How many numbers to
store in first memory block
//
Constructor - initialise everything
constructor TNumberList.Create;
begin
msCount:= 0;// No numbers in the list yet
// Allocate space for a limited number
of numbers
GetMem(memStart, ALLOCATE_SIZE * SizeOf(Int64));
// Indicate how many numbers that we can add
before acquiring more memory
maxCount := ALLOCATE_SIZE;
// And point to the next free memory slot - the
first!
nextSlot := memStart;
end;
// Destructor
- release storage obtained
destructor TNumberList.Destroy;
begin
// Free the allocated memory
FreeMem(memStart);
// Call TObject destructor
inherited;
end;
// Add a
number to the list
procedure TNumberList.Add(const number : Int64);
var
newMemoryStart : Pointer;
oldPtr, newPtr : PInt64;
i : Integer;
begin
// if we do not have enough space
to add the number, then get more space!
if msCount = maxCount then
begin
// First allocate a bigger memory space
GetMem(newMemoryStart, (maxCount + ALLOCATE_SIZE) * SizeOf(Int64));
// Copy the data from the old memory here
oldPtr := memStart;
newPtr := newMemoryStart;
for i := 1 to maxCount do
begin
// Copy one number at a time
newPtr^ := oldPtr^;
Inc(oldPtr);
Inc(newPtr);
end;
// Free the old memory
FreeMem(memStart);
// And now refer to the new memory
memStart := newMemoryStart;
nextSlot := memStart;
Inc(nextSlot, maxCount);
Inc(maxCount, ALLOCATE_SIZE);
end;
// Now we can safely add the number to the list
nextSlot^ := number;
// And update things to suit
Inc(msCount);
Inc(nextSlot);
end;
// Get the
number at the index position (starting at 0)
function TNumberList.GetValue(index : Integer): Int64;
var
numberPtr : PInt64;
begin
// Simply get the value at the given Int64
index position
numberPtr := memStart;
Inc(numberPtr, index); // Point to the
index'th Int64 number in storage
Result := numberPtr^;// And get the Int64
number it points to
end;
end.
And here is how the code could be used :
var
list: TNumberList;
value : Int64;
i : Integer;
begin
// Create a number list object
list := TNumberList.Create;
// Add the first 30 even numbers to the list,
each doubled in size
for i := 0 to 29 do
list.Add(i * 2);
// Get the 22nd value = 44 (22 * 2)
value := list[22];
ShowMessage('22nd value = '+IntToStr(value));
end;