|
Pointers |
Top Previous Next |
|
Pretranslated C# code > Pointers
Overview
Delphi2C# includes a small pointer runtime that helps translated Delphi code preserve common Delphi pointer semantics in managed C#. The runtime supports constructs such as:
C# does not provide Delphi-compatible managed pointer types. Delphi2C# therefore uses small pointer structs that simulate the most important Delphi operations while retaining bounds checks and managed-memory integration where possible.
The pointer runtime is a compatibility layer for translated Delphi code. It is not intended to replace normal C# arrays, spans, strings, references, or safe handles in newly written C# code.
Pointer values and backing storage
The runtime separates a pointer value from the storage to which it points.
A pointer value contains a view consisting conceptually of:
The backing storage may represent:
Pointer, Pointer<T>, UntypedPointer, and PChar are value types. Assigning a pointer copies its current position, just as assigning a Delphi pointer copies an address:
Pointer<int> p = new Pointer<int>(values); Pointer<int> q = p;
q++;
After this code, q points to the next element while p still points to the original element. Both pointers share the same backing storage, so writes performed through either pointer remain visible through the other pointer.
This distinction is important:
Null pointers
The default value of every pointer struct represents Delphi nil:
PChar p = default; Pointer raw = default; Pointer<int> typed = default;
Use IsNull() to test a pointer:
if (p.IsNull()) { // Delphi: if P = nil then }
Use default or SetNull() to clear a pointer value:
p = default; // or p.SetNull();
Do not normally declare pointer structs as nullable values:
// Normally not recommended: PChar? p = null;
default(PChar) already represents a null pointer.
Dereferencing a null pointer throws NullReferenceException. Access outside a known pointer view throws a bounds exception.
Shared views and pointer copies
Pointer conversions and pointer arithmetic normally create new views over the same backing storage. They do not copy the underlying data.
Pointer<int> typed = new Pointer<int>(values); UntypedPointer bytes = typed.ToUntypedPointer(); Pointer raw = typed.ToPointer(); Pointer<int> next = typed + 1;
All these pointer values refer to the same storage.
Operations that explicitly create a value copy are documented separately. For example, UntypedPointer.FromValue<T>() creates a separate one-element backing and is not an alias to the original local variable.
Memory ownership
Pointer values do not individually own memory. Ownership belongs to the shared backing object.
This prevents a pointer copy from becoming a second owner of the same allocation.
Owned native memory is normally created by GetMem, AllocMem, Pointer(int), or UntypedPointer.AllocNative():
Pointer p = DelphiMemory.GetMem(256);
Release such memory with FreeMem:
DelphiMemory.FreeMem(ref p);
FreeMem releases the shared native backing and clears the supplied pointer variable. Every other pointer view that refers to the same backing becomes invalid.
SetNull() and Dispose() only clear the current pointer value. They do not release shared native memory:
Pointer q = p; q.Dispose(); // Clears q only.
DelphiMemory.FreeMem(ref p); // Releases the allocation.
Borrowed native memory cannot be freed through the pointer runtime:
Pointer borrowed = new Pointer(address, size, ownsMemory: false);
Calling FreeMemory() or FreeMem for borrowed memory throws an exception.
Pointer arithmetic units
The unit used for arithmetic depends on the pointer type:
Internally, all pointer views use byte offsets. Typed pointers convert element offsets to bytes by using DelphiTypeLayout.
Main pointer types
UntypedPointer
UntypedPointer is the canonical byte-addressed view used internally by the runtime. It is used for untyped Delphi parameters and for low-level operations such as Move and FillChar.
Pointer
Pointer corresponds to Delphi Pointer. It is a non-generic byte-addressed facade over UntypedPointer.
Pointer<T>
Pointer<T> represents a typed pointer. Dereferencing and pointer arithmetic use values of type T.
PByte
The current runtime normally represents Delphi PByte as Pointer<byte>. PByte does not require a separate storage implementation.
Depending on generator settings, generated code may use a type alias or the explicit type Pointer<byte>.
PChar and PWideChar
PChar represents a UTF-16 character pointer. In the current Unicode runtime, Delphi PWideChar uses the same representation.
Move and FillChar
Delphi Move and FillChar always use byte counts, even when the source or destination contains characters or records.
DelphiMemory.Move( source, destination, byteCount);
The runtime Move implementation:
The caller must allocate a sufficiently large destination before calling Move.
Native addresses and pinning
A pointer may be passed to a native API as an IntPtr:
IntPtr address = pointer.ToIntPtr();
For managed array or string backing, obtaining a persistent address pins the managed object. For scoped native calls, Pin() is preferred:
using PointerPin pin = pointer.Pin(); NativeFunction(pin.Address);
PointerPin.Dispose() releases the temporary pin.
If a native API returns an address inside an existing owned block, use an owner-relative view where available:
Pointer<TRecord> record = owner.CreateView<TRecord>( address, byteLength);
This preserves the relationship between the returned pointer and the owner backing. The owner must remain valid while the returned view is used.
Bounds and unsupported raw layouts
The runtime checks pointer ranges whenever the backing length is known. A pointer may be positioned one element past the end, but it cannot be dereferenced there.
A native pointer constructed with a zero capacity has an unknown length:
Pointer p = new Pointer(address);
In that case, complete bounds checking is not possible.
Raw byte access is supported for primitive values and records with a defined Delphi-compatible binary layout. Arrays whose elements contain managed references cannot be treated as arbitrary bytes.
Generated records used through raw pointers should normally have an explicit layout:
[StructLayout(LayoutKind.Sequential)] public struct TMyRecord { public int Value; public ushort Flags; }
For packed or otherwise non-standard Delphi layouts, register the expected size and optionally a binary codec with DelphiTypeLayout.
|
|
This page belongs to the Delphi2C# Documentation |
Delphi2C# home Content |