EFD
EFD

Reputation: 61

Delphi OleVariant to array of string from COM Library

I have Delphi 2006 client application. This client recieves an Olevariant type data from COM server. The function is:

procedure OnLimitsChanged(var SymbolsChanged: {??PSafeArray}OleVariant);

This function returns an array of string. I can´t read OleVariant type data from delphi.

From Excel VBA it´s working:

Private Sub g_Realtime_OnLimitsChanged(SymbolsChanged() As String)
    Dim i%
    Dim Salir As Boolean
    If UBound(SymbolsChanged) <> -1 Then
        i = 0: Salir = False
        While Not Salir
            If SymbolsChanged(i) = Simbolo Then
                LlamarALimites
                Salir = True
            Else
                i = i + 1
                If i > UBound(SymbolsChanged) Then Salir = True
            End If
        Wend
    End If
End Sub

I tried to convert OleVariant to Psafearray...

procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject;
  var ArrayTicks : Olevariant);    
var 
  Data : pSafeArray;
  i,iLow, iHigh : Integer;  
  value : wideString;
begin
  Data:=PSafeArray(TVarData(ArrayTicks).VArray);
  SafeArrayGetLBound(Data,1,iLow);
  SafeArrayGetUBound(Data,1,iHigh);
  for i:=iLow to iHigh do
  begin
    SafeArrayGetElement(Data,i,Value);
    Showmessage(Value);
  end; 

But I recieve an except in this line:

 SafeArrayGetLBound(Data,1,iLow);

Debugger Fault Notification
Project ... faulted with message: ' access violation at 0x751de18c: read of address 0xabababab'. Process Stopper. Use Step or Run to continue

Any advice and suggestions will be greatly appreciated.

Upvotes: 6

Views: 7273

Answers (2)

Ian Boyd
Ian Boyd

Reputation: 256581

The RTL has the function let access a Varant array as a SAFEARRAY:

function VarArrayAsPSafeArray(const V: Variant): PSafeArray;

I wanted to document how to do the reverse.

Variant is a structure

In Delphi a Variant is an opaque blob. But internally it is really the TVarData structure (aka the Windows VARIANT structure). A variant can hold different types of data. You indicate which type through the VType member. The value of the VType member tells you how to interpret the rest of the structure:

a 32-bit Integer (VT_I4)

  • Variant
    • VType: Word = VT_I4; //3
    • VInteger: Integer;

a IUnknown interface (VT_UNKNOWN)

  • Variant
    • VType: Word = VT_UNKNOWN; //13
    • VUnknown: Pointer; //actually IUnknown

an BSTR (aka WideString in Delphi)

  • Variant
    • VType: Word = VT_BSTR; //8
    • VOleStr: PWideChar;

In the case that the variant is a SAFEARRAY of 32-bit integers:

  • Variant
    • VType: Word = (VT_ARRAY or VT_I4);
    • VArray: PVarArray;

And then VArray points to a SAFEARRAY strucuture:

  • Variant
    • VType: Word = (VT_ARRAY or VT_I4);
    • VArray: PVarArray;
      • cDims: Word;
      • fFeatures: Word;
      • cbElements: LongWord;
      • cLocks: LongWord;
      • pvData: Pointer;
      • rgsabound: array[0..0] of TSafeArrayBound;

What if we start with a SAFEARRAY

There are times, particularly when interacting with COM or .NET that you:

  • have to supply a PSafeArray,
  • or are given a PSafeArray.

You can construct a SafeArray easily enough, if you use Delphi's functions to create a variant array. Delphi does the heavy lifting to creating the underlying SafeArray that your "variant array" actually is.

But we want to go the other way; we are given a PSafeArray, and we want to wrap it up inside a Delphi Variant variable, so that it handles all the ugliness and lifetime.

assemblies: PSafeArray;

assemblies := DefaultAppDomain.GetAssemblies;

How can we deal with this pointer to a SAFEARRAY?

function PSafeArrayToVariant(psa: PSafeArray): OleVariant;
begin
   TVarData(v).VType = (VT_ARRAY or VT_xxxx); 
   TVarData(v).VArray := PVarArray(psa);
end;

except we need to know what the SafeArray contains; we need to fill in the VT_xxxx in the above code.

Fortunately, one of the members of the SAFEARRAY structure tells what VType the members of the array are:

  • fFeatures: Word;
    • FADF_BSTR: It is an array of BSTRs (VT_BSTR)
    • FADF_UNKNOWN: It is an array of IUnknown (VT_UNKNOWN)
    • FADF_DISPATCH: It is an array of IDispatch (VT_DISPATCH)
    • FADF_VARIANT: It is an array of Variants (VT_VARIANT)
    • FADF_HAVEVARTYPE: You can get the type using SafeArrayGetVartype

Final function

function SafeArrayGetVartype(psa: PSafeArray): TVarType; safecall; external 'OleAut32.dll';

function PSafeArrayToVariant(psa: PSafeArray): OleVariant;
var
    features: Word;
    vt: TVarType;
const
    FADF_HAVEVARTYPE = $80;
begin
    features := psa^.fFeatures;
    if (features and FADF_UNKNOWN) = FADF_UNKNOWN then
        vt := VT_UNKNOWN
    else if (features and FADF_DISPATCH) = FADF_DISPATCH then
        vt := VT_DISPATCH
    else if (features and FADF_VARIANT) = FADF_VARIANT then
        vt := VT_VARIANT
    else if (features and FADF_BSTR) <> 0 then
        vt := VT_BSTR
    else if (features and FADF_HAVEVARTYPE) <> 0 then
        vt := SafeArrayGetVartype(psa)
    else
        vt := VT_UI4; //assume 4 bytes of *something*

    TVarData(Result).VType := VT_ARRAY or vt;
    TVarData(Result).VArray := PVarArray(psa);
end;

Upvotes: 3

Remy Lebeau
Remy Lebeau

Reputation: 595295

The RTL has a VarArrayAsPSafeArray() function for extracting a PSafeArray correctly from an (Ole)Variant:

procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject; var ArrayTicks : OleVariant);    
var 
  Data : PVarArray; // RTL's version of PSafeArray
  //...
begin
  Data := VarArrayAsPSafeArray(ArrayTicks);
  //...
end; 

If the (Ole)Variant does not contain an array, an exception will be raised. Or you can use VarIsArray() to check it manually:

procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject; var ArrayTicks : OleVariant);    
var 
  Data : PVarArray;
  //...
begin
  if not VarIsArray(ArrayTicks) then Exit;
  Data := VarArrayAsPSafeArray(ArrayTicks);
  //...
end; 

That being said, (Ole)Variant has build-in support for accessing PSafeArray element data, so you don't really need to resort to accessing PSafeArray directly (unless you want an extra performance boost, in which case you need to validate the PSafeArray yourself before you access its data):

procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject; var ArrayTicks : Olevariant);    
var 
  i : Integer;  
  value : String;
begin
  if not VarIsArray(ArrayTicks) then Exit;
  for i := VarArrayLowBound(ArrayTicks, 1) to VarArrayHighBound(ArrayTicks, 1) do
  begin
    Value := ArrayTicks[i];
    ShowMessage(Value);
  end; 

Upvotes: 5

Related Questions