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:
- Passing PSafeArray to COM server by Serge tkint
- Evaluation of marshalling by Peter Petersen and Kåre Kjelstrøm
- MSDN about Win32 and COM development
Array Manipulation Functions
July 20th, 2008 - Posted in delphi | | 1 Comments