How to display values from a VARIANT with a SAFEARRAY of BSTRs
Asked Answered
E

3

8

I am working on a COM Object library with function that returns a VARIANT with a SAFEARRAY of BSTRs. How can I display the values from this VARIANT instance and save it inside a TStringList? I tried searching the net with no clear answer.

I tried the following with no success:

Variant V;
String mystr;

VarClear(V);
TVarData(V).VType = varOleStr;
V = ComFunction->GetValues();  //<<<<----- V is empty
mystr = (wchar_t *)(TVarData(V).VString);
Memo1->Lines->Add(mystr);
VarClear(V);
Easeful answered 19/12, 2011 at 7:33 Comment(0)
M
4

You can use TWideStringDynArray and let Delphi do the conversion:

procedure LoadStringsFromVariant(const Values: TWideStringDynArray; Strings: TStrings);
var
  I: Integer;
begin
  Strings.BeginUpdate;
  try
    for I := Low(Values) to High(Values) do
      Strings.Add(Values[I]);
  finally
    Strings.EndUpdate;
  end;
end;

When you call this with your Variant safearray of BSTRs it will be converted to TWideStringDynArray automatically. An incompatible Variant will cause the runtime error EVariantInvalidArgError.

To check if a Variant holds a safe array of BSTR you can do this:

IsOK := VarIsArray(V) and (VarArrayDimCount(V) = 1) and (VarType(V) and varTypeMask = varOleStr);
Mestas answered 19/12, 2011 at 8:27 Comment(0)
M
4
uses ActiveX;

var
  VSafeArray: PSafeArray;
  LBound, UBound, I: LongInt;
  W: WideString;
begin
  VSafeArray := ComFunction.GetValues();
  SafeArrayGetLBound(VSafeArray, 1, LBound);
  SafeArrayGetUBound(VSafeArray, 1, UBound);
  for I := LBound to UBound do
  begin
    SafeArrayGetElement(VSafeArray, I, W);
    Memo1.Lines.Add(W);
  end;
  SafeArrayDestroy(VSafeArray); // cleanup PSafeArray

if you are creating ComFunction via late binding (CreateOleObject) you should use:

var
  v: Variant;
v := ComFunction.GetValues;
for i := VarArrayLowBound(v, 1) to VarArrayHighBound(v, 1) do
begin 
  W := VarArrayGet(v, [i]);
  Memo1.Lines.Add (W);
end;
Musky answered 19/12, 2011 at 11:1 Comment(0)
H
2

How can I display the values from this VARIANT instance and save it inside a TStringList?

The COM VARIANT struct has parray and pparray data members that are pointers to a SAFEARRAY, eg:

VARIANT V;
LPSAFEARRAY sa = V_ISBYREF(&V) ? V_ARRAYREF(&V) : V_ARRAY(&V);

The VCL Variant class, on the other hand, has an LPSAFEARRAY conversion operator defined, so you can assign it directly (but only if the Variant.VType field that not have the varByRef flag present, that is), eg:

Variant V;
LPSAFEARRAY sa = V;

Either way, once you have the SAFEARRAY pointer, use the SafeArray API to access the BSTR values, eg:

bool __fastcall VariantToStrings(const Variant &V, TStrings *List)
{
    // make sure the Variant is holding an array
    if (!V_ISARRAY(&V)) return false;

    // get the array pointer
    LPSAFEARRAY sa = V_ISBYREF(&V) ? V_ARRAYREF(&V) : V_ARRAY(&V);

    // make sure the array is holding BSTR values
    VARTYPE vt;
    if (FAILED(SafeArrayGetVartype(sa, &vt))) return false;
    if (vt != VT_BSTR) return false;

    // make sure the array has only 1 dimension
    if (SafeArrayGetDim(sa) != 1) return false;

    // get the bounds of the array's sole dimension
    LONG lBound = -1, uBound = -1;
    if (FAILED(SafeArrayGetLBound(sa, 0, &lBound))) return false;
    if (FAILED(SafeArrayGetUBound(sa, 0, &uBound))) return false;

    if ((lBound > -1) && (uBound > -1))
    {
        // access the raw data of the array
        BSTR *values = NULL;
        if (FAILED(SafeArrayAccessData(sa, (void**)&values))) return false;
        try
        {
            List->BeginUpdate();
            try
            {
                // loop through the array adding the elements to the list
                for (LONG idx = lBound; l <= uBound; ++idx)
                {
                    String s;
                    if (values[idx] != NULL)
                        s = String(values[idx], SysStringLen(values[idx]));
                    List->Add(s);
                }
            }
            __finally
            {
                List->EndUpdate();
            }
        }
        __finally
        {
            // unaccess the raw data of the array
            SafeArrayUnaccessData(sa);
        }
    }

    return true;
}

VarClear(V); TVarData(V).VType = varOleStr;

You don't need those at all. The VCL Variant class initializes itself to a blank state, and there is no need to assign the VType since you are assigning a new value to the entire Variant immediately afterwards.

V = ComFunction->GetValues(); //<<<<----- V is empty

If V is empty, then GetValues() is returning an empty Variant to begin with.

mystr = (wchar_t *)(TVarData(V).VString);

TVarData::VString is an AnsiString& reference, not a wchar_t* pointer. To convert a VCL Variant (not a COM VARIANT) to a String, just assign it as-is and let the RTL works out the detail for you:

String mystr = V;
Haemophiliac answered 20/12, 2011 at 9:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.