The mysteries of PSafeArray

I use Delphi 7 for all my Windows projects, nice simple clean, easy maintainable AND Pascal. However if you find yourself lost in COM and automation, all of a sudden strange variable types appear like varArray, OleVariants, U/L bounds and PSafeArray’s and it´s getting worse: their initialization/finalization do not use Object.Free, Dispose() and the like anymore! As far I can understand its all about marshalling; the process of converting variables to COM variables and back..

PSafeArray is defined in ActiveX.pas:

  PSafeArray = ^TSafeArray;
  {$EXTERNALSYM tagSAFEARRAY}
  tagSAFEARRAY = record
    cDims: Word;
    fFeatures: Word;
    cbElements: Longint;
    cLocks: Longint;
    pvData: Pointer;
    rgsabound: array[0..0] of TSafeArrayBound;
  end;
  TSafeArray = tagSAFEARRAY;
  {$EXTERNALSYM SAFEARRAY}
  SAFEARRAY = TSafeArray;

Instantiate and free PSafeArray directly:

var
  Bounds: array[0..0] of TSafeArrayBound;
  SafeArray: PSafeArray;
begin
  Bounds[0].lLbound   := 0;
  Bounds[0].cElements := 100;
  SafeArray := SafeArrayCreate(VT_I1, 1, Bounds);
  SafeArrayDestroy(SafeArray);
end;

Typecast and multidimensional example:

var
  Data: Variant;
  SafeArray: PSafeArray;
begin
  Data      := VarArrayCreate([0, 1, 0, 2], VT_VARIANT);
  Data[0,0] := 1;
  Data[0,1] := 'some text';
  Data[0,2] := Now();
  Data[1,0] := 2;
  Data[1,1] := 'more text';
  Data[1,2] := Now();
  SafeArray := PSafeArray(TVarData(Data).VArray);
end;

To sum it up I´ve created an example inwhich a good ole pascal array is converted into an varArray which in turn is converted to an PSafeArray and back again to plain pascal:

procedure SMA(InData: TArrayOfDouble; Period: Integer; var OutData: TArrayOfDouble);
var
  psaInMatrix, psaOutMatrix: PSafeArray;
  vaInMatrix: Variant;
  LBound, HBound : Integer;

  I: Integer;
  InDataLength: Integer;
  D: Double;
begin
  //create varArray
  InDataLength := Length(InData);
  vaInMatrix := VarArrayCreate([0, InDataLength-1], varDouble);

  //copy data into varArray
  for I := 0 to InDataLength-1 do
    vaInMatrix[I] := InData[I];

  //typecast magic
  psaInMatrix := PSafeArray(TVarData(vaInMatrix).VArray);

  //COM call with a PSafeArray as return value
  psaOutMatrix := COM.SimpleMovingAverage(psaInMatrix, Period);

  //enum PSafeArray
  SafeArrayGetLBound(psaOutMatrix, 1, LBound);
  SafeArrayGetUBound(psaOutMatrix, 1, HBound);

    //copy data
  SetLength(OutData, HBound+1);
  for I := LBound to HBound do
  begin
    SafeArrayGetElement(psaOutMatrix, I, D);
    OutData[I] := D;
  end;

  //cleanup varArray
  VarClear(vaInMatrix);

  //cleanup PSafeArray
  SafeArrayDestroy(psaOutMatrix);
end;

This function can be called in the following way:

type
  TArrayOfDouble = array of Double;
var
  InData, OutData: TArrayOfDouble;
begin
  SetLength(InData, 9);
  InData[0] := 2.48;
  InData[1] := 2.54;
  InData[2] := 2.56;
  InData[3] := 2.48;
  InData[4] := 2.54;
  InData[5] := 2.56;
  InData[6] := 2.48;
  InData[7] := 2.54;
  InData[8] := 2.56;

  SMA(InData, 3, OutData);
end;

Last but not least, to enumerate an varArray use something like this:

for I := VarArrayLowBound(vaInMatrix, 1) to VarArrayHighBound(vaInMatrix, 1) do
  Memo1.Lines.Add(FloatToStr( vaInMatrix[I] ));

FloatToStr is needed here because the varArray was created with varDouble.

Further readings:

July 20th, 2008 - Posted in delphi | |

2 Responses to ' The mysteries of PSafeArray '

Subscribe to comments with RSS or TrackBack to ' The mysteries of PSafeArray '.

  1. evilripper said,

    on November 12th, 2009 at 8:43 pm

    Thank you a lot, you save my brain today!! This pSafeArray maked me crazy!
    :-(

    ps
    Why in delphi there is a unit varUtils where there are the same procedures that there are in ActiveX.pas????? oh my god :-D


  2. on August 6th, 2011 at 8:47 am

    […] a look at this article, which explains how to get data in and out of a PSafeArray in […]

Leave a reply