The Delphi Language, Part 1 - Pointers
(Page 9 of 10 )
A pointer is a variable that contains a memory location. You already saw an example of a pointer in the PChar type earlier in this chapter. Delphi's generic pointer type is called, aptly, Pointer. A Pointer is sometimes called an untyped pointer because it contains only a memory address, and the compiler doesn't maintain any information on the data to which it points. That notion, however, goes against the grain of Delphi's type-safe nature, so pointers in your code will usually be typed pointers.
Note - In .NET, the System.IntPtr type is used to represent an opaque, untyped pointer.
Typed pointers are declared by using the ^ (or pointer) operator in the Type section of your program. Typed pointers help the compiler keep track of exactly what kind of type a particular pointer points to, thus enabling the compiler to keep track of what you're doing (and can do) with a pointer variable. Here are some typical declarations for pointers:
Type
PInt = ^Integer; // PInt is now a pointer to an Integer
Foo = record // A record type
GobbledyGook: string;
Snarf: Double;
end;
PFoo = ^Foo; // PFoo is a pointer to a foo type
var
P: Pointer; // Untyped pointer
P2: PFoo; // Instance of PFoo
Note - C/C++ programmers will notice the similarity between Delphi's ^ operator and C's * operator. Delphi's Pointer type corresponds to the C/C++ void * type.
Remember that a pointer variable only stores a memory address. Allocating space for whatever the pointer points to is your job as a programmer. Previous versions of Delphi had many functions that enabled a developer to allocate and deallocate memory; however, because direct memory allocation is so rare in .NET, it is typically accomplished only via the System.Runtime.InteropServices.Marshal class. The following code demonstrates how to use this class to create and free a block of memory, as a well as copy some array data in and out of the block.
{$UNSAFECODE ON}
type
TArray = array[0..31] of Char;
procedure ArrayCopy; unsafe;
var
A1: TArray;
A2: array of char;
P: IntPtr;
begin
A1 := 'safety first'; // fill character array
SetLength(A2, High(TArray) + 1);
P := Marshal.AllocHGlobal(High(TArray) + 1);
try
Marshal.Copy(A1, 0, P, High(TArray) + 1); // copy A1 to temp
Marshal.Copy(P, A2, 0, High(TArray) + 1); // copy temp to A2
MessageBox.Show(A2); // show changed array
finally
Marshal.FreeHGlobal(P);
end;
end;
If you want to access the data that a particular pointer points to, follow the pointer variable name with the ^ operator. This method is known as dereferencing the pointer. The following code illustrates working with pointers:
procedure PointerFun; unsafe;
var
I: Integer;
PI: ^Integer;
begin
I := 42;
PI := @I; // points to I
PI^ := 24; // changes I
MessageBox.Show(I.ToString);
end; The Delphi compiler employs strict type checking on pointer types. For example, the variables a and b in the following example aren't type compatible:
var
a: ^Integer;
b: ^Integer;
By contrast, the variables a and b in the equivalent declaration in C are type compatible:
int *a;
int *b The Delphi language creates a unique type for each pointer-to-type declaration, so you must create a named type if you want to assign values from a to b, as shown here:
type
PtrInteger = ^Integer; // create named type
var
a: PtrInteger;
b: PtrInteger; // now a and b are compatible
Note - When a pointer doesn't point to anything (its value is zero), its value is said to be nil, and it is often called a nil or null pointer.
Null-Terminated Strings Earlier, this chapter mentions that Delphi has three different null-terminated string types: PChar, PAnsiChar, and PWideChar. As their names imply, each of these represents a null-terminated string of each of Delphi's three character types. In .NET, PChar is an alias for PWideChar, whereas PChar is an alias for PAnsiChar in Win32. In this chapter, we refer to each of these string types generically as PChar. The PChar type in Delphi exists mainly for backward compatibility with previous versions. A PChar is defined as a pointer to a string followed by a null (zero) value. Because it is a raw, unmanaged, unsafe pointer, memory for PChar types isn't automatically allocated and managed by .NET.
In the Win32 flavor of Delphi, PChars are assignment compatible with strings. However, in .NET, these types are no longer compatible, which makes their use far less common in the .NET world.
Variant Records The Delphi language also supports variant records, which enable different pieces of data to overlay the same portion of memory in the record. Not to be confused with the Variant data type, variant records enable each overlapping data field to be accessed independently. If your background is C, you'll recognize variant records as being the same concept as a union within a C struct. The following code shows a variant record in which a Double, Integer, and Char all occupy the same memory space:
type
TVariantRecord = record
NullStrField: PChar;
IntField: Integer;
case Integer of
0: (D: Double);
1: (I: Integer);
2: (C: Char);
end;
Note - The rules of the Delphi language state that the variant portion of a record cannot be of any lifetime-managed type. This includes classes, interfaces, variants, dynamic arrays, and strings.
Here's the C equivalent of the preceding type declaration:
struct TUnionStruct
{
char * StrField;
int IntField;
union u
{
double D;
int i;
char c;
};
}; Because variant records deal with explicit memory layout, they also are considered unsafe types.
Note - Record memory layout can be controlled by the developer in .NET using the StructLayout and FieldOffset attributes.
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
Buy this book now. |
Next: Classes and Objects >>
More .NET Articles
More By Xavier Pacheco