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

前端 未结 4 463
情深已故
情深已故 2021-01-14 23:33

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条回答
  • 2021-01-15 00:08

    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.

    0 讨论(0)
  • 2021-01-15 00:13

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

    0 讨论(0)
  • 2021-01-15 00:31

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

    0 讨论(0)
  • 2021-01-15 00:31

    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.

    0 讨论(0)
提交回复
热议问题