Delphi OleVariant to array of string from COM Library
Asked Answered
K

2

6

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.

Kiri answered 25/5, 2015 at 15:54 Comment(3)
You're assuming that ArrayTicks is an array but perhaps it's not.Warder
Why is ArrayTicks being passed by var?Rior
Thank you for your quick response. This function is a third-party com library. They told me that function returns an array of string (CStringArray to Safearray).Kiri
R
5

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; 
Rior answered 25/5, 2015 at 17:55 Comment(3)
if not VarIsArray(ArrayTicks) then Exit; Data := VarArrayAsPSafeArray(ArrayTicks); Incompatibles types: ‘PsafeArray’ and ‘PVarArray’Kiri
Exception in this line for i := VarArrayLowBound(ArrayTicks,1) to VarArrayHighBound(ArrayTicks,1) do Debugger Fault Notification: Project … Faulted with message: access violation at 0x76cde18c: read of address 0xad33a794. Process Stopped. Use Step or Run to continueKiri
@EFD: I corrected the "incompatible types" error. As for the exception, I have no clue. If VarIsArray() is not crashing, and is returning True, then clearly the OleVariant holds a valid array. This kind of code works fine for me when I try it, so something else is going on with your case.Rior
A
3

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;
Astor answered 27/3, 2017 at 21:47 Comment(2)
The declaration for SafeArrayGetvartype seems to have been removed from Delphi?Flunky
@DaveNottage I don't know if it ever existed in Delphi, but it isn't a Delphi function - it's a Windows API function SafeArrayGetVarType. The code includes the declaration to let you call it (function blah blah blah external 'OleAut32.dll;)Astor

© 2022 - 2024 — McMap. All rights reserved.