- 14, May 2020
- #1
Demystifying pointers in Delphi
Lars Fosdal - 31/Oct/2019
Скрытая информация :: Авторизуйтесь для просмотра »
gives you the pointer memory address of the content of Variable
The ^ also is used when declaring a type pointer – i.e. a type safe reference to the memory we are going to access. A typed pointer is just the same size as the basic pointer type, since it also is just a memory reference, but we now also know the size of what the pointer refers to.
Use of pointers in APIs
APIs come in many flavours, and one frequently used model for Windows APIs is that you allocate a structure and then pass the address of that structure to the API. The API call will then fill the structure with data which you can use.
So, what is the size of TQueryNameData?
Name: PData is only a pointer and hence needs to be allocated separately.
Hence in this example, we manage the memory ourselves
Then we pass the address of our variable to the API
Another API model is to have a Open/Process/Close use pattern, i.e. you first query the API to get a handle and f.x. a list of pointers.
These pointers will then be accessible until you call the related closing API.
In these cases, the memory is often managed by the API, and if you want to keep the values, you have to copy them during the process phase.
Look at Names.Who^[ix].Name^:
The same goes for Names.Who^[ix].Address^ as well.
In modern Delphi you can actually do away with the ptr^ notation for typed pointers, and simply type ptr, and the compiler understands that it is what you refer to that you want, and not the address of what you refer to.
This means that for typed pointers, Names.Who^[ix].Address^ can be written as Names.Who[ix].Address instead.
Personally I sometimes like to use the old-school notation for clarity.
So, why can’t you keep referencing the data you received after letting go of the API?
The simple answer is that you no longer can trust the referred memory to be available and unchanged.
Typically, the API opening may allocate a block of memory that it fills and passes you, and it will release that block after you close the API.
Скрытая информация :: Авторизуйтесь для просмотра »
gives you the pointer memory address of the content of Variable
var
v: integer;
p: pointer;
begin
v := 5;
p := @v; // @ finds the address of v
- p now contains the memory address of v
- ^ = Look at that address
- p^ = v = 5
The ^ also is used when declaring a type pointer – i.e. a type safe reference to the memory we are going to access. A typed pointer is just the same size as the basic pointer type, since it also is just a memory reference, but we now also know the size of what the pointer refers to.
Use of pointers in APIs
APIs come in many flavours, and one frequently used model for Windows APIs is that you allocate a structure and then pass the address of that structure to the API. The API call will then fill the structure with data which you can use.
type
TData = Array of char;
PData = ^TData; // Defines a typed pointer
TQueryNameData = record
Length: Integer;
Name: PData;
end;
So, what is the size of TQueryNameData?
SizeOf( TQueryNameData) = SizeOf(Length) + SizeOf(Name) = 4 + 4 = 8
Name: PData is only a pointer and hence needs to be allocated separately.
Hence in this example, we manage the memory ourselves
const
MaxLen = 100;
var
GetName: TQueryNameData;
begin
// first we prepare a memory location to hold the data
// This API allows us to define a max size that it will copy
// so that we can avoid a buffer overrun
GetName.Length := MaxLen;
GetName.Name := AllocMem(GetName.Length * SizeOf(char));
Then we pass the address of our variable to the API
if QueryNameAPI(@GetName)
then begin
// GetName.Name^ should now contain data filled from the API
// NB: Remember to call FreeMem for each AllocMem
Another API model is to have a Open/Process/Close use pattern, i.e. you first query the API to get a handle and f.x. a list of pointers.
These pointers will then be accessible until you call the related closing API.
In these cases, the memory is often managed by the API, and if you want to keep the values, you have to copy them during the process phase.
type
TWho = record
Name: PAnsiString;
Address: PAnsiString;
end;
TWhoList = Array of TWho;
PWhoList = ^TWhoList;
TQueryWhoListData = record
Count: Integer;
Who: PWhoList;
end;
var
Names: TQueryWhoListData;
begin
var h: HANDLE = AcquireWhoList(@Names);
//
if h > 0 then // The API gave us a list
begin
try
for var ix := 0 to Names.Count - 1 do
Writeln(Names.Who^[ix].Name^, ' ', Names.Who^[ix].Address^);
// or copy the data to your own structure
finally
ReleaseWhoList(h); // Let go of the API
end;
end;
end;
Look at Names.Who^[ix].Name^:
- The first ^ points to the address where the array of TWho records is located.
- The second ^ points to the address where the Name Ansistring is located.
The same goes for Names.Who^[ix].Address^ as well.
In modern Delphi you can actually do away with the ptr^ notation for typed pointers, and simply type ptr, and the compiler understands that it is what you refer to that you want, and not the address of what you refer to.
This means that for typed pointers, Names.Who^[ix].Address^ can be written as Names.Who[ix].Address instead.
Personally I sometimes like to use the old-school notation for clarity.
So, why can’t you keep referencing the data you received after letting go of the API?
The simple answer is that you no longer can trust the referred memory to be available and unchanged.
Typically, the API opening may allocate a block of memory that it fills and passes you, and it will release that block after you close the API.