JScript配列をCOM(C++)で参照する

ちょっとした経緯で,JScriptから呼び出されるCOMをC++で実装することになりました。

そこでJScriptからCOMに対して,パラメータとして配列を渡す必要があったのですが, JScriptで扱う配列は,COMで通常扱うSafeArray配列ではないということをことを知りました。 Webでいろいろ調べてみると, "length"と配列インデックス("0","1","2",...)をメソッドとして持つIDispatchインターフェースとして渡されているようです。

このインターフェース(JScript配列)をCOM内部で取り扱う形式に変換する処理を作成してみましたので,ここで紹介します。 なお,今回扱っているのは旧版のJScriptで,.Net 言語になった新しいもの(JScript.Net)には当てはまりませんので, その点了承下さい。

最初にIDispatchインターフェースを介して"name"で指定したプロパティ値を取得するメソッドを用意します。

CComVariant JsArray::GetIDispatchProperty(IDispatch* iDisp, 
                                          const char* name) {
  HRESULT hr;

  DISPID dispid = 0;
  OLECHAR FAR* szMember = CA2W(name);
  hr = iDisp->GetIDsOfNames(IID_NULL, &szMember, 1,
                            LOCALE_SYSTEM_DEFAULT, &dispid);
  if(FAILED(hr)) {
    throw exception("IDispatch::GetIDsOfNames");
  }

  DISPPARAMS params = { NULL, NULL, 0, 0 };
  VARIANT  var;
  EXCEPINFO excepinfo;
  UINT nArgErr;
  hr = iDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, 
                     DISPATCH_PROPERTYGET, ¶ms, &var,
                     &excepinfo, &nArgErr);
  if(FAILED(hr)) {
    throw exception("IDispatch::Invoke");
  }

  return CComVariant(var);
}

このメソッドを使って,JScript配列の要素数を取得します。 要素数は,プロパティ"length"から取り出せる整数値(VT_I4)です。

int JsArray::GetJScriptArrayLength(IDispatch* iDispatch) {
  CComVariant length = GetIDispatchProperty(iDispatch, "length");
  HRESULT hr = length.ChangeType(VT_I4);
  if(FAILED(hr)) {
    throw exception("CComVariant::ChangeType(VT_I4)");
  }
  return length.intVal;
}

要素数を参照して,配列の内容を取得するメソッドを作成します。 配列値はVariantで格納されているので,CComVariant配列(Vector)として取得しています。

vector JsArray::GetJScriptArrayValues(IDispatch* iDispatch) {
  int length = GetJScriptArrayLength(iDispatch);
  std::vector result(length);
  for(int n = 0; n < length; ++n) {
    char str[10];
    ::sprintf(str, "%d", n);
    result[n] = GetIDispatchProperty(iDispatch, str);
  }
  return result;
}

後は取得したVariant配列の各要素を,所定の型の値として参照すればOKです。