Q:

Sending and receiving arrays over COM

What is the right way to receive and send arrays over COM?  Here's my attempt so far: a safearray of doubles wrapped in a variant.
//takes variant holding safearray of doubles
//returns a similar variant having multipled every element by 2
STDMETHODIMP MyComClass::safearraytimestwo(VARIANT in, VARIANT* out)
{
    CComSafeArray<double> sa_in;
    sa_in.Attach(*in.pparray);
    ULONG size = sa_in.GetCount();

    CComSafeArray<double> *out_sa = new CComSafeArray<double>(size);

    for (long i=0;i<size;i++)
        out_sa->SetAt(i,sa_in[i]*2);

    out = new CComVariant(out_sa);
    return S_OK;
}

Problems:
 - currently compilation fails on the loop operation: error C2679: binary '=' : no operator found which takes a right-hand operand of type 'ATL::_ATL_AutomationType<DOUBLE>::_typewrapper' (or there is no acceptable conversion) edit: solved using SetAt() instead of operator[]
 - Should I be declaring out_sa on the heap?  Will it get deallocated when out gets deallocated (which I can only presume the client will do?)
Any help would be greatly appreciated!
Edit 2: here is a partial implementation that tries just to return a safearray.
STDMETHODIMP CSpatialNet::array3(VARIANT in, VARIANT* out)
{
    CComSafeArray<double> out_sa;
    out_sa.Create(2);
    out_sa.SetAt(0,1.2);
    out_sa.SetAt(1,3.4);
    *out = CComVariant(out_sa);
    out_sa.Detach();
    return S_OK;
}

This also fails; lisp reports 
(vl-load-com)
(setq n (vlax-create-object "sdnacomwrapper.SpatialNet"))
(setq v (vlax-make-variant 1.0))
(vlax-invoke-method n 'array3 v 'newvar)
; error: ActiveX Server returned an error: The parameter is incorrect

Replacing CComSafeArray<double> with an array of variants produces the same error.

A:

Got this working - my code is this (edit: though apparently not without faults - see Dietrich's answer):
STDMETHODIMP MyComClass::arraytimestwo(VARIANT in, VARIANT* out)
{
    CComSafeArray<double> sa_in;
    sa_in.Attach(in.parray);
    ULONG size = sa_in.GetCount();
    CComSafeArray<double> out_sa;
    out_sa.Create(size);
    for (long i=0;i<size;i++)
        out_sa.SetAt(i,sa_in.GetAt(i)*2);

    CComVariant(out_sa).Detach(out);
    return S_OK;
}

And in Lisp...
(vl-load-com)
(setq n (vlax-create-object "mycomdll.MyComClass"))
(setq sa (vlax-make-safearray vlax-vbDouble '(0 . 1)))
(vlax-safearray-fill sa '(1 2))
(vlax-safearray->list sa)
(vlax-invoke-method n 'arraytimestwo sa 'newvar)
(vlax-safearray->list newvar)

Things specifically wrong with the original attempts:

needed to use Detach method to assign value to out
needed to attach to in.parray not *in.pparray (not the same thing)

A:

The solutions of Sideshow Bob and Roman R. use
ComVariant(out_sa).Detach(out);

This has a serious drawback. The  SAFEARRAY out_sa is passed to the CComVariant's constructor and the constructor will make a copy of the SAFEARRAY. To avoid a copy better use
::VariantInit(out);
out->vt = (VT_ARRAY | VT_R8);
out->parray = out_sa.Detach();

As Roman pointed out, you should also start with checking whether in really is of type VT_ARRAY | VT_R8. Bob's solution has imho a serious fault: in.parray is attached to sa_in but not detached and thus the destructor will destroy in.parray. But by the rules of COM, the function arraytimestwo(VARIANT in,...is not allowed to modify the argument in. COM is full of traps. Therefore, I think it's better to pass the parameter in by reference.
I give a (hopefully!) improved solution and a test function:
STDMETHODIMP arraytimestwo(const VARIANT &in, VARIANT* out)
{
  try
  {
    if (in.vt != (VT_ARRAY | VT_R8)) return E_INVALIDARG;
    CComSafeArray<double> sa_out;
    variant_t wrapCopyIn(in);
    sa_out.Attach(wrapCopyIn.parray);
    if (sa_out.GetDimensions() > 1) return E_INVALIDARG;

    for (long i = sa_out.GetLowerBound(0); i <= sa_out.GetUpperBound(0); i++)
       sa_out[i] *= 2;
    //Don't forget
    sa_out.Detach();

    *out = wrapCopyIn.Detach();
  }
  catch (const CAtlException& e)
  {
    // Exception object implicitly converted to HRESULT,
    // and returned as an error code to the caller
    return e;
  }
  return S_OK;
}

void TestArraytimestwo()
{
  CComSafeArray<double> vec(5, 1);
  for (int i = vec.GetLowerBound(); i <= vec.GetUpperBound(); i++) vec[i] = i * 1.1;

  variant_t in, out;
  in.vt = (VT_ARRAY | VT_R8);
  in.parray = vec.Detach();

  if (!SUCCEEDED(arraytimestwo(in, &out)))
  {
   std::cout << "Something went wrong!" << "\n";
   return;
  }

  CComSafeArray<double> sa_out;
  sa_out.Attach(out.parray);
  vec.Attach(in.parray);
  for (int i = vec.GetLowerBound(); i <= vec.GetUpperBound(); i++)
     std::cout << vec[i] << "  " << sa_out[i] << std::endl;

  //Not necessary, but I do it out of habit
  vec.Detach();
  sa_out.Detach();
}

Remark: Bob's original function should be like this (skipping try ... catch)
STDMETHODIMP arraytimestwoBob(const VARIANT &in, VARIANT* out)
{
  CComSafeArray<double> sa_in;
  sa_in.Attach(in.parray);
  CComSafeArray<double> out_sa(sa_in.GetCount(), sa_in.GetLowerBound());
  for (long i = sa_in.GetLowerBound(); i <= sa_in.GetUpperBound(); i++) out_sa[i] = 2 * sa_in[i];
  sa_in.Detach();//Detach, this function doesn't own in 
  ::VariantInit(out);
  out->vt = (VT_ARRAY | VT_R8);
  out->parray = out_sa.Detach();
  return S_OK;
}

