I have a chunk (1024 bytes) of shared memory between two processes for which I have an address pointing to. I want to copy some data to this shared memory, and read it on the other process. Coming from a C background, it seems easiest to map a record to this address, and then write to the record, but it does not seem to be copying correctly.
Currently, I am trying to convert the pointer to a pointer-to-record type using an Unchecked Conversion, and copy to the record, but I am seeing differences in the data when I compare the original payload with the one received in the second process.
Is this the proper way of doing this?:
type Payload_Array_Type is array (1..255) of Integer_32;
type Common_Buffer_Type is
record
Size : Integer_32;
Payload : Payload_Array_Type;
end record;
type Common_Buffer_Ptr_Type is access Common_Buffer_Type;
function Convert_Common_Memory_Ptr is new Unchecked_Conversion (
Source => System.Address,
Target => Common_Buffer_Ptr_Type);
Common_Memory_Ptr : System.Address;
procedure Copy_To_Common_Buffer
(
Size : Integer_32;
Payload : Payload_Array_Type
) is
Common_Buffer_Ptr : Common_Buffer_Ptr_Type;
begin
Common_Buffer_Ptr := Convert_Common_Memory_Ptr(Common_Memory_Ptr);
Common_Buffer_Ptr.Size := Size;
Common_Buffer_Ptr.Payload(1..255) := Payload(1..255);
end Copy_To_Common_Buffer;
I would try to do it this way:
procedure Payload is
type Payload_Array_Type is array (1..255) of Integer_32;
type Common_Buffer_Type is record
Size : Integer_32;
Payload : Payload_Array_Type;
end record;
for Common_Buffer_Type use record -- representation clause should be common to both processes
Size at 0 range 0 .. 31;
Payload at 0 range 32 .. 1023;
end record;
for Common_Buffer_Type'Size use 1024; -- check this is also used in the other process.
type Common_Buffer_Ptr_Type is access Common_Buffer_Type;
Common_Memory_Ptr : System.Address; -- assuming this is where the shared object resides with a real address, possibly make it constant
procedure Copy_To_Common_Buffer (Size : in Integer_32;
Payload : in Payload_Array_Type) is
Common_Buffer : Common_Buffer_Type;
for Common_Buffer'Address use Common_Memory_Ptr; -- address overlay
begin
Common_Buffer := (Size => Size,
Payload => Payload);
end Copy_To_Common_Buffer;
begin
Copy_To_Common_Buffer (9,(others => 876));
end Payload;
The type definitions should be common to the two processes, and note I've used a representation clause to specify where the components go.
I've used an address overlay to specify the location of where I'm writing, and written the whole record in one go.
Also look up usage of pragma volatile as #Brian Drummond suggests.
Related
I'm interfacing to a USB device (on Debian Stretch) using hidraw, and I need to process some information supplied by the USB device in the form of wchar_t* which I need to convert into (Ada) Wide_String. This is giving some trouble and I'm not seeing a clean way forward using the facilities in Interfaces.C and Interfaces.C.Strings.
All files are edited down without destroying their consistency. They will build, but without one of these, they won't actually run.
The problem is that device information like Serial Number and Product Name are presented by the Linux device driver as an access stddef_h.wchar_t from which I want to return a Wide_String or even a normal String) and I'm not seeing any good way to get there.
Interfaces.C.Strings has function Value (Item : in chars_ptr) return String; but no equivalent exists for Wide_String that I can see. So I think I need an equivalent Value function for wide characters.
The approach below uses To_Ada (from Interfaces.C) to return a Wide_String given a wchar_array. It fails, of course, because an access wchar_t is not convertible to a wchar_array.
-- helper function to deal with wchar_t * to wide_string
function Value (P : access stddef_h.wchar_t) return Wide_String is
temp : Wide_String(1 .. 256);
count : natural := 0;
-- ugliness to convert pointer types
type sd_wchar_ptr is access all stddef_h.wchar_t;
type wchar_array_ptr is access wchar_array;
Function To_Wchar_Array_Ptr is new Ada.Unchecked_Conversion(sd_wchar_ptr, wchar_array_ptr);
-- this does NOT create the required wchar_array pointer
WCP : wchar_array_ptr := To_Wchar_Array_Ptr(sd_wchar_ptr(P));
begin
Put_Line("Wide string");
To_Ada(WCP.all, temp, count);
Put_Line("Wide string length " & natural'image(count));
return temp(1..count);
end Value;
and the inevitable result
./test_hid
Wide string
Execution terminated by unhandled exception raised STORAGE_ERROR :
stack overflow or erroneous memory access
A similar character by character approach would be possible ... if (and I can't believe I'm saying this!) you could increment access types...
Feels like there's something missing from Interfaces.C here... what am I missing? any ideas to get round this relatively trivial seeming stumbling block?
EDIT : I'm leaning towards some brazen theft from the Interfaces.C.Strings sources with appropriate changes, but I'd welcome alternative suggestions.
The rest of this below is the full story so far (including all code necessary to reproduce)
Step 1 : generate low level Ada bindings automatically using gcc.
gcc -c -fdump-ada-spec-slim /usr/include/hidapi/hidapi.h
producing the low level binding package hidapi_hidapi_h
pragma Ada_2005;
pragma Style_Checks (Off);
with Interfaces.C; use Interfaces.C;
with Interfaces.C.Strings;
with stddef_h;
with System;
package hidapi_hidapi_h is
-- see source file /usr/include/hidapi/hidapi.h
type hid_device_info is record
path : Interfaces.C.Strings.chars_ptr; -- /usr/include/hidapi/hidapi.h:51
vendor_id : aliased unsigned_short; -- /usr/include/hidapi/hidapi.h:53
product_id : aliased unsigned_short; -- /usr/include/hidapi/hidapi.h:55
serial_number : access stddef_h.wchar_t; -- /usr/include/hidapi/hidapi.h:57
release_number : aliased unsigned_short; -- /usr/include/hidapi/hidapi.h:60
manufacturer_string : access stddef_h.wchar_t; -- /usr/include/hidapi/hidapi.h:62
product_string : access stddef_h.wchar_t; -- /usr/include/hidapi/hidapi.h:64
usage_page : aliased unsigned_short; -- /usr/include/hidapi/hidapi.h:67
usage : aliased unsigned_short; -- /usr/include/hidapi/hidapi.h:70
interface_number : aliased int; -- /usr/include/hidapi/hidapi.h:75
next : access hid_device_info; -- /usr/include/hidapi/hidapi.h:78
end record;
pragma Convention (C_Pass_By_Copy, hid_device_info); -- /usr/include/hidapi/hidapi.h:49
function hid_enumerate (arg1 : unsigned_short; arg2 : unsigned_short) return access hid_device_info; -- /usr/include/hidapi/hidapi.h:132
pragma Import (C, hid_enumerate, "hid_enumerate");
end hidapi_hidapi_h;
This is a low level binding, exposing C types (and the binding generator has decided that the wchar_t in Interfaces.C isn't good enough, it wants one from stddef.h too, so...
pragma Ada_2005;
pragma Style_Checks (Off);
with Interfaces.C; use Interfaces.C;
package stddef_h is
-- unsupported macro: NULL ((void *)0)
subtype size_t is unsigned_long; -- /usr/lib/gcc/x86_64-linux-gnu/6/include/stddef.h:216
subtype wchar_t is int; -- /usr/lib/gcc/x86_64-linux-gnu/6/include/stddef.h:328
end stddef_h;
Because it is a low level binding; we want to hide it (and implement RAII etc) behind a simpler and more usable high level binding, so ... (below)
with Ada.Finalization; use Ada.Finalization;
private with hidapi_hidapi_h;
private with System;
package hidapi is
type id is new natural range 0 .. 2**16 - 1;
type hid_device is new Limited_Controlled with private;
-- find first matching devices by enumeration : the RA part of RAII.
function enumerate (vendor_id, product_id : id) return hid_device;
-- accessors for device characteristics on enumerated device
function Serial_No (D : hid_device) return Wide_String;
function Product_String (D : hid_device) return Wide_String;
private
type hid_device is new Limited_Controlled with record
member : access hidapi_hidapi_h.hid_device_info;
addr : System.Address;
end record;
end hidapi;
and its implementation, containing the problem function value to return a Wide_String.
with hidapi_hidapi_h;
with Interfaces.C; use Interfaces.C;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Unchecked_Conversion;
with stddef_h;
package body hidapi is
function enumerate (vendor_id, product_id : id) return hid_device is
use hidapi_hidapi_h;
first : access hid_device_info;
begin
first := hid_enumerate(unsigned_short(vendor_id), unsigned_short(product_id));
if first /= null then
return H : hid_device do
H.member := first;
H.addr := System.Null_Address;
end return;
else raise Program_Error;
end if;
end enumerate;
-- helper function to deal with wchar_t * to wide_string
function Value (P : access stddef_h.wchar_t) return Wide_String is
temp : Wide_String(1 .. 256);
count : natural := 0;
type sd_wchar_ptr is access all stddef_h.wchar_t;
type wchar_array_ptr is access wchar_array;
Function To_Wchar_Array_Ptr is new Ada.Unchecked_Conversion(sd_wchar_ptr, wchar_array_ptr);
WCP : wchar_array_ptr := To_Wchar_Array_Ptr(sd_wchar_ptr(P));
begin
Put_Line("Wide string");
To_Ada(WCP.all, temp, count);
Put_Line("Wide string length " & natural'image(count));
return temp(1..count);
end Value;
function Serial_No (D : hid_device) return Wide_String is
use hidapi_hidapi_h;
begin
return Value(D.member.serial_number);
end Serial_No;
function Product_String (D : hid_device) return Wide_String is
use hidapi_hidapi_h;
begin
return Value(D.member.product_string);
end Product_String;
end hidapi;
And of course a test case to exercise it...
with Hidapi;
with Ada.Wide_Text_IO;
procedure Test_Hid is
usbrelay_vendor_id : constant Hidapi.id := 16#16c0#;
usbrelay_product_id : constant Hidapi.id := 16#05df#;
Device : Hidapi.hid_device := Hidapi.Enumerate(usbrelay_vendor_id, usbrelay_product_id);
begin
Ada.Wide_Text_IO.Put_Line("Serial : " & Device.Serial_No);
Ada.Wide_Text_IO.Put_Line("Product : " & Device.Product_String);
end Test_Hid;
One answer, slavishly copying the approach in the package body for Tnterfaces.C.Strings with necessary changes.
The naughty stuff is in functions "+" and Peek which use Unchecked Conversions on pointers,
to permit address arithmetic. Not pointer increment, but pointer+offset. One change is that the offset has to be scaled for 4 byte characters. I haven't set that scaling in a portable manner, but note that "+" will overload for each different return type so that offsets will be scaled appropriately for different named access types.
to allow the stddef_h.wchar_t to be viewed as a Wide_Wide_Character in the absence of any type conversion function. Whether the representation is correct is another matter (here, it is) but this technique could also be used to fake the input type of a suitable conversion function like To_Ada in Interfaces.C.
The remainder is straightforward character by character handling. One other change (so far) is to return Wide_Wide_Character rather than Wide_Character (because as the stddef_h package above reveals, the stored characters are 32 bit, same size as Interfaces.C.int. I'm happy to change my interface, but Wide_String could be easily handled by Ada.Strings packages.
type sd_wchar_ptr is access all stddef_h.wchar_t;
type w_w_char_ptr is access all char32_t;
-- Two Unchecked_Conversions to allow pointer arithmetic
-- And a third to allow the resulting storage to be interpreted as Wide_Wide_Char
function To_Sd_wchar_ptr is new Ada.Unchecked_Conversion (System.Address, sd_wchar_ptr);
function To_Address is new Ada.Unchecked_Conversion (sd_wchar_ptr, System.Address);
function To_Wchar_Ptr is new Ada.Unchecked_Conversion (sd_wchar_ptr, w_w_char_ptr);
-- pointer + offset arithmetic, with offset scaled for size of stddef_h.wchar_t;
-- TODO: attempted better way of computing word size ran into type errors
function "+" (Left : sd_wchar_ptr; Right : size_t) return sd_wchar_ptr is
begin
return To_Sd_wchar_ptr (To_Address (Left) + Storage_Offset (Right) * 4);
end "+";
function Peek (From : sd_wchar_ptr) return char32_t is
begin
return To_Wchar_Ptr(From).all;
end Peek;
function Strlen (Item : sd_wchar_ptr) return size_t is
Item_Index : size_t := 0;
begin
if Item = Null then
raise Program_Error;
end if;
loop
if Peek (Item + Item_Index) = char32_nul then
return Item_Index;
end if;
Item_Index := Item_Index + 1;
end loop;
end Strlen;
function Value (Item : sd_wchar_ptr) return char32_array is
Result : char32_array (0 .. Strlen (Item));
begin
if Item = Null then
raise Program_Error;
end if;
Put_Line("String length " & size_t'image(Strlen(Item)));
-- Note that the following loop will also copy the terminating Nul
for J in Result'Range loop
Result (J) := Peek (Item + J);
end loop;
return Result;
end Value;
-- helper function to deal with wchar_t * to wide_wide_string
function Value (Item : access stddef_h.wchar_t) return Wide_Wide_String is
begin
return To_Ada (Value (sd_wchar_ptr(Item)));
end Value;
when 'B' |'b' =>
cons.Put_Line("Enter text - less than 20 chars: ");
cons.Get_Line(Item => st,
Last => m);
--buffer_ser'Write(st,m);
ser.Write(Port => S_Port,
Buffer => buffer_ser);
Defined as:
package cons renames gnat.IO;
package ser renames gnat.Serial_Communications;
S_Port : gnat.Serial_Communications.Serial_Port;
buffer_ser: ada.Streams.Stream_Element_Array(1..20);
x : Ada.Streams.Stream_Element_Offset;
m : Integer;
st : string(1..20) := (others => ASCII.NUL);
ComPort : GNAT.Serial_Communications.Port_Name(1..5);
Basically, I needed a very portable application, to operate an old device that overlays a video stream based on serial commands. I decided to try out Ada as I'd been learning it for something else.
How do I convert to a Stream_element_array(1..20) from a String(1..20) in Ada? I've tried the obvious answers, and I'm just totally stuck!
Edited as requested for the person who down voted me...
First we need an MCVE.
with gnat.IO;
with ada.Streams;
with gnat.Serial_Communications;
procedure MCVE is
package cons renames gnat.IO;
package ser renames gnat.Serial_Communications;
S_Port : gnat.Serial_Communications.Serial_Port;
buffer_ser: ada.Streams.Stream_Element_Array(1..20);
x : Ada.Streams.Stream_Element_Offset;
m : Integer;
st : string(1..20) := (others => ASCII.NUL);
ComPort : GNAT.Serial_Communications.Port_Name(1..5);
begin
cons.Put_Line("Enter text - less than 20 chars: ");
cons.Get_Line(Item => st,
Last => m);
--buffer_ser'Write(st,m);
ser.Write(Port => S_Port,
Buffer => buffer_ser);
end MCVE;
it compiles successfully and fails with the error
raised GNAT.SERIAL_COMMUNICATIONS.SERIAL_ERROR : write: port not
opened
A little reading on Streams shows that the answer to the actual question:
How do I convert to a Stream_element_array(1..20)
is : normally, you don't. Streams take care of that for you.
A couple of modifications, respectively:
make S_Port aliased so we can take its access (to allow redirectable stream Writes) and delete unnecessary intermediate variables
actually open the serial port as a Stream
write the string directly to the stream
look like
S_Port : aliased gnat.Serial_Communications.Serial_Port;
-- buffer_ser: ada.Streams.Stream_Element_Array(1..20);
-- x : Ada.Streams.Stream_Element_Offset;
...
gnat.Serial_Communications.Open(S_Port,ComPort);
String'Write(S_Port'access, st(1..m));
--buffer_ser'Write(st,m);
--ser.Write(Port => S_Port, Buffer => buffer_ser);
But using fixed length strings when they aren't appropriate is a bad idea. Let's declare the string to be of the right length and simplify further. This requires a function form of Get_Line, which the Gnat.IO package doesn't provide, so let's use the more portable Ada.Text_IO instead. (And initialise the serial port name, and actually use the renamed packages...)
When we're done, we have something like
with Ada.Text_IO;
with ada.Streams;
with gnat.Serial_Communications;
procedure MCVE is
package cons renames Ada.Text_IO;
package ser renames gnat.Serial_Communications;
S_Port : aliased ser.Serial_Port;
ComPort : ser.Port_Name := "COM1";
begin
ser.Open(S_Port,ComPort);
cons.Put_Line("Enter text : ");
declare
st : String := cons.Get_Line;
begin
String'Write(S_Port'access, st);
end;
end MCVE;
I simply don't catch why the following does not work. Could someone help me to fix it? It complains (at runtime):
raised CONSTRAINT_ERROR : variant2.adb:21 discriminant check failed
procedure Variant2 is
type POWER is (NONE,GAS, STEAM);
type VEHICLE (Engine : POWER := NONE) is
record
Model_Year : INTEGER range 1888..1992;
case Engine is
when NONE => null;
when GAS => Cylinders : INTEGER range 1..16;
when STEAM => Boiler_Size : INTEGER range 5..22;
Coal_Burner : BOOLEAN;
end case;
end record;
Works : VEHICLE;
Works_Not : access VEHICLE := new VEHICLE;
begin
Works := (GAS,1980,4); -- (1)
Works_Not.all := (GAS,1981,8); -- (2)
end Variant2;
(1) is working, but (2) does not
Thanks in advance!
The RM explicitly states that "If the designated type is composite, [...] the created object is constrained by its initial value (even if the designated subtype is unconstrained with defaults)." (RM 4.8(6/3))
which means you have to reallocate your access type
Works_Not := new VEHICLE'(GAS,1981,8);
(of course, you should deallocate the old access value first (see RM 13.11.2 Unchecked Storage Deallocation), but I leave that as an exercise)
UPDATE: as discussed in the comments
Here's an example you can play around with:
with Ada.Text_IO;
procedure Array_Of_Aliased is
type POWER is (NONE, GAS, STEAM);
type VEHICLE(Engine : POWER := NONE) is
record
Model_Year : Integer range 1888..1992;
case Engine is
when NONE => null;
when GAS => Cylinders : INTEGER range 1..16;
when STEAM => Boiler_Size : INTEGER range 5..22;
Coal_Burner : BOOLEAN;
end case;
end record;
-- array of aliased elements
type Vehicle_Array is array(1..5) of aliased VEHICLE;
-- the access type need to be "all" or "constant" in order to access aliased values
type Vehicle_Access is access all VEHICLE;
Vehicles : Vehicle_Array;
Works : Vehicle_Access;
begin
-- access to the first element of the array. Can't change discriminant this way...
Works := Vehicles(1)'Access;
Ada.Text_IO.Put_Line(POWER'Image(Works.Engine));
-- However, using the array, we _can_ change the discriminant, since it's _not_ an access value
Vehicles(1) := (STEAM, 1890, 20, True);
Vehicles(2) := (GAS, 1981, 8);
Ada.Text_IO.Put_Line(POWER'Image(Works.Engine));
-- We can still update the record elements using the access value, as long as the discriminant stays the same
Works.all := (STEAM, 1900, 15, False);
end Array_Of_Aliased;
As egilhh said, when you allocate a discriminant record using new, you can't change the discriminant of the record you allocated, even though you could do this for a variable of the type (as opposed to an allocated record). This rule has been around since Ada 83. The rationale was, I believe, that it allows the compiler to optimize space when allocating records. In your example, if we assume all the fields (including the discriminant) are 1 word, then the record will be 2 words if ENGINE=NONE, 3 words if ENGINE=GAS, 4 words if ENGINE=STEAM. When Works_Not is initialized, it's initialized to a NONE, which means it may take only 2 words on the heap (note: it's not a requirement that compilers optimize in this way). If it uses only 2 words, then reassigning the record to one with ENGINE=GAS would be a disaster--you'd be overflowing the area that you previously allocated, and stomping on other data.
Whether this was a good language design decision or not, I can't say; I don't know how many compilers, and how many applications, needed to take advantage of this optimization. Somebody 33 years ago thought it would be useful, and they must have had some good reasons for thinking so.
The restriction is annoying but not insurmountable. I've definitely run into it before, multiple times, but the simple answer is to wrap it in another record.
type VEHICLE_DATA (Engine : POWER := NONE) is
record
Model_Year : INTEGER range 1888..1992;
case Engine is
when NONE => null;
when GAS => Cylinders : INTEGER range 1..16;
when STEAM => Boiler_Size : INTEGER range 5..22;
Coal_Burner : BOOLEAN;
end case;
end record;
type VEHICLE is record
Data : VEHICLE_DATA;
end record;
Now_Works : access VEHICLE := new VEHICLE; -- still sets ENGINE=NONE
Now_Works := (Data => (Gas, 1981, 8)); -- legal
Now_Works.Data := (Gas, 1981, 8); -- legal, does the same thing
These are OK because the allocated record is a VEHICLE, which isn't a discriminant record. It's OK to change the discriminant of a subcomponent like this. That's how I've gotten around the rule.
I am having a constraint error on the following situation:
Get constrained buffer from procedure:
Get_MyBuffer(data => Buffer); -- This is ok
Buffer is of type Unsigned_Byte. Want to convert it to Byte.
function To_Byte is new Unchecked_Conversion (Source => Unsigned_Byte,
Target => Byte);
MyFunction2Pass(To_Byte(Buffer)); -- Having warning 'uncheked conversion to unconstrained array subtype is not portable.
Printing inside MyFunction2Pass
function MyFunction2Pass(Data : Byte) is
begin
New_Line(integer'image(integer(Data(1)))); -- **Shoot Constrain Error**
end
That one line of yours is doing an awful lot. There's nothing wrong with that, but it is temporarily inconvienent while you are getting this exception. You might consider splitting each routine call into its own line for now, just so you can track down which call is putting out the constraint error.
Bit : constant boolean := Data(1); -- I'm guessing this is the right type
Bit_Int : constant integer := integer(Bit);
Bit_Img : constant string := integer'image(Bit_Int);
begin
New_Line (Bit_Img);
end
Now which line is giving the constraint error? (After cleaning up any compiler errors of course).
I want to declare an array with element-type of a variant record.
Something like this:
type myStruct (theType : vehicleType) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
myArray : array (Positive range <>) of myStruct;
But in this case I get error.
It only allows this:
myArray : array (1.100) of myStruct(Car); //such as
So how to solve the index-problem and how to describe an array of a variant record's type without giving it's discriminant?
The example above will not compile. Here is a correct version (I changed mystruct to Integer for simplicity):
procedure test_array is
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Arr_T is array (Vehicle_Array_Index range <>) of Integer;
type Vehicle_Array (Size: Vehicle_Array_Index := 1) is record
Vehicles : Arr_T(1..Size);
end record;
begin
null;
end;
One of the errors was that you cannot have anonymous arrays inside records, and second, you should use the discriminant to constrain the array inside.
As mentioned above this is not a good solution for varying-length arrays as most likely you will get a maximum sized array anyway. If you want arrays with dynamically determined sizes you can use blocks for that.
declare
a: array(1..n) of integer; -- n can be a variable here
begin
--some code using a
end;
It also works in the declaration parts of procedures and functions where n can be a parameter passed to the subprogram (one of the advantages Ada has over C/C++). And of course you can just allocate arrays dynamically on the heap using allocators.
If you want to be able to create objects of a discriminated type and change (or figure out) what the type is at runtime, you have to give the discriminant a default value in the declaration.
The reason for this is that Ada doesn't want to have to worry about dealing with uninitialized discriminated objects that it can't even figure out the size and valid record fields of.
For this reason, and some reasons I go into a bit in the comments, Ada discriminated records aren't actually very useful in mixed language programming (eg: exactly duplicating a C union). They can be handy on their own terms though.
For the example you give, you'd do the following (warning: Not compiled):
type myStruct (theType : vehicleType := car) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
Then you could set one of the array values at runtime thusly:
myArray(20) := (theType => car,
field1 => myArray(20).field1,
... , --// Fill in the rest of the fields by name
numberOfWheels => 4);
As for this part:
myArray : array (Positive range <>) of myStruct;
You cannot declare actual array objects with an indeterminate range like this. You could declare a type that way, but an object has to have an actual size. If you want an array of varying length, you can once again use a variant record. (Again, not compiled)
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Vehicle_Array (Vehicle_Array_Index : Size := 1) is record
Vehicles : array (Vehicle_Array_Index range <>) of myStruct;
end record;
Oh, and one more thing. You aren't doing this in your example, but if you ever want to use your discriminant to size an array like above, beware. When you declare objects of that type (again, assuming you used a default for the discriminant), the compiler will try to reserve enough space for the largest possible size you could ever feed it a value for. That makes it a Very Bad Idea to make a discriminated array indexed by something with a huge range like Integer or Positive. I know computers are bigger these days, but still most folks don't have 4 gig to spare for one silly little array. So if you are using your discriminant as an array index, it would be a good idea to create a smaller subtype of Integer to use for the type of the discriminant.