2

How can I convert a JavaScript array() to an ATL/COM array without using VBArray?

What I want to convert is a new Array() to a SAFEARRAY.

4 Answers 4

9

Here's a code to do just that (considering you already got the JS Array object as a C++ Variant), same way as Sheng Jiang suggested earlier:

bool VariantToArray(__in const CComVariant& var, __out vector<CComVariant>& vecVars)
{
    // convert variant to dispatch object
    CComPtr<IDispatch> pDispatch = VariantToDispatch(var);
    if (!pDispatch)
        return false;

    // invoke the object to retrieve the enumerator containing object
    CComVariant varResult;
    DISPPARAMS dispparamsNoArgs = {0};
    EXCEPINFO excepInfo = {0};
    UINT uiArgErr = (UINT)-1;  // initialize to invalid arg
    HRESULT hr = pDispatch->Invoke( DISPID_NEWENUM,
                                    IID_NULL,
                                    LOCALE_USER_DEFAULT,
                                    DISPATCH_METHOD | DISPATCH_PROPERTYGET,
                                    &dispparamsNoArgs,
                                    &varResult,
                                    &excepInfo,
                                    &uiArgErr);
    if (FAILED(hr))
        return false;

    // query the retrieved interface and get the enumerator object
    CComPtr<IEnumVARIANT> pEnumVariant;
    switch (varResult.vt)
    {
        case VT_UNKNOWN:
        {
            CComPtr<IUnknown> pUnknownResult = varResult.punkVal;
            if (!pUnknownResult)
                return false;
            pEnumVariant = pUnknownResult; // implied query interface
        }
        break;

        case VT_DISPATCH:
        {
            CComPtr<IDispatch> pDispatchResult = varResult.pdispVal;
            if (!pDispatchResult)
                return false;
            pEnumVariant = pDispatchResult; // implied query interface
        }
        break;

        default:
            return false;
    }

    if (!pEnumVariant)
        return false;

    // reset enumerator to beginning of the list
    hr = pEnumVariant->Reset();
    if (FAILED(hr))
        return false;

    // enumerate and fetch items
    CComVariant varItem;
    ULONG uiFetched = 0;
    do 
    {
        // get next item
        hr = pEnumVariant->Next(1, &varItem, &uiFetched);
        if (FAILED(hr))
            return false;

        if (uiFetched == NULL) // last item
            break;

        // insert the item to the vector 
        vecVars.push_back(varItem);
    } while (true);

    return true;
}

hope that helps.

Note:
I saw a post where someone complained this doesn't work on IE9 (but it does on IE6,7,8), I checked it out myself - on IE9 (only) the Invoke method fail and return DISP_E_EXCEPTION.
So i'm still looking for a better solution.

Edited:
Here's a code that will works on all IE browsers:

bool VariantToArray(__in const CComVariant& var, __out vector<CComVariant>& vecVars)
        {
            // convert variant to dispatch object
            CComPtr<IDispatch> pDispatch = VariantToDispatch(var);
            if (!pDispatch)
                return false;

            // get DISPID of length parameter from array object
            LPOLESTR sLengthName = L"length";
            DISPID dispidLength = 0;
            HRESULT hr = pDispatch->GetIDsOfNames(IID_NULL, &sLengthName, 1, LOCALE_USER_DEFAULT, &dispidLength);
            if (FAILED(hr))
                return false;

            // get the number of elements using the DISPID of length parameter
            CComVariant varLength;
            DISPPARAMS dispParams = {0};
            hr = pDispatch->Invoke(dispidLength, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &varLength, NULL, NULL);
            if (FAILED(hr))
                return false;

            int nLength = 0; // length of the array
            bool bGotInt = VariantToInt(varLength, nLength);
            if (!bGotInt)
                return false;

            // get items of array
            for (int i=0 ; i<nLength ; ++i)
            {
                // get DISPID of item[i] from array object
                wstring strIndex = StringUtils::IntToString(i);
                DISPID dispidIndex = 0;
                LPOLESTR pIndex = reinterpret_cast<LPOLESTR>(const_cast<WCHAR *>(strIndex.data()));
                hr = pDispatch->GetIDsOfNames(IID_NULL, &pIndex, 1, LOCALE_USER_DEFAULT, &dispidIndex);
                if (FAILED(hr))
                    continue;

                CComVariant varItem;
                hr = pDispatch->Invoke(dispidIndex, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &varItem, NULL, NULL);
                if (FAILED(hr))
                    continue;

                vecVars.push_back(varItem);
            }

            return true;
        }

Enjoy :)

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot, I was going to give up and use comma-separate string, but this code really works (at least with IE11)! Just wanted to add - the code uses VariantToDispatch and VariantToInt functions which turned out to be very simple. In the former you just check if vt == VT_DISPATCH and if yes, return pdispVal. The latter one is a bit more tricky - you just use something like VariantChangeType(&newvar,&inputvar,0,VT_I4) and get newvar.intVal if it is succesfull. I wish I could insert a code snippet but I don't think that SO allows doing this in comments.
1

With IActiveScript you can instantiate a JavaScript engine in C++ and use it to:

  • Make IDispatch* pointers for JavaScript functions
  • Make VARIANT variables containing JavaScript objects
  • Pass JavaScript objects to JavaScript functions in C++

Using this technique, we shall do the following:

  1. Declare a JavaScript function, e.g. function (arr) { return arr.length; }
  2. Declare a JavaScript array, e.g. [2, 3, 5, 7, 11]
  3. Call the JavaScript function with the JavaScript array as input

To make this work, you must create an IActiveScriptSite. The following is a C++ console application that demonstrates this concept:

// C++ headers for ATL and Active Script Hosting.
#include <atlbase.h>
#include <atlcom.h>
#include <activscp.h>

// A minimal implementation of IActiveScriptSite.
class ATL_NO_VTABLE CScriptSite :
    public CComObjectRootEx<CComSingleThreadModel>,
    public IActiveScriptSite,
    public IActiveScriptSiteWindow
{
public:
BEGIN_COM_MAP(CScriptSite)
    COM_INTERFACE_ENTRY(IActiveScriptSite)
    COM_INTERFACE_ENTRY(IActiveScriptSiteWindow)
END_COM_MAP()
    DECLARE_PROTECT_FINAL_CONSTRUCT()
    HRESULT FinalConstruct()
    {
        return S_OK;
    }
    void FinalRelease()
    {
    }
public:
    // IActiveScriptSite
    STDMETHOD(GetLCID)(LCID* plcid)
    {
        *plcid = 0;
        return S_OK;
    }
    STDMETHOD(GetItemInfo)(
        LPCOLESTR pstrName,
        DWORD dwReturnMask,
        IUnknown** ppiunkItem,
        ITypeInfo** ppti)
    {
        return TYPE_E_ELEMENTNOTFOUND;
    }
    STDMETHOD(GetDocVersionString)(BSTR* pbstrVersion)
    {
        *pbstrVersion = ::SysAllocString(L"1.0");
        return S_OK;
    }
    STDMETHOD(OnScriptTerminate)(
        const VARIANT* pvarResult,
        const EXCEPINFO* pexcepinfo)
    {
        return S_OK;
    }
    STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState)
    {
        return S_OK;
    }
    STDMETHOD(OnScriptError)(IActiveScriptError* pIActiveScriptError)
    {
        return S_OK;
    }
    STDMETHOD(OnEnterScript)(void)
    {
        return S_OK;
    }
    STDMETHOD(OnLeaveScript)(void)
    {
        return S_OK;
    }
    // IActiveScriptSiteWindow
    STDMETHOD(GetWindow)(HWND* phWnd)
    {
        *phWnd = NULL;
        return S_OK;
    }
    STDMETHOD(EnableModeless)(BOOL fEnable)
    {
        return S_OK;
    }
};

// ATL in a Console app.
CComModule _Module;
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()

// Main body
int _tmain(int argc, _TCHAR* argv[])
{
    HRESULT hr = S_OK;
    hr = _Module.Init(ObjectMap, NULL, NULL);

    // Instantiate JavaScript engine.
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    CComObject<CScriptSite>* pScriptSite = NULL;
    hr = CComObject<CScriptSite>::CreateInstance(&pScriptSite);
    pScriptSite->AddRef();
    CComPtr<IActiveScript> spIActiveScript;
    hr = spIActiveScript.CoCreateInstance(OLESTR("JScript"));
    hr = spIActiveScript->SetScriptSite(pScriptSite);
    CComPtr<IActiveScriptParse> spIActiveScriptParse;
    hr = spIActiveScript->QueryInterface(IID_IActiveScriptParse, (void **) &spIActiveScriptParse);
    hr = spIActiveScriptParse->InitNew();
    hr = spIActiveScript->SetScriptState(SCRIPTSTATE_CONNECTED);

    // Evaluate an anonymous JavaScript function.
    CComVariant vSomeFunc;
    EXCEPINFO ei = { };
    hr = spIActiveScriptParse->ParseScriptText(
        OLESTR("(function () { return function (arr) { return arr.length; }; } )();"),  // pstrCode
        NULL,                       // pstrItemName
        NULL,                       // punkContent
        NULL,                       // pstrDelimiter
        0,                          // dwSourceContextCookie
        0,                          // ulStartingLineNumber
        SCRIPTTEXT_ISEXPRESSION,    // dwFlags
        &vSomeFunc,                 // pvarResult
        &ei                         // pexcepinfo
        );

    // Make a JavaScript array object.
    CComVariant vObject;
    hr = spIActiveScriptParse->ParseScriptText(
        OLESTR("[2,3,5,7,11]"), // pstrCode
        NULL,                       // pstrItemName
        NULL,                       // punkContent
        NULL,                       // pstrDelimiter
        0,                          // dwSourceContextCookie
        0,                          // ulStartingLineNumber
        SCRIPTTEXT_ISEXPRESSION,    // dwFlags
        &vObject,                   // pvarResult
        &ei                         // pexcepinfo
        );

    // Call the anonymous JavaScript function (gives answer of 5).
    CComVariant vResult;
    DISPPARAMS dispParams = { &vObject, 0, 1, 0 };
    hr = V_DISPATCH(&vSomeFunc)->Invoke(
        DISPID_VALUE,
        IID_NULL,
        0,
        DISPATCH_METHOD,
        &dispParams,
        &vResult,
        &ei,
        NULL);

    // Release variables.
    hr = vSomeFunc.Clear();
    hr = vObject.Clear();
    hr = vResult.Clear();

    // Release JavaScript engine.
    spIActiveScriptParse = NULL;
    spIActiveScript = NULL;
    pScriptSite->Release();
    pScriptSite = NULL;
    ::CoUninitialize();
    return 0;
}

To answer the original posters question, we then need to create another JavaScript function to extract elements from the array, say, function (arr,idx) { return arr[idx]; }. Now we have enough functions to walk JavaScript arrays in C++.

Comments

0

I have looked into this in the past and as far as I know it is not possible. Your only option from script is to use VBScript and VBArray.

Comments

0

A javascript array is a VARIANT that contains an IDispatch pointer. The IDispatch implementation has an enumerator method at dispatch id DISPID_NEWENUM . if you use C++, you can also query IEnumVARIANT from the object to access its elements.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.