Custom Variant Wrapper for TDataset - Part 1

This is the first in a series of articles that will attempt to document the process of creating a Delphi custom variant.  In this article I will cover the data structure required to define a custom variant.  Later articles will address the classes and methods that need to be implemented in order for you custom variant to be able to do any useful work.

To begin with, you need to understand that any Variant variable is simply a TVarData record.  This is a 16 byte block of memory that contains everything that you need to know about the Variant.  The first word (2 bytes) contains the variant type.  The content of the remaining 14 bytes is dependent on the type of the variant.  The following table shows the system defined variant types.

VarType Contents of variant
varEmpty The variant is Unassigned.
varNull The variant is Null.
varSmallint 16-bit signed integer (type Smallint in Delphi, short in C++ ).
varInteger 32-bit signed integer (type Integer in Delphi, int in C++).
varSingle Single-precision floating-point value (type Single in Delphi, float in C++).
varDouble Double-precision floating-point value (type double).
varCurrency Currency floating-point value (type Currency).
varDate Date and time value (type TDateTime).
varOleStr Reference to a dynamically allocated UNICODE string.
varDispatch Reference to an Automation object (an IDispatch interface pointer).
varError Operating system error code.
varBoolean 16-bit boolean (type WordBool).
varVariant A variant.
varUnknown Reference to an unknown object (an IInterface or IUnknown interface pointer).
varShortInt 8-bit signed integer (type ShortInt in Delphi or signed char in C++)
varByte A Byte
varWord unsigned 16-bit value (Word)
varLongWord unsigned 32-bit value (type LongWord in Delphi or unsigned long in C++)
varInt64 64-bit signed integer (Int64 in Delphi or __int64 in C++)
varStrArg COM-compatible string.
varString Reference to a dynamically allocated string (not COM compatible).

These values aren’t terribly important just now, but you will need to know them when we get to casting your variant to and from other data types.

In any case, the first thing you need to do to create a custom variant is to create a 16 byte record that will be your equivalent to the TVarData record.  As with TVarData, the first word must be set aside to hold your variant’s variant type.  More on that later.  The remaining 14 bytes are yours to do with as you will.  If you are implementing a custom variant whose data can be represented in 14 bytes or less, you can just store the data in your variant’s TVarData equivalent.  If your variant’s data is more that 14 bytes, or if you want to use the variant as a wrapper for an object (as I do) then you will need to store a reference to the data in your variant’s TVarData equivalent.

For example, suppose you wanted to create a variant that stored a measurement.  You would need to store 2 values for each measurement, the units (inches or centimeters) and the measurement itself.  Your variant’s record might look like this:

TMeasurementData = packed record
VType: TVarType;
VUnits: Char;
Reserved1: Char;
Reserved2, Reserved3: Word;
VMeasurement: Double;

Where VUnits would be ‘I’ or ‘C’ for inches or centimeters and VMeasurement would be the actual measurement.

If you want to create a wrapper for an object, like a TDataset, your TVarData equivalent would look like this:

TDatasetData = packed record
VType: TVarType;
Reserved1, Reserved2, Reserved3: Word;
VDataset: TDataset;
Reserved4: LongWord;

Where VDataset is the reference to the TDataset that we want to encapsulate.

Note that the size of both of the above records in 16 bytes exactly.

Leave a Reply