user3045525
user3045525

Reputation: 65

Reference a Delphi variant array without copying

This might seem like a strange request, but there is a good reason (code generation application). I pass a variant array to a procedure, contained within an array of variants, as follows:

TVarArray = array of variant;

procedure TMainForm.Button1Click(Sender: TObject);
var
  params: TVarArray;
  numRows: integer;
  numCols: integer;
  i: integer;
  j: integer;
begin
  SetLength(params, 2);
  numRows := 2;
  numCols := 2;

  params[0] := 5;
  params[1] := VarArrayCreate([1, numRows, 1, numCols], varVariant);
  for i := 1 to numRows do
    for j := 1 to numCols do
      params[1][i, j] := i + j;

  TestProc(params);
end;

procedure TMainForm.TestProc(params: TVarArray);
var
  arr: variant;
  p: PVariant;
  v: variant;
begin
  arr := params[1];    // -- Copies the array to arr.
  arr[2, 2] := 99;

  p := @(params[1]);
  p^[2, 2] := 88;      // -- Directly reference the passed-in array.

  v := p^;             // -- Copies the array to v -> How to prevent?
  v[2, 2] := 77;       // -- This should change the value in the original array.

  edit1.Text := VarToStr(arr[2, 2]);             // -- 99
  edit2.Text := VarToStr(params[1][2, 2]);       // -- 88  - should be 77
  edit3.Text := VarToStr(v[2, 2]);               // -- 77
end;

I don't want to create a copy of the array, so could use p^[] to directly access the passed-in array. However, I don't want to use the p^ syntax in TestProc but would prefer to use a variable name without the ^. Of course, if I try v := p^ I just get a copy. Is there any way around this? Thanks!

Upvotes: 2

Views: 1293

Answers (1)

Rob Kennedy
Rob Kennedy

Reputation: 163287

What you're looking for is a local variable that can act as a reference for something else (in particular, an element in an array of Variant). However, Delphi provides no way of creating a "local reference" variable. References only exist in the context of parameters passed as var, out, or sometimes const.

Maybe you could introduce a subroutine and pass param[1] as a var parameter. Inside the subroutine, you could refer to that parameter, and it would alias the array element form the caller. For example:

procedure ModifyVariant(var p: Variant);
begin
  p[2, 2] := 77;
end;

procedure TMainForm.TestProc(params: TVarArray);
var
  p: PVariant;
begin
  p := @params[1];

  ModifyVariant(params[1]);

  Assert(params[1][2, 2] = p^[2, 2]);
end;

ModifyVariant could even be an anonymous procedure so you can implement within the same scope as the caller:

procedure TMainForm.TestProc(params: TVarArray);
var
  ModifyVariant: reference to procedure(var x: Variant);
  p: PVariant;
begin
  p := @params[1];

  ModifyVariant := procedure(var v: Variant)
  begin
    v[2, 2] := 77;
  end;

  ModifyVariant(params[1]);

  Assert(params[1][2, 2] = p^[2, 2]);
end;

Neither of those looks particularly appealing, though, especially if you're afraid that mere pointer access will "spook" your code's consumers.

You've mentioned that you expect your users to incorporate their own code into the code you're generating. I wouldn't advise that. After all, what are they expected to do after they re-run your code generator? Surely they'll lose whatever customizations they've made. It's better to keep generated code separate, ideally in a separate file. For user customization, you can provide hooks in the form of callback functions that users can implement. That way, for example, a user could provide something analogous to ModifyVariant, and then your generated code can simply call it. You'll have your "Variant references," and you'll have generated code cleanly separated from user code.

Upvotes: 3

Related Questions