Question

In c++ we can easily set up method chaining in a class by designing methods returning *this. Would this be possible in an ATL/COM setting ? Let's say I have a simple ATL class MyOBj. I would like to know if chaining is possible in this context, and if so, what would be the idl signature of the method that would support chaining ? Simple examples would be appreciated ! (In fact, my methods are called from VBA for excel, and I would like to have chaining in that VBA context, as we have chaining already for standard VBA methods.)

Thx a lot

R

EDIT :

In the .idl file I have this :

interface IRateModel : IDispatch{
    [id(1), helpstring("SETRATEMODEL")] HRESULT SETRATEMODEL( [in] VARIANT * var_in ) ;
    [id(2), helpstring("GETRATETERMSTRUCTURE")] HRESULT GETRATETERMSTRUCTURE( [in, out] VARIANT * var_in ) ;
};

interface IVolatilityModel : IDispatch{
    [id(1), helpstring("SETVOLATILITYMODEL")] HRESULT SETVOLATILITYMODEL( [in] VARIANT * var_in ) ;
    [id(2), helpstring("GETVOLATILITY")] HRESULT GETVOLATILITY( [in, out] VARIANT * var_in ) ;
};

interface IMyOption : IDispatch{
    [id(1), helpstring("SETMATURITY")] HRESULT SETMATURITY( [in] VARIANT * TheMaturity, [out,retval] IMyOption ** ret ) ;
    [id(2), helpstring("SETSTRIKE")] HRESULT SETSTRIKE( [in] VARIANT * TheStrike, [out,retval] IMyOption ** ret ) ;
    [id(3), helpstring("SETPAYOFF")] HRESULT SETPAYOFF( [in] VARIANT * ThePayoff, [out,retval] IMyOption ** ret ) ;
    [id(4), helpstring("ATTACHRATEMODEL")] HRESULT ATTACHRATEMODEL( [in] IRateModel ** TheRateModel, [out,retval] IMyOption ** ret ) ;
    [id(5), helpstring("ATTACHVOLATILITYPROCESS")] HRESULT ATTACHVOLATILITYPROCESS( [in] IVolatilityModel ** TheVolatilityModel, [out,retval] IMyOption ** ret ) ;
    [id(6), helpstring("PRICEIT")] HRESULT PRICEIT( [in, out] DOUBLE * price ) ;
};

SETRATEMODEL's implementation is :

STDMETHODIMP CRateModel::SETRATEMODEL( /*[in]*/ VARIANT * var_in )
{
    // something
    // ...

    return S_OK ;
}

This implementation hasn't changed since I added other interface. Before adding them, at debug, the VARIANT was as VT_R8 (coming from a vba VARIANT, this one coming from an excel's double) Now at debug, the variant is a VT_DISPATCH.

PS : I'm a very fresh-starter in ATL/COM.

Was it helpful?

Solution

Something like this:

interface IMyInterface {
  HRESULT DoSomething([in] long someParam, [out, retval] IMyInterface** ret);
  HRESULT DoSomethingElse([out, retval] IMyInterface** ret);
};

Scripting clients should be able to do myObj.DoSomething(42).DoSomethingElse()

OTHER TIPS

Following Igor Tandetnik first answer, I've tried chaining methods for ATL/COM as follows, in simple ATL/COM object called "Complex", modelling complex numbers :

in the IDL file :

[id(1), helpstring("SET")] HRESULT SET( [in/*,out*/] VARIANT * var_inx, [in/*,out*/] VARIANT * var_iny ) ;
[id(2), helpstring("SETREALPART")] HRESULT SETREALPART( [in] VARIANT * var_inx, [out, retval] IComplex** ret ) ;
[id(3), helpstring("SETIMAGPART")] HRESULT SETIMAGPART( [in] VARIANT * var_iny, [out, retval] IComplex** ret ) ;
[id(4), helpstring("MODULE")] HRESULT MODULE( [out, retval] VARIANT * var_out ) ;

in the Complex.h file :

class ATL_NO_VTABLE CComplex :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CComplex, &CLSID_Complex>,
    public IDispatchImpl<IComplex, &IID_IComplex, &LIBID_ATLSimpleChainingTestLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
    CComplex() ;
    CComplex( double x, double y ) ;
    CComplex & setRealPart( double x );
    CComplex & setImagPart( double y );
    void setRealPart2( double x );
    void setImagPart2( double y );
    double getRealPart( void ) ;
    double getImagPart( void ) ;
    double getModule( void ) ;

private:

    double _RealPart ;
    double _ImagPart ;

public:
DECLARE_REGISTRY_RESOURCEID(IDR_COMPLEX)


BEGIN_COM_MAP(CComplex)
    COM_INTERFACE_ENTRY(IComplex)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()



    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct()
    {
        return S_OK;
    }

    void FinalRelease()
    {
    }

public:

    STDMETHOD( SET )( /*[in]*/ VARIANT * var_inx, /*[in]*/ VARIANT * var_iny ) ;
    STDMETHOD( SETREALPART )( /*[in]*/ VARIANT * var_inx, /*[out, retval]*/ IComplex** ret ) ;
    STDMETHOD( SETIMAGPART )( /*[in]*/ VARIANT * var_iny, /*[out, retval]*/ IComplex** ret ) ;
    //STDMETHOD( SETREALPART )( /*[in]*/ VARIANT * var_inx ) ;
    //STDMETHOD( SETIMAGPART )( /*[in]*/ VARIANT * var_iny ) ;
    STDMETHOD( MODULE )( /*[out, retval]*/ VARIANT * var_out ) ;
};

OBJECT_ENTRY_AUTO(__uuidof(Complex), CComplex)

In the Complex.cpp file :

// Complex.cpp : Implementation of CComplex

#include "stdafx.h"
#include "Complex.h"
#include <cmath>
#include "MYVARIANT.h"

// CComplex

CComplex::CComplex( void )
{

}

CComplex::CComplex( double x, double y )
{
    _RealPart = x ;
    _ImagPart = y ;
}

CComplex & CComplex::setRealPart( double x )
{
    _RealPart = x ;
    return *this ;
}

void CComplex::setRealPart2( double x )
{
    _RealPart = x ;
}

CComplex & CComplex::setImagPart( double y )
{
    _ImagPart = y ;
    return *this ;
}

void CComplex::setImagPart2( double y )
{
    _ImagPart = y ;
}

double CComplex::getRealPart( void )
{
    return _RealPart ;
}

double CComplex::getImagPart( void )
{
    return _ImagPart ;
}

double CComplex::getModule( void )
{
    return std::sqrt( _RealPart*_RealPart + _ImagPart*_ImagPart ) ;
}

STDMETHODIMP CComplex::SET( /*[in]*/ VARIANT * var_inx, /*[in]*/ VARIANT * var_iny )
{
    MyVARIANT myvarx( var_inx ) ;
    MyVARIANT myvary( var_iny ) ;
    if ( myvarx.GETNBLINES()*myvarx.GETNBCOLS()*myvary.GETNBLINES()*myvary.GETNBCOLS() != 1L )
        return E_INVALIDARG ;
    ATL::CComVariant myccomvarx ;
    ATL::CComVariant myccomvary ;
    myvarx.GET(0, 0, myccomvarx ) ;
    myvary.GET(0, 0, myccomvary ) ;
    if ( ( myccomvarx.vt != VT_R8 ) || ( myccomvary.vt != VT_R8 ) )
        return E_INVALIDARG ;
    setRealPart2( myccomvarx.dblVal ) ;
    setImagPart2( myccomvary.dblVal ) ;
    return S_OK ;
}

STDMETHODIMP CComplex::SETREALPART( /*[in]*/ VARIANT * var_inx, /*[out, retval]*/ IComplex** ret )
//STDMETHODIMP CComplex::SETREALPART( /*[in]*/ VARIANT * var_inx  )
{
    MyVARIANT myvarx( var_inx ) ;
    if ( myvarx.GETNBLINES()*myvarx.GETNBCOLS() != 1L )
        return E_INVALIDARG ;
    ATL::CComVariant myccomvarx ;
    myvarx.GET(0, 0, myccomvarx ) ;
    if ( myccomvarx.vt != VT_R8 )
        return E_INVALIDARG ;
    setRealPart2( myccomvarx.dblVal ) ;
    return S_OK ;
}

STDMETHODIMP CComplex::SETIMAGPART( /*[in]*/ VARIANT * var_iny, /*[out, retval]*/ IComplex** ret )
//STDMETHODIMP CComplex::SETIMAGPART( /*[in]*/ VARIANT * var_iny  )
{
    MyVARIANT myvary( var_iny ) ;
    if ( myvary.GETNBLINES()*myvary.GETNBCOLS() != 1L )
        return E_INVALIDARG ;
    ATL::CComVariant myccomvary ;
    myvary.GET(0, 0, myccomvary ) ;
    if ( myccomvary.vt != VT_R8 )
        return E_INVALIDARG ;
    setImagPart2( myccomvary.dblVal ) ;
    return S_OK ;
}

STDMETHODIMP CComplex::MODULE( /*[out, retval]*/ VARIANT * var_out )
{
    double mod = getModule() ;
    MyVARIANT module( &mod, 1, 1) ;
    module.ATTACH( var_out ) ;
    return S_OK ;
}

//

MyVARIANT is a VARIANT wrapper class, that works perfectly and that as been fully backtested. In

ATL::CComVariant myccomvarx ;
myvarx.GET(0, 0, myccomvarx ) ;

GET fills the ATL::CComVariant myccomvarx with the coeff (0,0) of the MyVARIANT myvarx.

One can easily guess what

GETNBLINES()

and

GETNBCOLS()

methods are doing. In

MyVARIANT module( &mod, 1, 1) ;
module.ATTACH( var_out ) ;

the method ATTACH "fills" the VARIANT var_out with the MyVARIANT "module" constructed by the constructor

MyVARIANT( double *, long, 1)

which assciates (in this case) a MyVARIANT to a pointer to double. Let me say again that MyVARIANT has been fully backtested, and that it fully works.

Now, on the VBA for Excel side, I created the six following functions :

Function calcmodule11(ByRef x As Variant, ByRef y As Variant) As Variant

    Dim z As ATLSimpleChainingTestLib.Complex
    Set z = New ATLSimpleChainingTestLib.Complex
    Call z.SET(x, y)
    calcmodule11 = z.module()

End Function

Function calcmodule12(ByRef x As Variant, ByRef y As Variant) As Variant

    Dim z As ATLSimpleChainingTestLib.Complex
    Set z = New ATLSimpleChainingTestLib.Complex
    Dim xx As Variant
    xx = x
    Dim yy As Variant
    yy = y
    Call z.SET(xx, yy)
    calcmodule12 = z.module()

End Function

Function calcmodule21(ByRef x As Variant, ByRef y As Variant) As Variant

    Dim z As ATLSimpleChainingTestLib.Complex
    Set z = New ATLSimpleChainingTestLib.Complex
    z.SETREALPART (x)
    z.SETIMAGPART (y)
    calcmodule21 = z.module()

End Function

Function calcmodule22(ByRef x As Variant, ByRef y As Variant) As Variant

    Dim z As ATLSimpleChainingTestLib.Complex
    Set z = New ATLSimpleChainingTestLib.Complex
    Dim xx As Variant
    xx = x
    Dim yy As Variant
    yy = y
    z.SETREALPART (xx)
    z.SETIMAGPART (yy)
    calcmodule22 = z.module()

End Function

Function calcmodule31(ByRef x As Variant, ByRef y As Variant) As Variant

    Dim z As ATLSimpleChainingTestLib.Complex
    Set z = New ATLSimpleChainingTestLib.Complex
    z.SETREALPART(x).SETIMAGPART (y)
    calcmodule31 = z.module()

End Function

Function calcmodule32(ByRef x As Variant, ByRef y As Variant) As Variant

    Dim z As ATLSimpleChainingTestLib.Complex
    Set z = New ATLSimpleChainingTestLib.Complex
    Dim xx As Variant
    xx = x
    Dim yy As Variant
    yy = y
    Call z.SETREALPART(x).SETIMAGPART(y)
    calcmodule32 = z.module()

End Function

I called each of these six functions F (that, F is equal to calcmodule11, or calcmodule12, or... etc) in a excel cell, writing

=F(B3,B4)

in it, where B3 and B4 are two excel cells containing each the double 1. Here are the results obtained for each function :

calcmodule11 #VALUE! calcmodule12 1.414213562 calcmodule21 1.414213562 calcmodule22 #VALUE! calcmodule31 #VALUE! calcmodule32 #VALUE!

1.414213562 is indeed the right value expected.

Questions :

1) Why do I have a #VALUE! for the call to calcmodule11 ?

2) As calcmodule12 gives the right value and calcmodule11 not, I would expected the same behaviour for the pair (calcmodule21, calcmodule22), but it is the contrary : calcmodule21 gives the right value, and calcmodule22 not. Why ?

2) As Igor Tandetnik explained it its first answer to my question, I put method chaining in place in functions calcmodule31 and calcmodule32. And it doesn't work, where it work in the calcmodule21 and calcmodule22 case, at least for calcmodule21. Why ?

Igor Tandetnik, as I put in place exactly what you advised to me (correct me if I'm wrong), why doesn't it work ?...

Thx a lot.

Ok, the #VALUE! is caused in function calcmodule11 because what is passed to it as VARIANT isa "VARIANT/Object/Range" whose value2 component is a VARIANT/double, and I'm not handling VARIANT/Object/Range in my MyVARIANT wrapper class. But if y pass x.value2 (in vba) do the method, everything's fine. This explain also why the trick dim xx as variant, xx = x functions : do this somehow puts in xx the x.value2, but I dont know why... For the #VALUE! in VBA functions involving method chaining, the reason is the same, except that the VARIANT is even more complex : a pointer to a com obj instance...

Therefore, I will have to rewrite/complete my MyVARIANT class to handle all cases of VT_DISPATCH that will arise, as "VARIANT/Object/Range", but also more complexes other VT_DISPATCH'es...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top