6
votes

Is it possible to return an array of defined interface objects from a C++ COM function (VC6) to a VB6 client? I've scoured the web and haven't been able to come across anything that describes what I need to do. I've seen a lot of passing BSTR and VARIANT types, but I need some way to actually have the client side utilise the interface type that I return inside the array.

What I assume I'll need to do
- Use a SAFEARRAY
- Use the SAFEARRAY with the VT_UNKNOWN type, which in turns means I need to place the objects into the array as IUnknown objects.

From here on in I'm stumped. Is it possible to interpret an IUnknown type in VB6, and somehow turn it into the type that I require? Or am I going about this in the complete wrong way...

Clarification:
The interfaces being placed in the collection are being used to mimic a struct. I essentially need to pass back an array of structs.

3

3 Answers

3
votes

I've come up with a solution that is suitable for my purposes, despite not being exactly what I set out in the question.

My solution was to create a COM function that takes a SAFEARRAY as a parameter and modifies it, instead of returning a created array. The VB6 client instantiates the array, and passes it to C++ for populating. I envision that future usage will include a precursor function which VB6 calls to determine the required size of the array. For reference, here's the code snippets:

Interface function:

[id(4), helpstring("method PopulateWithStruct")] HRESULT PopulateWithStruct([in,out]SAFEARRAY (IReturnStruct*)*ppArray, [out,retval] long*plResult);

Where IReturnStruct is an interface containing property values, acting as a struct:

interface IReturnStruct : IDispatch
{
    [propget, id(1), helpstring("property num1")] HRESULT num1([out, retval] long *pVal);
    [propget, id(2), helpstring("property str1")] HRESULT str1([out, retval] BSTR *pVal);
};

And is implemented by ReturnStruct

[
    uuid(843870D0-E3B3-4123-82B4-74DE514C33C9),
    helpstring("ReturnStruct Class")
]
coclass ReturnStruct
{
    [default] interface IReturnStruct;
};

PopulateWithStruct has the following definition:

STDMETHODIMP CCTestInterface::PopulateWithStruct(SAFEARRAY **ppArray, long *plResult)
{
    long lLowerBound = -1;
    long lUpperBound = -1;
    SafeArrayGetLBound(*ppArray, 1, &lLowerBound);
    SafeArrayGetUBound(*ppArray, 1, &lUpperBound);

    long lArraySize = lUpperBound - lLowerBound;

    VARTYPE type;
    SafeArrayGetVartype(*ppArray, &type);

    if (lArraySize > 0)
    {
        for ( int i = lLowerBound; i < lUpperBound; ++i)
        {
            CComPtr<CReturnStruct> pRetStruct;
            HRESULT hr = CoCreateInstance(__uuidof(ReturnStruct), NULL, CLSCTX_ALL, __uuidof(IUnknown), reinterpret_cast<void **>(&pRetStruct));
            if (SUCCEEDED(hr))
            {
                pRetStruct->Initialise();
                hr = SafeArrayPutElement(*ppArray, (long*)&i, pRetStruct);
                if (FAILED(hr))
                {
                    return hr;
                }
                pRetStruct.Release();
            }
        }
        SafeArrayUnaccessData(*ppArray);
    }

    *plResult = 1;

    return S_OK;
}

On the VB side:

Dim obj As ATL_SERVICETESTLib.CTestInterface
Set obj = New CTestInterface

Dim Result As Long
Dim RetStructs(3) As ReturnStruct

Result = obj.PopulateWithStruct(RetStructs())

Any comments on this approach?

2
votes

VB will do a QueryInterface behind the scenes when you assign the IUnknown to a particular interface type, so that should Just Work.

I don't know if you can pass an array of user-defined type to VB6, all the documentaiton I can find on the web stops at VS2003, but I would expect it would be possible.

1
votes

You can wrap the thing in a variant and then it works.

idl:

[propget, id(10), helpstring("blabla")]
HRESULT MyListProp([out, retval] VARIANT *ppsaList); 

cpp:

STDMETHODIMP CAnyClass::get_MyListProp(/*[out, retval]*/ VARIANT* ppsaList)
{
HRESULT hr = S_OK;

if (ppsaList== NULL)
{
    return E_INVALIDARG;
}

CComSafeArray <IDispatch*> saVars;

// I have my objects in a list m_List that I am copying to saVars
for (std::list<IMyObj*>::iterator it = m_List.begin();
     it != m_List.end();
     ++it)
{
    IDispatch* pUnk = NULL;
    if ((*it)->QueryInterface(IID_IDispatch, (void**)&pUnk) == S_OK)
    {
        saVars.Add(pUnk);
    }
}

CComVariant varReturn (saVars.Detach());
varReturn.Detach(ppsaList);
return S_OK;
} 

vb:

Dim arr
arr = obj.MyListProp

' these will all work
ub = UBound(arr)
lb = LBound(arr)