Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations John Tel on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Variant not being initialised properly

Status
Not open for further replies.

Griffyn

Programmer
Jul 11, 2002
1,077
AU
Hi all,

Seems to be one problem after another. This time, I have a dynamic array of Variant
Code:
TMyClass = class
private
  FCacheArray = array of Variant;
end;
When I initialise it I'm using
Code:
constructor TMyClass.Create;
begin
  inherited;
  SetLength(FCacheArray, 10);
end;
Which, according to the help file, and to what most other parts of my program are doing, should make FCacheArray a 10 element array, with every element set to the Unassigned constant.

Except for one of my classes. In this case, the first element (FCacheArray[0]), is being initialised to Null, and the rest Unassigned.

Even when I try
Code:
FCacheArray[0] := Unassigned;

It's value remains at Null - even though it should change to Unassigned. If I set it to '0' it changes correctly, but if I set it again back to Unassigned, it changes to.... Null!

I've been unable to replicate it in a simple program with varying array lengths, but it consistently happens in just this one class I have, which unfortunately is hugely complex, so I won't be posting it unless necessary.

I use the Unassigned values as indicators on whether I've fetched values from a database, so it's important. I can get around this by setting up an array of Boolean to match it, but obviously fixing this would be preferable.

I'm using Delphi 6 Enterprise with Update 2, and a RTL 3 update applied. Anyone come across this or know if there's any fixes/patches out there I should have?

Many thanks.
 
You don't have to declare an array of variant like
Code:
  FCacheArray : array of Variant;
Instead you can just declare it it like this:
Code:
  FCacheArray : variant;
And when you initialize the variant use VarArrayCreate (check help for more information).

 
Hi Pikkunero,

Ok, I tried that - and it's made no difference. I used:
Code:
TMyClass = class
private
  FCacheArray : Variant
end;

constructor TMyClass.Create;
begin
  inherited;
  FCacheArray := VarArrayCreate([0, 9], varVariant)
end;
FCacheArray[0] = Null
FCacheArray[1] = Unassigned

I need variant elements, because it needs to hold different types

I tried changing my array from dynamic to static:
Code:
TMyClass = class
private
  FCacheArray[0..9] of Variant;
end;
Exactly the same thing happens - although!! Weirdness! The following shows my the actual class inheritance in my program:
Code:
TBaseClass = class(TObject)
public
  constructor Create; virtual;
end;

TMyClass = class(TBaseClass)
private
  FCacheArray[0..9] of Variant;
public
  constructor Create; override;
end;
so in the TMyClass.Create constructor, prior to calling inherited (to call TBaseClass.Create), when the code step is on 'begin', the debug watch shows that FCacheArray equals all Unassigned! I then step through the TBaseClass.Create, watching FCacheArray on every line, and it remains at Unassigned all the way.

BUT! The moment it returns to TMyClass.Create to continue on, FCacheArray[0] has changed to Null! What is going on?

I was thinking that I've done some sort of lazy assigning past an array boundary or somesuch so that memory is being overwritten that's used by my FCacheArray - but I checked and it's all good there.

And then, I changed my FCacheArray range to
Code:
FCacheArray[1..10] of Variant;
And it stays at all Unassigned! I also tried previously to declare a variant before and after my FCachearray to see if memory was getting clobbered, but it didn't change anything.

Still confused about it all - appreciate any more ideas.
 
It stays unassigned since you haven't assigned any values to the variant array items. You have only created an array with 10 elements in it.

Try setting FCacheArray[0] := 1; for instance and see what you get then.
 
Hi Pikkunero,

My apologies for not being clear. I want my new array to have all it's elements starting at Unassigned.

The problem I have is that when creating a dynamic array is that the element 0 in array[0..9] is being initialised to Null instead of Unassigned. And I cannot change it to Unassigned.

Declaring my array as array[1..10] fixes the problem, as all 10 elements start as Unassigned.

array[0..9] starts with one Null element and 9 Unassigned elements. And I cannot figure it out. My TBaseClass in my examples above has a fair bit of code in it that is used by many other classes - none of which are showing these symptoms. And I've been unable to recreate this problem using these simple example code fragments.
 
Here's little something for you. It's an example of unassigned-, null- and assigned vars. Drop 4 button and a memo to a form to test it out.

When i write unassigned vars to a memo i get 10 empty lines. When i write null vars i get text 'null' and when i have set a value to each variant i get the value out. Check the help for functions VarArrayLowBound/-Highbound and VarIsNull.

I hope this gives you more idea of what's going on or if you just missed something.
Code:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TBaseClass = Class(TObject)
    constructor Create; Virtual;
  end;

  TMyClass = class(TBaseClass)
  private
    FCacheArray : Variant;
  public
    constructor Create; override;
    procedure SetVars;
    procedure Initvars;
    procedure WriteVars(Memo: TMemo; SetToNull: boolean);
  end;

  TForm1 = class(TForm)
    Memo1: TMemo;
    AssignVars: TButton;
    WriteVars: TButton;
    WriteNullvars: TButton;
    UnassignVars: TButton;
    procedure WriteVarsClick(Sender: TObject);
    procedure AssignVarsClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure WriteNullvarsClick(Sender: TObject);
    procedure UnassignVarsClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Tempclass : TMyClass;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TMyClass }

constructor TMyClass.Create;
begin
  inherited;
  FCacheArray := VarArrayOf([Unassigned, Unassigned, Unassigned, Unassigned, Unassigned,
    Unassigned, Unassigned, Unassigned, Unassigned, Unassigned]);
  FCacheArray := VarArrayCreate([0,9], varvariant);
//  Initvars;
end;

procedure TMyClass.Initvars;
var
  i : Integer;
begin
  { Assign each item to unassigned }
  for i := VarArrayLowBound(FCacheArray, 1) to VarArrayHighBound(FCacheArray, 1) do
    FCacheArray[i] := unassigned;
end;

procedure TMyClass.SetVars;
var
  i : Integer;
begin
  { Set a value to each item }
  for i := VarArrayLowBound(FCacheArray, 1) to VarArrayHighBound(FCacheArray, 1) do
    FCacheArray[i] := i;
end;

procedure TMyClass.WriteVars(Memo: TMemo; SetToNull: boolean);
var
  i : Integer;
begin
  { Clear lines }
  Memo.lines.Clear;
  { Show each items value }
  for i := VarArrayLowBound(FCacheArray, 1) to VarArrayHighBound(FCacheArray, 1) do
  begin
    { Set items to null if requested }
    if SetToNull then
      FCacheArray[i] := Null;
    { Check if item is null }
    if VarIsNull(FCacheArray[i]) then
      Memo.Lines.Add('null')
    else
      Memo.lines.Add(VarToStr(FCacheArray[i]))
  end;
end;

procedure TForm1.WriteVarsClick(Sender: TObject);
begin
  Tempclass.WriteVars(Memo1, false);
end;

procedure TForm1.AssignVarsClick(Sender: TObject);
begin
  Tempclass.SetVars;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Tempclass := TMyClass.Create;
end;

{ TBaseClass }

constructor TBaseClass.Create;
begin

end;

procedure TForm1.WriteNullvarsClick(Sender: TObject);
begin
  Tempclass.WriteVars(Memo1, true);
end;

procedure TForm1.UnassignVarsClick(Sender: TObject);
begin
  Tempclass.Initvars;
end;

end.
 
Oops... i forgat to comment out something...

In the TMyClass.Create you can use either of those methods.

And furthermore set different values to the FCacheArray just to test it out. Set some of them to unassigned, some to null and a value to some of them.

When they are unassigned you should get an empty row to the memo...
 
I think I solved it, and it's a combination of my fault and my misunderstanding of how the debug tool works.

First: my code sets up the FCacheArray as an array of values that can be retrieved from a database record. If an element is Unassigned, it means it needs to be retrieved. A bug in this class setup meant that FCacheArray[0] would be unable to be retrieved, and so it gets set a value of Null (so that it's not continually trying to retrieve a value it cannot get).

Second: I assumed that the Debug window for Watches ran in a sandbox - which is does, but I mistakenly assumed each individual watch item ran in a sandbox, which they don't.

In my debug watch I had put in a function that in order to be evaluated tried to retrieve the value for FCacheArray[0]. It was causing an exception prior to initialising FCacheArray, and immediately assigning it Null when it FCacheArray had been initialised. The instance where it remained Unassigned during TBaseClass.Create and jumped to Null the moment it came out was because FCacheArray was a private member of TMyClass, and so wasn't visible within TBaseClass.

It all makes sense now. Thanks for your time Pikkunero - very much appreciated.
 
No problem.

I've had my share of problems so at least i can try to help :)
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top