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

匆匆过客 提交于 2019-12-29 08:08:08

问题


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.


回答1:


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 :)




回答2:


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++.




回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/5014711/how-can-i-convert-a-javascript-array-to-an-atl-com-array

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!