Search code examples
c++safearrayscripting.dictionary

How to initialise CComSafeArray which a SAFEARRAY returned from a Scripting.Dictionary Keys method


I'm using a Scripting.Dictionary from the COM Library "Microsoft Scripting Runtime" (c:\windows\System32\scrrun.dll). I'm using import to get my wrappers and these are working. After adding some items, I'm trying to get the list of keys but I am stuck.

I have got some way. I can get the SAFEARRAY of keys out of the Dictionary but I want to use the CComSafeArray class defined in <atlsafe.h> but I cannot figure out a good construction. Currently, the constructor throws an ATL assertion complaining about vartype mismatch. Admittedly, the newly constructed CComSafeArray has a vartype of 52428 of CCCC in hex and this looks potentially like uninitialised memory. But the type inferred doesn't look right either, I am expecting the vartype of the safearray to be strings but the assertion code gives vartype 12 which is UI2. Very strange.

Anyway, this is probably easy for someone familiar with this. Here is a console program Minimal, Complete, Verifiable, Example (MCVE) as per SO standards.

#include "stdafx.h"
#include <atlbase.h>
#include <atlsafe.h>

#import "c:\windows\System32\scrrun.dll" raw_interfaces_only,raw_native_types, named_guids, rename("DeleteFile", "_DeleteFile"), rename("MoveFile","_MoveFile"), rename("CopyFile", "_CopyFile"), rename("GetFreeSpace", "_GetFreeSpace")

int main()
{
    HRESULT hr = S_OK;

    CoInitialize(0);

    CComQIPtr<Scripting::IDictionary> dictColours;
    hr = dictColours.CoCreateInstance(__uuidof(Scripting::Dictionary));
    if (!SUCCEEDED(hr)) { return hr; }

    CComVariant varZero(0); //dummy value, only interested in keys

    hr = dictColours->Add(&variant_t(L"red"), &varZero);
    if (!SUCCEEDED(hr)) { return hr; }

    hr = dictColours->Add(&variant_t(L"green"), &varZero);
    if (!SUCCEEDED(hr)) { return hr; }

    hr = dictColours->Add(&variant_t(L"blue"), &varZero);
    if (!SUCCEEDED(hr)) { return hr; }

    long lColourCount(0);
    hr = dictColours->get_Count(&lColourCount);
    if (!SUCCEEDED(hr)) { return hr; }

    CComVariant varColoursKeys;
    hr = dictColours->Keys(&varColoursKeys);
    if (!SUCCEEDED(hr)) { return hr; }

    SAFEARRAY keys(*(varColoursKeys.parray));

    //fine up to this point

    CComSafeArray<VARIANT> varColours;

    /* The following code throws an error for the next (line) live, vt=52428 or hex CCCC which looks like uninitialised memory
       whilst GetType() returns 12 which is UI2 (and somehow I expected string type 8!) 
    VARTYPE vt;
    HRESULT hRes = ::ATL::AtlSafeArrayGetActualVartype(const_cast<LPSAFEARRAY>(psaSrc), &vt);
    ATLENSURE_SUCCEEDED(hRes);
    ATLENSURE_THROW(vt == GetType(), E_INVALIDARG);
    */

    varColours = (keys);

    CoUninitialize();
    return 0;
}

Solution

  • Change this:

    SAFEARRAY keys(*(varColoursKeys.parray));
    

    To this:

    SAFEARRAY* keys = varColoursKeys.parray;
    

    (optionally, first make sure that varColoursKeys.vt has the VT_ARRAY flag)

    And then you can query keys for its vt:

    hr = ::ATL::AtlSafeArrayGetActualVartype(keys, &vt);
    

    Note that varColours = keys is going to make a copy of the array data. If that is not what you really want, then you should Detach() the array from the CComVariant (or just use VARIANT directly and not CComVariant) and Attach() it to the CComSafeArray (then you can use the CComSafeArray::GetType() method).

    Also, you need to make sure all of your COM wrappers go out of scope and release resources before you call CoUninitialize().

    Try this:

    #include "stdafx.h"
    #include <atlbase.h>
    #include <atlsafe.h>
    
    #import "c:\windows\System32\scrrun.dll" raw_interfaces_only,raw_native_types, named_guids, rename("DeleteFile", "_DeleteFile"), rename("MoveFile","_MoveFile"), rename("CopyFile", "_CopyFile"), rename("GetFreeSpace", "_GetFreeSpace")
    
    HRESULT DoIt()
    {
        CComQIPtr<Scripting::IDictionary> dictColours;
        hr = dictColours.CoCreateInstance(__uuidof(Scripting::Dictionary));
        if (FAILED(hr)) { return hr; }
    
        CComVariant varZero(0); //dummy value, only interested in keys
    
        hr = dictColours->Add(&variant_t(L"red"), &varZero);
        if (FAILED(hr)) { return hr; }
    
        hr = dictColours->Add(&variant_t(L"green"), &varZero);
        if (FAILED(hr)) { return hr; }
    
        hr = dictColours->Add(&variant_t(L"blue"), &varZero);
        if (FAILED(hr)) { return hr; }
    
        long lColourCount(0);
        hr = dictColours->get_Count(&lColourCount);
        if (FAILED(hr)) { return hr; }
    
        VARIANT varColoursKeys;
        hr = dictColours->Keys(&varColoursKeys);
        if (FAILED(hr)) { return hr; }
    
        CComSafeArray<VARIANT> varColours;
        varColours.Attach(varColoursKeys.parray);
    
        // use varColours as needed...
    
        VARTYPE vt = varColours.GetType();
    
        LONG lLower = varColours.GetLowerBound();
        LONG lUpper = varColours.GetUpperBound();
        for (LONG i = lLower; i <= lUpper; ++i)
        {
            VARIANT &v = varColours.GetAt(i);
            // use v.bstrVal as needed ...
        }
    
        //...
    
        return S_OK;
    }
    
    int main()
    {
        HRESULT hr = CoInitialize(0);
        if (FAILED(hr)) { return hr; }
    
        hr = DoIt();
    
        CoUninitialize();
        return hr;
    }