title: Create MultiDimensional javascript array from C++
body: <p>Edit: I have figured out the problem.  There were several bugs in my originally posted code that came from my (very) poor understanding of COM, most notably not understanding what the calls to GetDispID were doing.  I'll update the post with the correctly working code, in case anyone else runs into this issue.</p>
  
  <hr>
  
  <p>I have a C++ application (MFC) with an embedded web browser.  We are calling C++ functions from javascript using:</p>
  
  <pre><code>var arrResult = window.external.MyFunction (myArgs);
  </code></pre>
  
  <p>It works well.  We send data back as an array using the code from CodeProject article <a href="http://www.codeproject.com/Articles/88753/Creating-JavaScript-arrays-and-other-objects-from" rel="nofollow">http://www.codeproject.com/Articles/88753/Creating-JavaScript-arrays-and-other-objects-from</a></p>
  
  <p>Now we need to send back a 2-dimensional array.  This code works for me, now.  Note that, in javascript, my 2 dimensional array really comes as an Object, not an array.  So, isArray returns false:</p>
  
  <pre><code>oMyResult = window.external.VPQuery (strQuery);
  if (Array.isArray (oMyResult))
      alert ("This is an array!");
  </code></pre>
  
  <p>But this will be successful:</p>
  
  <pre><code>if( (typeof arrVPResult === "object") &amp;&amp; (arrVPResult !== null) )
      alert ("This is an object!");
  </code></pre>
  
  <p>So I can iterate through it like this:</p>
  
  <pre><code>    for (var strProp in arrVPResult) {
          if (arrVPResult.hasOwnProperty (strProp)) {
                //  do something...
          }
      }
  </code></pre>
  
  <p>Edit #2:  If the call to theObject->GetDispID uses a numeric value rather than a text value (where CComBSTR(rarrData[n]) is way down below) then the created object will all act like an array in javascript (and have a .length value).  If you use a text value, it will act like an object (and will not have a .length value).</p>
  
  <p>Anyway, here's my C++ code.  I hope it's useful for someone.</p>
  
  <pre><code>bool CMyHtmlDispatch::BuildJsArrayTest  (CStringArray&amp; rarrData, VARIANT&amp; vtResult)
  {
      CComQIPtr&lt;IDispatchEx&gt;  theObject;
      DISPID                  didMainArray    = 0;
      DISPID                  didCurSubArray  = 0;
      DISPID                  didTemp         = 0;
  
      //
      //  First create our main array.  The elements in this array will be 
      //  arrays, too, giving us our 2 dimensional array.  We'll operate on
      //  this array using theObject.  As far as I can tell, we don't need didMainArray (the dispatch ID)
  
      if (! BuildJsArray_CreateObject (vtResult, theObject, didMainArray))
          return false;
  
      if (NULL == theObject)
          return false;
  
      //
      // Now create our sub-arrays.  Each of them will contain an array of strings.
  
      CString strIndex;
      for(size_t n = 0; n &lt; rarrData.GetCount(); n++)
      {
          CComQIPtr&lt;IDispatchEx&gt;  theObjectSubArray;
          _variant_t              vtSubArray;
  
          //
          //  Just like above, this creates our javascript array object.  We'll refer to it
          //  using theObjectSubArray.  We won't need to use didCurSubArray (the dispatch ID)
  
          if (! BuildJsArray_CreateObject (vtSubArray, theObjectSubArray, didCurSubArray))
              return false;
  
          //
          //  Add our data to the sub-array, then we'll add the sub-array to the main array
          //  In our case, we'll just create some test data.  We'll use the value in rarrData 
          //  as the Key name and fill the sub-array values with variations on the key name.
  
          for (int x = 0; x &lt; 3; x ++) {
              //
              //  First create the key for our index in our sub array.  We'll just use the index
              //  number as the key, though we have to pass it to GetDispID as a string.
              //  fdexNameEnsure makes theObjectSubArray create it for us.  It sends the dispatch ID 
              //  back that we'll use below to set the value for our key in InvokeEx
  
              strIndex.Format (_T("%d"), x);
              if (FAILED (theObjectSubArray-&gt;GetDispID (CComBSTR(strIndex), fdexNameEnsure, &amp;didTemp)))
                  break;
  
              //
              //  Now add our sub-item value string to the sub-array object.   We'll call
              //  InvokeEx on theSubObjectArray and need to pass in didTemp so it knows which
              //  key we're setting the value for.  
  
              CString s; s.Format (_T("%s - %d"), rarrData[n], x);
              CComVariant vArg (s);
  
              DISPID      namedArgs[] = {DISPID_PROPERTYPUT};
              DISPPARAMS  p           = {&amp;vArg, namedArgs, 1, 1};
  
              if (FAILED (theObjectSubArray-&gt;InvokeEx(didTemp, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &amp;p, NULL, NULL, NULL)))
                  return false;
          }
  
          //
          //  OK!  Now, in theory, all the data for rarrData[n] is in didCurSubArray.  Let's
          //  add that to the main array.  Remember that GetDispID returns a dispatch ID that
          //  we'll use in our call to InvokeEx.
  
          if (FAILED (theObject-&gt;GetDispID (CComBSTR(rarrData[n]), fdexNameEnsure, &amp;didTemp)))
              break;
  
          CComVariant vArg (vtSubArray);
  
          DISPID      namedArgs[] = {DISPID_PROPERTYPUT};
          DISPPARAMS  p           = {&amp;vArg, namedArgs, 1, 1};
  
          //
          //  Because our vArg really contains an IDispatch, we have to use DISPATCH_PROPERTYPUTREF 
          //  rather than DISPATCH_PROPERTYPUT.  So here we're associating that sub array with the
          //  key whose dispatch ID is didTemp.
  
          if (FAILED (theObject-&gt;InvokeEx (didTemp, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUTREF, &amp;p, NULL, NULL, NULL))) // DISPATCH_PROPERTYPUT
              return false;
  
      } // end loop through main data
  
      return true;
  } // end build js array int
  
  
  
  
  
  //////////////////////////////////////////////////////////////////////////////
  //
  //  This does the grunt work
  //
  //  This function turns our MFC array into a native Javascript array.  
  //  Javascript can't handle SAFEARRAYS directly.  It _can_ handle them 
  //  by using the javascript object VBArray, like this:
  //  var arrResult = new VBArray(window.external.CB_CustomFunctionWithParams (oRed, nPi)).toArray()
  //
  //  But by doing the BuildJsArray stuff it lets the javascript side
  //  treat it just like a native javascript array.
  //
  //  I got this code from:
  //  http://www.codeproject.com/Articles/88753/Creating-JavaScript-arrays-and-other-objects-from
  //
  bool CMyHtmlDispatch::BuildJsArray_CreateObject (VARIANT&amp; vtResult, CComQIPtr&lt;IDispatchEx&gt;&amp; rpObject, DISPID&amp; did)
  {
      if (NULL == m_pHtmlView)
          return false;
  
      CWebDocument2Ptr    pWebDocument2 = m_pHtmlView-&gt;GetWebDocument2 ();
      if (NULL == pWebDocument2)
          return false;
  
      CComQIPtr&lt;IDispatch&gt; scriptDispatch;
      pWebDocument2-&gt;get_Script (&amp;scriptDispatch);
  
      if (!scriptDispatch)
          return false;
  
      //
      // get DISPID for "Array"
  
      did = 0;
      LPOLESTR lpNames[] = {L"Array"};
      HRESULT hr = scriptDispatch-&gt;GetIDsOfNames(IID_NULL, lpNames, 1, LOCALE_USER_DEFAULT, &amp;did);
      if (FAILED(hr))
          return false;
  
      //
      // invoke scriptdispatch with DISPATCH_PROPERTYGET for "Array"
  
      CComVariant vtRet;
      DISPPARAMS params = {0};
      hr = scriptDispatch-&gt;Invoke(did, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &amp;params, &amp;vtResult, NULL, NULL);
      if (FAILED(hr))
          return false;
  
      //
      // check result: should be a VT_DISPATCH
  
      if ((VT_DISPATCH != vtResult.vt) || (NULL == vtResult.pdispVal))
          return false;
  
      //
      // get IDispatchEx on returned IDispatch
  
      CComQIPtr&lt;IDispatchEx&gt; prototype(vtResult.pdispVal);
      if (!prototype)
          return false;
  
      //
      // call InvokeEx with DISPID_VALUE
      // and DISPATCH_CONSTRUCT to construct new array
  
      VariantClear (&amp;vtResult);
      hr = prototype-&gt;InvokeEx(DISPID_VALUE, LOCALE_USER_DEFAULT, DISPATCH_CONSTRUCT, &amp;params, &amp;vtResult, NULL, NULL);
      if (FAILED(hr))
          return false;
  
      //
      // vtresult should contain the new array now.
  
      if ((VT_DISPATCH != vtResult.vt) || (NULL == vtResult.pdispVal))
          return false;
  
      //
      // get IDispatchEx on returned IDispatch
  
      CComQIPtr&lt;IDispatchEx&gt; theObject(vtResult.pdispVal);
      if (!theObject)
          return false;
  
      rpObject = theObject;
      return true;
  }
  </code></pre>