0

I created a ATL/COM class, with a method (IDL) :

[id(4)] HRESULT MULT3(double lCoeff, [in, out] VARIANT* pVarInOut);

It takes pVarInOut, checks that it is an array whose coefficients are real numbers, and updates all its coefficients by multiplying them all by lCoeff.

1) When I call this method from VBA for excel in the following fashion :

Dim OneArrayIn As Variant
OneArrayIn = ws.Range("OneArrayIn")
Call Obj.MULT3(2#,OneArrayIn)
ws.Range("OneArrayOut").Offset(0, 0).Value2 = OneArrayOut

everything is alright : OneArrayIn is updated indeed, with the expected values in it.

2) When I call this method from c# in the following fashion :

MyVARIANT_TESTLib.TheATLObject TheATLObj = new MyVARIANT_TESTLib.TheATLObject();
double TheDouble = 2.0;
object[,] TheMatrix = { { -3.0, 3.0, 2.0, 1.0 } };
TheATLObj.MULT(TheDouble, TheMatrix);

there is a problem : TheMatrix doesn't change at all, as if TheMatrix was not passed by "reference" as it expected to be... even it MULT3 is declared as [in, out] in the IDL, with a pointer to VARIANT*.

For sake of completeness I give the code of MULT3 here :

STDMETHODIMP CTheATLObject::MULT3(double lCoeff, /*[in, out]*/ VARIANT* pVarInOut)
{
    THEVARIANT pMyVarInOut = THEVARIANT(pVarInOut);
    long lNbLines = pMyVarInOut.GETNBLINES();
    long lNbColumns = pMyVarInOut.GETNBCOLS();
    ATL::CComVariant vTMPInOut;
    try
    {
        for (long i = 0; i < lNbLines; ++i)
        {
            for (long j = 0; j < lNbColumns; ++j)
            {
                pMyVarInOut.GET(i, j, vTMPInOut);
                if (vTMPInOut.vt != VT_R8)
                {
                    return E_FAIL;
                }
                else
                {
                    vTMPInOut.dblVal = vTMPInOut.dblVal * lCoeff;
                    pMyVarInOut.FILL(i, j, vTMPInOut, vTMPInOut.vt);
                }
            }
        }
        pMyVarInOut.ATTACH(pVarInOut);
        return S_OK;
    }
    catch (...) // for now
    {

    }
    return S_OK;
}

This is based on the VARIANT wrapper class THEVARIANT, defined as follows in the header file (note that I have updated the FILL method to make it take a reference to a ATL::CComVariant, as this ATL::CComVariant was passed by value before) :

#pragma once
#include <atlcomcli.h>
#include "atlsafe.h"
#include <vector>


class THEVARIANT
{
    private:

        ATL::CComVariant vTMP;
        ATL::CComSafeArray<VARIANT> *pSA;
        ATL::CComSafeArrayBound plDIM[2];
        long plINDEX[2];
        bool bIS_INIT;
        long lNBLINES;
        long lNBCOLUMNS;

    public:
        THEVARIANT(void);
        THEVARIANT(VARIANT * pVARIANT_IN);
        THEVARIANT(double * pdARRAY_IN, long NBLINES, long NBCOLUMNS);
        THEVARIANT(std::vector<double>& pdARRAY_IN, long NBLINES, long NBCOLUMNS);
        THEVARIANT(std::vector<int>& pdARRAY_IN, long NBLINES, long NBCOLUMNS);
        THEVARIANT(const THEVARIANT& MyV_COPY);
        ~THEVARIANT(void);
        void SET_LDIMENSIONS(long NBLINES, long NBCOLUMNS);
        void FILL(long lLINE, long lCOL, ATL::CComVariant & VARIANT_IN, VARTYPE VARIANT_TYPE);
        void ATTACH(VARIANT * pVARIANT_OUT);
        void GET(long lLINE, long lCOL, ATL::CComVariant& VARIANT_OUT);
        long GETNBCOLS( void ) const;
        long GETNBLINES( void ) const;
};

and implemented as follows in the cpp file :

#include "StdAfx.h"
#include "THEVARIANT.h"
#include <comutil.h>
#include <atlconv.h>
#include <comdef.h>

#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>

#ifdef _DEBUG
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__)
#define new DEBUG_NEW
#endif

THEVARIANT::THEVARIANT(void)
{
    pSA = NULL;
    bIS_INIT = false;
}

THEVARIANT::THEVARIANT(VARIANT * pVARIANT_IN)
{
    pSA = NULL;
    if ( V_VT(pVARIANT_IN) == VT_DISPATCH )
    {
        unsigned int uiArgErr;
        DISPID dispidValue;
        LPOLESTR XName = L"Value";
        pVARIANT_IN->pdispVal->GetIDsOfNames(IID_NULL, &XName, 1, LOCALE_SYSTEM_DEFAULT, &dispidValue);
        VARIANT * pVARIANT_IN_EXTENSION = new VARIANT();
        VariantInit(pVARIANT_IN_EXTENSION);
        DISPPARAMS dispparams;
        dispparams.cArgs = 0;
        dispparams.cNamedArgs = 0;
        EXCEPINFO exception;
        pVARIANT_IN->pdispVal->Invoke(dispidValue, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, pVARIANT_IN_EXTENSION, &exception, &uiArgErr);
        if( V_VT(pVARIANT_IN_EXTENSION) & VT_ARRAY )
        {
            if(pVARIANT_IN_EXTENSION->parray->cDims == 2)
            {
                plDIM[0].SetLowerBound(pVARIANT_IN_EXTENSION->parray->rgsabound[0].lLbound);
                plDIM[0].SetCount(pVARIANT_IN_EXTENSION->parray->rgsabound[0].cElements);
                plDIM[1].SetLowerBound(pVARIANT_IN_EXTENSION->parray->rgsabound[1].lLbound);
                plDIM[1].SetCount(pVARIANT_IN_EXTENSION->parray->rgsabound[1].cElements);
                pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
                pSA->CopyFrom(pVARIANT_IN_EXTENSION->parray);
                lNBLINES = plDIM[1].cElements;
                lNBCOLUMNS = plDIM[0].cElements;
                bIS_INIT = true;
            }
            else if(pVARIANT_IN_EXTENSION->parray->cDims == 1)
            {
                plDIM[0].SetLowerBound(pVARIANT_IN_EXTENSION->parray->rgsabound[0].lLbound);
                plDIM[0].SetCount(pVARIANT_IN_EXTENSION->parray->rgsabound[0].cElements);
                plDIM[1].SetLowerBound(pVARIANT_IN_EXTENSION->parray->rgsabound[0].lLbound);
                plDIM[1].SetCount(1);
                pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
                pSA->CopyFrom(pVARIANT_IN_EXTENSION->parray);
                lNBLINES = plDIM[1].cElements;
                lNBCOLUMNS = plDIM[0].cElements;
                bIS_INIT = true;
            }
        }
        else
        {
            plDIM[0].SetLowerBound(0);
            plDIM[0].SetCount(1);
            plDIM[1].SetLowerBound(0);
            plDIM[1].SetCount(1);
            pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
            ATL::CComVariant varArrayWrapper(*pVARIANT_IN_EXTENSION);
            plINDEX[0]=0;
            plINDEX[1]=0;
            HRESULT hr = pSA->MultiDimSetAt(plINDEX,varArrayWrapper);
            ATLASSERT(hr == S_OK);
            lNBLINES = plDIM[1].cElements;
            lNBCOLUMNS = plDIM[0].cElements;
            bIS_INIT = true;
        }
    }
    else if( V_VT(pVARIANT_IN) & VT_ARRAY ) // this case
    {
        if(pVARIANT_IN->parray->cDims == 2)
        {
            plDIM[0].SetLowerBound(pVARIANT_IN->parray->rgsabound[0].lLbound);
            plDIM[0].SetCount(pVARIANT_IN->parray->rgsabound[0].cElements);
            plDIM[1].SetLowerBound(pVARIANT_IN->parray->rgsabound[1].lLbound);
            plDIM[1].SetCount(pVARIANT_IN->parray->rgsabound[1].cElements);
            pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
            pSA->CopyFrom(pVARIANT_IN->parray);
            lNBLINES = plDIM[1].cElements;
            lNBCOLUMNS = plDIM[0].cElements;
            bIS_INIT = true;
        }
        else if(pVARIANT_IN->parray->cDims == 1)
        {
            plDIM[0].SetLowerBound(pVARIANT_IN->parray->rgsabound[0].lLbound);
            plDIM[0].SetCount(pVARIANT_IN->parray->rgsabound[0].cElements);
            plDIM[1].SetLowerBound(pVARIANT_IN->parray->rgsabound[0].lLbound);
            plDIM[1].SetCount(1);
            pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
            pSA->CopyFrom(pVARIANT_IN->parray);
            lNBLINES = plDIM[1].cElements;
            lNBCOLUMNS = plDIM[0].cElements;
            bIS_INIT = true;
        }
    }
    else
    {
        plDIM[0].SetLowerBound(0);
        plDIM[0].SetCount(1);
        plDIM[1].SetLowerBound(0);
        plDIM[1].SetCount(1);
        pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
        ATL::CComVariant varArrayWrapper(*pVARIANT_IN);
        plINDEX[0]=0;
        plINDEX[1]=0;
        HRESULT hr = pSA->MultiDimSetAt(plINDEX,varArrayWrapper);
        ATLASSERT(hr == S_OK);
        lNBLINES = plDIM[1].cElements;
        lNBCOLUMNS = plDIM[0].cElements;
        bIS_INIT = true;
    }
}

THEVARIANT::THEVARIANT(double * pdARRAY_IN, long NBLINES, long NBCOLUMNS)
{
    plDIM[0].SetLowerBound(0);
    plDIM[0].SetCount(NBLINES);
    plDIM[1].SetLowerBound(0);
    plDIM[1].SetCount(NBCOLUMNS);
    pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
    for(int j=0; j<NBCOLUMNS; j++)
    {
        for(int i=0; i<NBLINES; i++)
        {
            vTMP = pdARRAY_IN[i+j*NBLINES];
            vTMP.vt = VT_R8;
            plINDEX[0] = i;
            plINDEX[1] = j;
            HRESULT hr = pSA->MultiDimSetAt(plINDEX,vTMP);
            ATLASSERT(hr == S_OK);
        }
    }
    lNBLINES = NBLINES;
    lNBCOLUMNS = NBCOLUMNS;
    bIS_INIT = true;
}

THEVARIANT::THEVARIANT(std::vector<double>& pdARRAY_IN, long NBLINES, long NBCOLUMNS)
{
    plDIM[0].SetLowerBound(0);
    plDIM[0].SetCount(NBLINES);
    plDIM[1].SetLowerBound(0);
    plDIM[1].SetCount(NBCOLUMNS);
    pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
    for(int j=0; j<NBCOLUMNS; j++)
    {
        for(int i=0; i<NBLINES; i++)
        {
            vTMP = pdARRAY_IN[i+j*NBLINES];
            vTMP.vt = VT_R8;
            plINDEX[0] = i;
            plINDEX[1] = j;
            HRESULT hr = pSA->MultiDimSetAt(plINDEX,vTMP);
            ATLASSERT(hr == S_OK);
        }
    }       
    lNBLINES = NBLINES;
    lNBCOLUMNS = NBCOLUMNS;
    bIS_INIT = true;
}

THEVARIANT::THEVARIANT(std::vector<int>& pdARRAY_IN, long NBLINES, long NBCOLUMNS)
{
    plDIM[0].SetLowerBound(0);
    plDIM[0].SetCount(NBLINES);
    plDIM[1].SetLowerBound(0);
    plDIM[1].SetCount(NBCOLUMNS);
    pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
    for(int j=0; j<NBCOLUMNS; j++)
    {
        for(int i=0; i<NBLINES; i++)
        {
            vTMP = pdARRAY_IN[i+j*NBLINES];
            vTMP.vt = VT_I4;
            plINDEX[0] = i;
            plINDEX[1] = j;
            HRESULT hr = pSA->MultiDimSetAt(plINDEX,vTMP);
            ATLASSERT(hr == S_OK);
        }
    }       
    lNBLINES = NBLINES;
    lNBCOLUMNS = NBCOLUMNS;
    bIS_INIT = true;
}

THEVARIANT::THEVARIANT(const THEVARIANT& MyV_COPY)
{
    pSA = NULL; 
    if( MyV_COPY.pSA != NULL && MyV_COPY.bIS_INIT )
    {
        plDIM[0].SetLowerBound(0);
        plDIM[0].SetCount(MyV_COPY.plDIM[0].GetLowerBound());
        plDIM[1].SetLowerBound(0);
        plDIM[1].SetCount(MyV_COPY.plDIM[1].GetLowerBound());
        pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
        pSA->CopyFrom(*MyV_COPY.pSA);
        lNBLINES = MyV_COPY.lNBLINES;
        lNBCOLUMNS = MyV_COPY.lNBCOLUMNS;
        bIS_INIT = true;
    }
}

THEVARIANT::~THEVARIANT(void)
{
    delete pSA;
    pSA = NULL;
    bIS_INIT = false;
}

void THEVARIANT::SET_LDIMENSIONS(long NBLINES, long NBCOLUMNS)
{
    if(!bIS_INIT)
    {
        plDIM[0].SetLowerBound(0);
        plDIM[0].SetCount(NBLINES);
        plDIM[1].SetLowerBound(0);
        plDIM[1].SetCount(NBCOLUMNS);
        pSA = new ATL::CComSafeArray<VARIANT>(plDIM,2);
        lNBLINES = NBLINES;
        lNBCOLUMNS = NBCOLUMNS;
        bIS_INIT=true;
    }
}

void THEVARIANT::FILL(long lLINE, long lCOL, ATL::CComVariant & VARIANT_IN, VARTYPE VARIANT_TYPE)
{
    if(bIS_INIT)
    {
        vTMP = VARIANT_IN;
        vTMP.vt = VARIANT_TYPE;
        plINDEX[0] = lLINE;
        plINDEX[1] = lCOL;
        HRESULT hr = pSA->MultiDimSetAt(plINDEX,vTMP);
        ATLASSERT(hr == S_OK);
    }
}

void THEVARIANT::ATTACH(VARIANT * pVARIANT_OUT)
{
    if(bIS_INIT)
    {
        ATL::CComVariant varArrayWrapper(*pSA);
        varArrayWrapper.Detach(pVARIANT_OUT);

    }
}

void THEVARIANT::GET(long lLINE, long lCOL, ATL::CComVariant& VARIANT_OUT)
{
    plINDEX[0] = lLINE+plDIM[1].lLbound;            //be careful with the order
    plINDEX[1] = lCOL+plDIM[0].lLbound;
    HRESULT hr = pSA->MultiDimGetAt(plINDEX,VARIANT_OUT);
    ATLASSERT(hr == S_OK);
}

long THEVARIANT::GETNBCOLS( void ) const
{
    return lNBCOLUMNS ;
}

long THEVARIANT::GETNBLINES( void ) const
{
    return lNBLINES ;
}

Maybe there is an error in this variant wrapper class THEVARIANT, but I tested it, and I don't think so.

Olórin
  • 3,367
  • 2
  • 22
  • 42
  • Not sure, but I think you may need `TheATLObj.MULT(TheDouble, ref TheMatrix);`. `ref` is the c# equivalent of `[in, out]`. – crashmstr Oct 21 '14 at 19:50
  • This is an error : "The best overloaded method match for 'MyVARIANT_TESTLib.ITheATLObject.MULT3(double, ref object)' has some invalid arguments". Actually, if you go in the c# idl, you will see that all [in, out] variant * elements in signatures of function are flagged as ref object. – Olórin Oct 21 '14 at 20:07
  • 1
    You are violating [out], you never actually update the caller's VARIANT in your C++ code. Not noticeable in the VBA case since it passes an IRange* and you have no reason whatsoever to generate a new IRange. But fail whale in the C# case. You have to create a new VARIANT that wraps the safe array and assign it to *pVarInOut. Significant changes required. – Hans Passant Oct 21 '14 at 23:30
  • When you say that I never update the caller's VARIANT in your C++ code, you mean pVarInOut, right ? I do not understand, because I construct a THEVARIANT pMyVarInOut from it, modify this one thank to the FILL method (correctly coded now, by passing in it the ATL::CComVariant VARIANT_IN by reference and not anymore by value), and then I update pVarInOut thanks to the ATTACH method. So that the C++ does modify indeed the caller's VARIANT, doesn't it ? – Olórin Oct 22 '14 at 07:02
  • (At the `return S_OK;` line of the MULT3 method of the ATL/COM object, the pVarInOut is filled with correct values.) – Olórin Oct 22 '14 at 07:20
  • By the way : even if I do what you advocate (creating a new VARIANT that wraps the safe array and assign it to *pVarInOut), after the command line `TheATLObj.MULT(TheDouble, TheMatrix);` is executed, TheMatrix still does not contain updated values... – Olórin Oct 22 '14 at 07:28

1 Answers1

0

The error is in the c# code. The good call to the ATL/COM method is :

MyVARIANT_TESTLib.TheATLObject TheATLObj = new MyVARIANT_TESTLib.TheATLObject();
double TheDouble = 2.0;
object[,] TheMatrix = { { -3.0, 3.0, 2.0, 1.0 } };
object thevariant = new object();
thevariant = TheMatrix ;
TheATLObj.MULT(TheDouble, ref thevariant); // ref is crucial here

and then, thevariant is updated indeed, all its coefficients being multiplied by 2.0.

Olórin
  • 3,367
  • 2
  • 22
  • 42
  • One question remains : declaring `object[,] TheMatrix = { { -3.0, 3.0, 2.0, 1.0 } };` and then `object thevariant = new object(); thevariant = TheMatrix ;` is really ugly. How can we do to avoid this, and to be able to pass TheMatrix directly ? – Olórin Oct 24 '14 at 12:49
  • Ok, `object TheMatrix = new object[,] { { -3.0, 3.0, 2.0, 1.0 } };` ; ; makes it works. I am not an expert in c# at all, but I think that object is the universal type is c#, so that when I passed a object[,] to toto, I though that even if toto expects an object, "c#" could "cast/see" it correctly to the correct object, and do the job. – Olórin Oct 24 '14 at 13:17