0

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.

Olórin
  • 3,367
  • 2
  • 22
  • 42

3 Answers3

2

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()

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • And, by the way, would it be possible to design methods for an ATL/COM class 1 taking instances of an other ATL/COM class B, or returning an instance of another ATL/COM class C ? What would be the better way of doing it ? (I am still in an VBA context, cf the question.) – Olórin Sep 15 '13 at 00:26
  • 1
    Of course. You do it all the time - think e.g. of `IClassFactory::CreateInstance`. Note that, in COM, you don't work with classes, you work with interfaces. You can have a method that takes an interface pointer as an [in] parameter, or returns an interface pointer as an [out] one. – Igor Tandetnik Sep 15 '13 at 04:20
  • I added three ATL/COM interfaces in the same idl, and now a simple method of one of the three interface, of signature [id(1), helpstring("SETRATE")] HRESULT SETRATE( [in] VARIANT * var_in ) ; gets a variant of vt equal to VT_DISPATCH, whereas before it was VT_R8, as I was passing a double to in VBA. What did I do wrong ?... – Olórin Sep 15 '13 at 18:23
  • You used to pass a double and got `VT_R8`. Now you pass an interface pointer, and get `VT_DISPATCH`. What exactly surprises you about this outcome? – Igor Tandetnik Sep 15 '13 at 18:29
  • I guess I should have said that I was starting in yesterday in ATL/COM, so I still don't know exactly how things are really working. (Thought that my question was noobish enough to make that clear, but no... ;-)) Before I was passing a VARIANT which was coming (through VBA) from a double in an excel cell, and which was VT_R8. Now I do the same, and the VARIANT "is a" VT_DISPATCH. This is what I don't understand, and what is puzzling me. I will edit my question to put idl signature, so that you can see what I did - bad. – Olórin Sep 15 '13 at 18:38
  • An example would be fantastic, and would help me, if possible... Thx – Olórin Sep 15 '13 at 21:58
  • What has changed between "before" and "now"? Also, if you want to accept a parameter of type double, why don't you just say so in the IDL? `... SETRATE([in] double param)` should work just fine. – Igor Tandetnik Sep 15 '13 at 23:51
  • Before, I was not passing a double but a variant because of error checking, I was doing things if the intended double wasn't a double etc. And it was perfectly working, the vt of the variant was VT_R8 indeed and everything was working fine. This was when the only interface I had was IRateModel. But as soon as I added an interface (with the wizard, "add atl simple class") IMyOption containing a method taking as a parameter a IRateModel **, it started not to work anymore, and the double I was passing was recognized as a VT_DISPATCH. And being a noob, I don't know what is happening. – Olórin Sep 16 '13 at 06:47
  • In fact, I just checked : even when I have only one interface IRateModel, it is sufficient to have method of signature [id(1), helpstring("SETSOMETHING")] HRESULT SETSOMETHING( [in] VARIANT * var_thatshoulbeavt_r8, [out, retval] IRateModel** ret ) for chaining (as you advised to me) for seeing that the var_thatshoulbeavt_r8 that I'm receiving as a vt equal to VT_DISPACH, and not VT_R8 anymore... – Olórin Sep 16 '13 at 08:20
  • What does VBA calling code look like when this happens? Also, check that the VBA project works off the fresh copy of the type library, one reflecting your recent changes. Strange things happen when a client uses a stale copy of the TLB. – Igor Tandetnik Sep 16 '13 at 12:35
  • I am always using the type library corresponding to the last compilation... The VBA callin code looks like this : Public Function PRICEBS(ByRef rate As Variant) As Double// Dim RateModel As ATLCOMExamplesLib.RateModel// Set RateModel = New ATLCOMExamplesLib.RateModel// Call RateModel.SETRATEMODEL(rate)// End Function// I put "//" for line breaking as I cannot suceed putting code here... – Olórin Sep 16 '13 at 13:41
  • How is `PRICEBS` called then? What's `rate`? Are you, by any chance, calling it passing a return value of some method - one that you changed from returning double to returning an interface? Also, `PRICEBS` is declared as returning Double, but I don't see it actually return anything. – Igor Tandetnik Sep 16 '13 at 18:18
  • PRICEBS can be called from excel, and RATE is just a excel cell containing a double. I missed one line before the End Function, and this line is : PRICEBS = 2#. It was just a "fake function to test" the call to SETRATEMODEL, and who showed already a problem. I will answer to my question to develop the things. – Olórin Sep 16 '13 at 20:29
  • All the details are put in the "answer" I just gave to my question. – Olórin Sep 16 '13 at 21:11
0

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.

Olórin
  • 3,367
  • 2
  • 22
  • 42
  • Seems to be some quirk of VBA, which I must admit I'm not really familiar with. As far as I can tell, the only difference between `calcmodule11` and `calcmodule12` is that parameters are copied to local variables first, before being passed to the method. I have no idea why that would matter. – Igor Tandetnik Sep 17 '13 at 02:37
  • 1
    As to the things I do know about: I can't help but notice that your methods that take `/*[out, retval]*/ IComplex** ret` don't actually assign anything to the `[out]` parameter. You are supposed to do something like `*ret = this; InternalAddRef();` – Igor Tandetnik Sep 17 '13 at 02:41
  • 1) Adding "*ret = this ;" and "InternalAddRef() ;" doesn't change anything at all. 2) Same conclusion for passing byval or byref in VBA functions signatures. 3) Regarding the copying to local variables inside VBA function, I don't know why it works, and tried it desperately. Quite strange. Nevertheless, thx for havin tried to help me ! – Olórin Sep 17 '13 at 07:43
0

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...

Olórin
  • 3,367
  • 2
  • 22
  • 42
  • I did here http://stackoverflow.com/questions/18859999/selected-range-of-excel-cells-passed-to-an-argument-as-variant-of-a-vba-functi/21202829#21202829 (see my own answer) thanks to the help of Igor Tandetnik – Olórin Jan 18 '14 at 10:32