Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

ANN: New article added to the MVP ATL/COM FAQ

11 views
Skip to first unread message

Alexander Nickolov

unread,
Apr 1, 2003, 6:31:13 PM4/1/03
to
http://www.mvps.org/vcfaq/com/9.htm

It cover the subject of implementing multiple dual interfaces.
(At last...)

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

Vi2

unread,
Apr 3, 2003, 8:09:06 AM4/3/03
to
"Alexander Nickolov" <agnic...@mvps.org> wrote in message news:<#ueTKaK#CHA....@TK2MSFTNGP11.phx.gbl>...

> http://www.mvps.org/vcfaq/com/9.htm
>
> It cover the subject of implementing multiple dual interfaces.
> (At last...)
>

The article is very useful.

But I don't agree with the first paragraph.

QUOTE: "A common question I've encountered in the ATL newsgroups is
how to implement multiple dual interfaces on a single object. Let's
state it upfront: this is illegal! It contradicts a core COM law -
each interface implemented on an object must behave consistently
regardless how it's obtained (in fact this is a corollary of other COM
laws, but I won't go into the details here). Attempting to implement
multiple dual interfaces breaks this law for the IDispatch interface.
This may often go unnoticed (people rarely need dual interfaecs in the
first place), but it manifests its ugly head when marshaling is
involved, because the proxy manager enforces it. "

The question is "If an object implements the IB interface derived from
IA interface, can this object return the error code from
QueryInterface(IID_IA)?"

The answer is "Yes, it can. Besides IUnknown."

The IDispatch is not IUnknown. The object with multiple dual
interfaces can have no IDispacth interface at all (in "QueryInterface"
terms).

OTOH, the QueryInterface(IID_IDispacth) can return the pointer which
doesn't belong to any dual interfaces of object.

So, IMHO, the problem of implementation of "multiple dual interfaces"
is a problem of C++ language, not the COM system.

Igor Tandetnik

unread,
Apr 3, 2003, 10:36:03 AM4/3/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...

> The IDispatch is not IUnknown. The object with multiple dual
> interfaces can have no IDispacth interface at all (in "QueryInterface"
> terms).

Why on earth would such an object have its interfaces be dual in the
first place? This does not make any sense. In any case, this is not
allowed and in some cases does not work, see below.

> OTOH, the QueryInterface(IID_IDispacth) can return the pointer which
> doesn't belong to any dual interfaces of object.

No it can't. This breaks under marshaling. When you have interface
IDerived derived from IBase, and when IDerived is marshalled, the proxy
manager on the client side actually creates two proxies, one that knows
how to marshal IBase methods and the other that marshals IDerived
methods beyond those in IBase. When an IBase method is called via
IDerived proxy, it simply chains to IBase proxy which performs the
actual marshaling.

Now the punch line - if the proxy manager was first queried for IDerived
and later for IBase, it satisfies the second QI locally, using existing
IBase proxy. It does not go over the wire and ask the real object for
IBase interface. The object may try to be smart and implement some
tricks in QI for IBase, but all this is moot - QI for IBase is never
called on it. Conversely, if the proxy manager is first queried for
IBase and later for IDerived, it uses existing IBase proxy to implement
IBase portion of IDerived, which means that on the server, IBase methods
are never called through IDerived implementation but rather through
standalone IBase implementation.

Thus, the server provides two different implementations of IBase
methods, but the client across apartment boundary can never access both
implementations on the same object. Furthemore, which of the two
implementations it can access depends on the order of QueryInterface
calls the client makes, something that COM rules are designed to avoid.

> So, IMHO, the problem of implementation of "multiple dual interfaces"
> is a problem of C++ language, not the COM system.

No it's not. Implementing multiple dual interfaces is prohibited by COM
rules, and there are situations where COM runtime actually enforces
those rules.

Besides the fact they don't work, multiple dual interfaces are useless
in practice. Clients that want to late-bind through IDispatch can
typically only use the default interface (the one you return in response
to QI for IDispatch). Clients that can QI for non-default interfaces can
also do early binding. I don't know of any client that is capable to
access non-default interfaces, but is incapable of early binding.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken


Alexander Nickolov

unread,
Apr 3, 2003, 3:53:05 PM4/3/03
to
It's a bit lengthy to explain the actual formal logic involved in the
space of an application oriented FAQ article, so I didn't. You are
completely wrong though, and Igor pointed out some ramifications.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Vi2" <shar...@hotmail.com> wrote in message news:e8f1a8be.03040...@posting.google.com...

Vi2

unread,
Apr 4, 2003, 6:56:40 AM4/4/03
to
"Igor Tandetnik" <itand...@whenu.com> wrote in message news:<#6Y$9af#CHA....@TK2MSFTNGP11.phx.gbl>...
>
> ...
>

Can the object have the multiple dispinterfaces?

In own "Understanding ActiveX and OLE" David Chappel speaks that yes.
It satisfys the COM rules.

If yes, why is it forbidden for the dual interfaces?

PS
If we cannot use the rest of the event interfaces, it doesn't point
that the object cannot fire several events set.

Igor Tandetnik

unread,
Apr 4, 2003, 9:20:42 AM4/4/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> Can the object have the multiple dispinterfaces?

No.

> In own "Understanding ActiveX and OLE" David Chappel speaks that yes.
> It satisfys the COM rules.

Never heard of David Chappell nor his book. Multiple dispinterfaces
demonstrably don't work in the presense of marshaling. Books exist that
claim Earth is flat and the sun turns around it, but I personally
wouldn't want to go around quoting those as hard facts.

> If we cannot use the rest of the event interfaces, it doesn't point
> that the object cannot fire several events set.

What prevents you from sinking several outgoing interfaces? Many
automation objects support at least two outgoing interfaces - their
default event interface and IPropertyNotifySink. I fail to see how this
is relevant to the discussion at hand though.

Vi2

unread,
Apr 7, 2003, 6:55:16 AM4/7/03
to
"Igor Tandetnik" <itand...@whenu.com> wrote in message news:<Oni#gVr#CHA....@TK2MSFTNGP10.phx.gbl>...

>
> > In own "Understanding ActiveX and OLE" David Chappel speaks that yes.
> > It satisfys the COM rules.
>
> Never heard of David Chappell nor his book. Multiple dispinterfaces
> demonstrably don't work in the presense of marshaling. Books exist that
> claim Earth is flat and the sun turns around it, but I personally
> wouldn't want to go around quoting those as hard facts.
>

I'm sorry. I'm powerless in English. I can read it well, but cannot
clearly write what I think. So I will try to use C++ code.

Without IDispatch
-----------------

class ATL_NO_VTABLE Cx :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<Cx, &CLSID_x>,
public IDispatchImpl<IDualx, &IID_IDualx, &LIBID_TANDETNIKLib>,
public IDispatchImpl<IDualy, &IID_IDualy, &LIBID_TANDETNIKLib>
{
public:

BEGIN_COM_MAP(Cx)
COM_INTERFACE_ENTRY(IDualx)
COM_INTERFACE_ENTRY(IDualy)
END_COM_MAP()

// IDualx
public:
STDMETHOD(MethodX)(/*[in]*/ long i);

// IDualy
public:
STDMETHOD(MethodY)(/*[in]*/ long i);
};

This object has only interfaces IUnknown, IDualx and IDualy (if uses
//***0***).

1. There is no IDispatch interface, which can be queried by
QI(IID_IDispatch). There is no proxy which can satisfy this QI.

2. This object has 2 separate implementations of functions of
IDispatch's part of dual interface, such as GetIdsOfNames and Invoke.

There is no breaking of any COM rule.

With IDispatch
--------------

class ATL_NO_VTABLE Cx :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<Cx, &CLSID_x>,
public IDispatchImpl<IDualx, &IID_IDualx, &LIBID_TANDETNIKLib>,
public IDispatchImpl<IDualy, &IID_IDualy, &LIBID_TANDETNIKLib>
{
public:

BEGIN_COM_MAP(Cx)
COM_INTERFACE_ENTRY(IDualx)
COM_INTERFACE_ENTRY(IDualy)
//***1*** COM_INTERFACE_ENTRY_IID(IID_IDispatch,IDualx)
//***2*** COM_INTERFACE_ENTRY_IID(IID_IDispatch,IDualy)
END_COM_MAP()

// IDualx
public:
STDMETHOD(MethodX)(/*[in]*/ long i);

// IDualy
public:
STDMETHOD(MethodY)(/*[in]*/ long i);
};

This object has interfaces IUnknown, IDispatch, IDualx and IDualy (if
you uncomment the //***1*** or //***2*** lines).

1. This object has 2 separate implementations of functions of
IDispatch's part of dual interface, such as GetIdsOfNames and Invoke.

There is also no breaking of any COM rule.

Common sentence
---------------

You do not feel the difference with IDispatch* pointer in such cases:
IDualInterface* p = ...;
IDispatch *p1, *p2;
// 1
p->QueryInterface(IID_IDispatch, (void**) &p1);
// 2
p2 = reinterpret_cast<IDispatch*>( p ); p2->AddRef();

As a rule, p1 will be not equal to p2. (Such rule acts on IUnknown's
part of any interface.) Because the p1 may be not returned.

Igor Tandetnik

unread,
Apr 7, 2003, 11:35:23 AM4/7/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> class ATL_NO_VTABLE Cx :
> public CComObjectRootEx<CComSingleThreadModel>,
> public CComCoClass<Cx, &CLSID_x>,
> public IDispatchImpl<IDualx, &IID_IDualx, &LIBID_TANDETNIKLib>,
> public IDispatchImpl<IDualy, &IID_IDualy, &LIBID_TANDETNIKLib>
> {
> public:
>
> BEGIN_COM_MAP(Cx)
> COM_INTERFACE_ENTRY(IDualx)
> COM_INTERFACE_ENTRY(IDualy)
> END_COM_MAP()
> };
>
> This object has only interfaces IUnknown, IDualx and IDualy (if uses
> //***0***).
>
> 1. There is no IDispatch interface, which can be queried by
> QI(IID_IDispatch). There is no proxy which can satisfy this QI.

Wrong. The client cannot query for IDispatch, but the client can still
call IDispatch::Invoke through either IDualx or IDualy (that's the whole
point of making them dual, right)? Thus, there must be _some_ proxy
capable of marshaling IDispatch::Invoke call (you know, pack the
parameters into byte stream, send the stream over to the stub, unpack
the results from the byte stream sent back from the stub, return them to
the caller). The point is, proxy manager never sets up two proxies for
the same interface, so both IDualx and IDualy always use the same proxy
for marshaling their IDispatch parts. On the server side, this proxy is
backed by whichever of IDualx and IDualy was first queried for.

I'm not sure it is legal in COM to succeed QI for derived interface but
fail QI for base interface. I'm not sure how this will work out accross
apartment boundary. I strongly suspect such technique is in fact illegal
(in keeping with the rule that a set of interfaces accessible on an
object must be static over the lifetime of the object, regardless of the
way an interface is accessed), and that the proxy manager will actually
successfully satisfy a QI for IDispatch using internal IDispatch proxy
(backed by IDispatch part of IDualx or IDualy, whichever was queried for
first). I haven't tested it though.

Regardless of whether it works or not, this configuration does not make
any sense anyway. If you don't actually want to expose IDispatch, why
make IDualx and IDualy dual in the first place? What exactly do you gain
from doing this, besides complexity, uncertainty and confusion?

> 2. This object has 2 separate implementations of functions of
> IDispatch's part of dual interface, such as GetIdsOfNames and Invoke.

Sure it has, but a client accross apartment boundary has no way to
access both of them on a given object, and which of them it _can_ access
depends on the order of QI calls it makes - something COM rules are
designed to avoid.

> There is no breaking of any COM rule.

This configuration breaks pretty much every COM rule except COM identity

> class ATL_NO_VTABLE Cx :
> public CComObjectRootEx<CComSingleThreadModel>,
> public CComCoClass<Cx, &CLSID_x>,
> public IDispatchImpl<IDualx, &IID_IDualx, &LIBID_TANDETNIKLib>,
> public IDispatchImpl<IDualy, &IID_IDualy, &LIBID_TANDETNIKLib>
> {
> public:
>
> BEGIN_COM_MAP(Cx)
> COM_INTERFACE_ENTRY(IDualx)
> COM_INTERFACE_ENTRY(IDualy)
> //***1*** COM_INTERFACE_ENTRY_IID(IID_IDispatch,IDualx)
> //***2*** COM_INTERFACE_ENTRY_IID(IID_IDispatch,IDualy)
> END_COM_MAP()
>

> This object has interfaces IUnknown, IDispatch, IDualx and IDualy (if
> you uncomment the //***1*** or //***2*** lines).
>
> 1. This object has 2 separate implementations of functions of
> IDispatch's part of dual interface, such as GetIdsOfNames and Invoke.

Sure it has, but a client accross apartment boundary has no way to
access both of them on a given object, and which of them it _can_ access
depends on the order of QI calls it makes - something COM rules are
designed to avoid (forget me for repeating myself - I just hope saying
the same thing over and over will get the point across after all).

> There is also no breaking of any COM rule.

This configuration breaks pretty much every COM rule except COM identity

> You do not feel the difference with IDispatch* pointer in such cases:
> IDualInterface* p = ...;
> IDispatch *p1, *p2;
> // 1
> p->QueryInterface(IID_IDispatch, (void**) &p1);
> // 2
> p2 = reinterpret_cast<IDispatch*>( p ); p2->AddRef();
>
> As a rule, p1 will be not equal to p2.

Correct. And that's exactly what causes problems. The client does not
care whether the pointers are physically equal or not, but it reasonably
expects to get the same effect when calling IDispatch::Invoke through
either p1 or p2. Moreover, when COM proxy manager happens to be the
client, it _assumes_ the effect is the same, and acts accordingly. If
the effect is _not_ the same after all, the manager acts upon wrong
assumption and the outcome is unpredictable. And you probably don't want
to run programs with unpredictable behavior (well, except maybe when you
code a white noise generator, then you want the behavior as
unpredictable as possible).

> (Such rule acts on IUnknown's part of any interface.)

Correct again. And there are rules carefully spelled out that require
that _any_ IUnknown implementation on a given object have the same
observable behavior - provide access to the same set of interfaces and
manage reference count consistently. The same holds for any interface.

Again, the client does not care if two interface pointers for the same
interface on the same object are physically equal (they are allowed not
to be equal, and in many cases they are indeed not equal), just that the
observable behavior of method calls through those pointers is the same.
However, you are trying to have two IDispatch implementations on the
same object with explicitly _different_ behavior, and this is what
violates COM rules.

Yet again: Two implementations of the same interface on the same object
are fine, as long as they _behave in exactly the same way_. Two
implementations that _behave differently_ violate COM rules, and break
as soon as marshaling is involved.

Craig Powers

unread,
Apr 7, 2003, 3:02:38 PM4/7/03
to

Here's a question where I'm still not satisfied with answers I've
gotten in the past: What happens when the implementation of
IDispatch in the coclass supports both IDualx and IDualy
simultaneously (assuming no DISPID collisions)? What potential
breakage ensues in this scenario? For that matter, if there are
DISPID collisions, are there any potential issues other than
arbitrary selection of which function gets called?

I realize that the appropriate approach is to define a single
combined interface, but I'm trying to evaluate the impact of doing
it incorrectly.

--
Craig Powers
MVP - Visual C++

Alexander Nickolov

unread,
Apr 7, 2003, 3:30:55 PM4/7/03
to
You are violating yet another COM rule - all base interfaces to a
derived interface you implement _must_ be exposed via QI!

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Vi2" <shar...@hotmail.com> wrote in message news:e8f1a8be.03040...@posting.google.com...

Alexander Nickolov

unread,
Apr 7, 2003, 3:36:21 PM4/7/03
to
This breaks the definition of a dual interface - that the functionality
available through the vtable is equivalent to the functionality accessed
via IDispatch::Invoke. This is not a COM rule - this is an Automation
rule (the one that defines what a dual interface _is_ in the first place).

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Craig Powers" <eni...@hal-pc.org> wrote in message news:3E91CB4E...@hal-pc.org...

Craig Powers

unread,
Apr 7, 2003, 4:19:37 PM4/7/03
to
Alexander Nickolov wrote:
>
> "Craig Powers" <eni...@hal-pc.org> wrote in message news:3E91CB4E...@hal-pc.org...
> >
> > Here's a question where I'm still not satisfied with answers I've
> > gotten in the past: What happens when the implementation of
> > IDispatch in the coclass supports both IDualx and IDualy
> > simultaneously (assuming no DISPID collisions)? What potential
> > breakage ensues in this scenario? For that matter, if there are
> > DISPID collisions, are there any potential issues other than
> > arbitrary selection of which function gets called?
> >
> > I realize that the appropriate approach is to define a single
> > combined interface, but I'm trying to evaluate the impact of doing
> > it incorrectly.
>
> This breaks the definition of a dual interface - that the functionality
> available through the vtable is equivalent to the functionality accessed
> via IDispatch::Invoke. This is not a COM rule - this is an Automation
> rule (the one that defines what a dual interface _is_ in the first place).

What's the practical impact of breaking the rule, though? The
practical impact that I can see is that if there's a DISPID collision
(for functions with different names) it would be unpredictable which
one got called. Are there more serious consequences?

Oh, and the other practical effect that I can see is that there
would actually be more functions available through Invoke than
through the vtable, assuming the interfaces aren't identical,
which I could see being an issue if it were abused but doesn't
seem like a problem if it's not used.


(And in case it's not clear, I'm not planning on doing multiple
dual interfaces in the future, I'm satisfied that it's something
that's not supposed to be done.)

Igor Tandetnik

unread,
Apr 7, 2003, 6:38:15 PM4/7/03
to
I don't think there are any ill effects, assuming IDispatch portions of
both IDualx and IDualy, as well as a standalone IDispatch implementation
if any, correctly defer to such a combined implementation. As I say, you
can have as many implementations of the same interface as you want, as
long as they all do the same thing when called.

Though legal, it still does not make much sense to me. If you are going
to write a hand-rolled IDispatch implementation, what's the point making
IDualx and IDualy dual? Why not just custom, possibly oleautomation,
interfaces? As soon as you answer with "hmm, right, there is no reason,
I'm happy with them being custom", please allow me to humbly refer you
to

http://www.sellsbrothers.com/tools/multidisp/

section 3, which describes just such an implementation.


--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken

"Craig Powers" <eni...@hal-pc.org> wrote in message
news:3E91CB4E...@hal-pc.org...

Craig Powers

unread,
Apr 7, 2003, 6:49:54 PM4/7/03
to
Igor Tandetnik wrote:
>
> "Craig Powers" <eni...@hal-pc.org> wrote in message
> news:3E91CB4E...@hal-pc.org...
> > Here's a question where I'm still not satisfied with answers I've
> > gotten in the past: What happens when the implementation of
> > IDispatch in the coclass supports both IDualx and IDualy
> > simultaneously (assuming no DISPID collisions)? What potential
> > breakage ensues in this scenario? For that matter, if there are
> > DISPID collisions, are there any potential issues other than
> > arbitrary selection of which function gets called?
> >
> > I realize that the appropriate approach is to define a single
> > combined interface, but I'm trying to evaluate the impact of doing
> > it incorrectly.
>
> I don't think there are any ill effects, assuming IDispatch portions of
> both IDualx and IDualy, as well as a standalone IDispatch implementation
> if any, correctly defer to such a combined implementation. As I say, you
> can have as many implementations of the same interface as you want, as
> long as they all do the same thing when called.
>
> Though legal, it still does not make much sense to me. If you are going
> to write a hand-rolled IDispatch implementation, what's the point making
> IDualx and IDualy dual? Why not just custom, possibly oleautomation,
> interfaces? As soon as you answer with "hmm, right, there is no reason,
> I'm happy with them being custom", please allow me to humbly refer you
> to
>
> http://www.sellsbrothers.com/tools/multidisp/
>
> section 3, which describes just such an implementation.

That's exactly what I'm using right now to handle multiple dispatch.
I just wanted to make sure that after all the dark warnings about
violating rules and such that there wouldn't be any serious
practical issues. I don't plan on continuing this strategy in
the future, but I've got some components that I got into it before
I knew better. I was trying to duplicate VB stuff without realizing
which portions I could safely discard (indeed, which I'd be safer to
discard).

Since most of this stuff seems related to marshaling, and I'm not
using anything like COM+, MTS, or DCOM, I'm not sure I'd run into
most of the trouble spots anyway, but I'd like to be aware of them
so that I can troubleshoot them in the future if necessary.

Alexander Nickolov

unread,
Apr 7, 2003, 8:14:03 PM4/7/03
to
Well, since this is not a COM rule being violated (it's an Automation
rule), I'd say there are no hard consequences. It can easily lead to
maintenance nightmare though... In such a scenario it doesn't make
sense to have the original interfaces dual anyway (since you intend
to break their dual-ness anyway). What you have in fact is interfaces
that needlessly derive from IDispatch, while they could just as well
derive from IUnknown. They aren't dual, so the [dual] keyword is a
misleading decoration (it really means [oleautomation]).

BTW, strictly speaking, deriving a dual interface from another dual
interface is illegal too...

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Craig Powers" <eni...@hal-pc.org> wrote in message news:3E91DD59...@hal-pc.org...

Vi2

unread,
Apr 8, 2003, 1:35:17 AM4/8/03
to
"Alexander Nickolov" <agnic...@mvps.org> wrote in message news:<eLbr0vT$CHA....@TK2MSFTNGP11.phx.gbl>...

> You are violating yet another COM rule - all base interfaces to a
> derived interface you implement must be exposed via QI!
>

If you know where is this COM rule documented, then it will be better
to look at. COM object supports only those interfaces which is
successfully returned by QueryInterface. Not more.

The COM interface is not divided and is not marshalled by part.
Remember that COM is not C++. Some COM language does not give the
access to part of interface (IBase part of IDerived interface).

Before talking about proxy and its strategy, you would test it. :) The
interface with IID_IDispatch is marshalled independent on other
interfaces (for example, with IID_IOne or IID_ITwo as shown below).

I did not would like to explain the ABC of COM. I get the working
example (from your CUSTOM interfaces). I'm sorry if it will be so big.

TANDETNIK.IDL
-------------
//
// Tandetnik.idl : IDL source for Tandetnik.exe
//

import "oaidl.idl";
import "ocidl.idl";

[object,dual,uuid(E8B5273F-6660-11D7-A545-004095426E66),pointer_default(unique)]
interface IOne : IDispatch
{
[id(1)] HRESULT Method1([in] long i);
};

[object,dual,uuid(E8B5273E-6660-11D7-A545-004095426E66),pointer_default(unique)]
interface ITwo : IDispatch
{
[id(2)] HRESULT Method2([in] long i);
};

[object,dual,uuid(E8B5273D-6660-11D7-A545-004095426E66),pointer_default(unique)]
interface IDual : IDispatch
{
[id(11)] HRESULT Method1([in] long i);
[id(22)] HRESULT Method2([in] long i);
};

[uuid(E8B52733-6660-11D7-A545-004095426E66),version(1.0)]
library TANDETNIKLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

interface IOne;
interface ITwo;
interface IDual;

[uuid(E8B52740-6660-11D7-A545-004095426E66)]
coclass x
{
[default] interface IOne;
interface ITwo;
};
};

X.H
---
/////////////////////////////////////////////////////////////////////////////
// Cx


class ATL_NO_VTABLE Cx :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<Cx, &CLSID_x>,

public IDispatchImpl<IOne, &IID_IOne, &LIBID_TANDETNIKLib,1,0>,
public IDispatchImpl<ITwo, &IID_ITwo, &LIBID_TANDETNIKLib,1,0>,
public IDispatchImpl<IDual, &IID_IDual, &LIBID_TANDETNIKLib,1,0>
{
public:
Cx() {}

DECLARE_REGISTRY_RESOURCEID(IDR_X)
DECLARE_NOT_AGGREGATABLE(Cx)
DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(Cx)
COM_INTERFACE_ENTRY_IID(IID_IDispatch,IDual)
COM_INTERFACE_ENTRY(IOne)
COM_INTERFACE_ENTRY(ITwo)
END_COM_MAP()

// IOne
public:
STDMETHOD(Method1)(/*[in]*/ long i);

// ITwo
public:
STDMETHOD(Method2)(/*[in]*/ long i);
};

X.CPP
-----

STDMETHODIMP Cx::Method1(/*[in]*/ long i)
{
char buf[100];
sprintf(buf,"Cx::Method1=%d",i);
MessageBox(NULL, buf, "Error", MB_OK);
return S_OK;
}
// ITwo
STDMETHODIMP Cx::Method2(/*[in]*/ long i)
{
char buf[100];
sprintf(buf,"Cx::Method2=%d",i);
MessageBox(NULL, buf, "Error", MB_OK);
return S_OK;
}

TANDETNIK.VBS
-------------

set obj = CreateObject("Tandetnik.x")
obj.Method1 2002 ' IOne method
obj.Method2 2003 ' ITwo method

PS
The dispinterfaces are converted alike this.

Vi2

unread,
Apr 8, 2003, 2:19:56 AM4/8/03
to
"Igor Tandetnik" <itand...@whenu.com> wrote in message news:<e63gPtR$CHA....@TK2MSFTNGP10.phx.gbl>...

>
> Wrong. The client cannot query for IDispatch, but the client can still
> call IDispatch::Invoke through either IDualx or IDualy (that's the whole
> point of making them dual, right)? Thus, there must be _some_ proxy
> capable of marshaling IDispatch::Invoke call (you know, pack the
> parameters into byte stream, send the stream over to the stub, unpack
> the results from the byte stream sent back from the stub, return them to
> the caller). The point is, proxy manager never sets up two proxies for
> the same interface, so both IDualx and IDualy always use the same proxy
> for marshaling their IDispatch parts. On the server side, this proxy is
> backed by whichever of IDualx and IDualy was first queried for.

Try to get IID_IDispatch interface from "ATL Simple COM object",
removing COM_INTERFACE_ENTRY(IDispatch) from BEGIN_COM_MAP. Or call it
from VBScript Set obj = CreateObject("..."). After this testing it
could return to further discuss.

>
> I'm not sure it is legal in COM to succeed QI for derived interface but
> fail QI for base interface.

In COM there is no relation between QI for derived and based
interfaces. Both derived and based interfaces are only interfaces.
Each of which contains the set of functions.

> I'm not sure how this will work out accross
> apartment boundary. I strongly suspect such technique is in fact illegal
> (in keeping with the rule that a set of interfaces accessible on an
> object must be static over the lifetime of the object, regardless of the
> way an interface is accessed), and that the proxy manager will actually
> successfully satisfy a QI for IDispatch using internal IDispatch proxy
> (backed by IDispatch part of IDualx or IDualy, whichever was queried for
> first). I haven't tested it though.
>

What can you tell about this code (without error handling)?

IUnknown* p = ...; // Some object's pointer
Ix* px; Iy* py;

p->QueryInterface(IID_Ix, (void**) &px);
p->QueryInterface(IID_Iy, (void**) &py);
...
p->Release(); // Destroy px pointer (instead of px->Release();)
p->Release(); // Destroy py pointer (instead of py->Release();)

Because IUnknown part is the same for p, px and py (as marshalling by
ONE IUnknown proxy). Because the implementation of any interfaces's
Release() is the same. :)

PS
I'm sorry for this longer discussion. I want only to remove the "COM
discrimination" from article.

Igor Tandetnik

unread,
Apr 8, 2003, 10:23:10 AM4/8/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> What can you tell about this code (without error handling)?
>
> IUnknown* p = ...; // Some object's pointer
> Ix* px; Iy* py;
>
> p->QueryInterface(IID_Ix, (void**) &px);
> p->QueryInterface(IID_Iy, (void**) &py);
> ...
> p->Release(); // Destroy px pointer (instead of px->Release();)
> p->Release(); // Destroy py pointer (instead of py->Release();)

Now this is definitely illegal, though you will get away with it for
most objects. While the server can and most often does implement
reference counting on a per-object basis, the client is required to
treat reference counting on a per-interface-pointer basis. You must
Release through the safe interface pointer you have AddRef'ed. Certain
advanced implementation techniques, most notably tear-off interfaces,
rely on this.

Igor Tandetnik

unread,
Apr 8, 2003, 11:04:04 AM4/8/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> "Alexander Nickolov" <agnic...@mvps.org> wrote in message
news:<eLbr0vT$CHA....@TK2MSFTNGP11.phx.gbl>...
> > You are violating yet another COM rule - all base interfaces to a
> > derived interface you implement must be exposed via QI!
> >
>
> If you know where is this COM rule documented, then it will be better
> to look at. COM object supports only those interfaces which is
> successfully returned by QueryInterface. Not more.

Easy. QI must be symmetric, reflexive and transitive. See

http://msdn.microsoft.com/library/en-us/com/htm/com_1j8l.asp

<quote>
If you have a pointer to an interface on an object, a call like the
following to QueryInterface for that same interface must succeed
</quote>

Now imagine:

void f(IBase* pBase)
{
IBase* pBaseAgain = 0;
pBase->QueryInterface(IID_IBase, (void**)&pBaseAgain);
if (pBaseAgain) pBaseAgain->Release();
}

IDerived* pDerived = ...; // obtained somehow
f(pDerived); // a legal call

Inside f(), a reflexive QI will fail.

> The COM interface is not divided and is not marshalled by part.
> Remember that COM is not C++. Some COM language does not give the
> access to part of interface (IBase part of IDerived interface).

How come? I can easily call IBase methods through IDerived pointer. I
can pass IDerived pointer wherever IBase pointer is required. That's the
whole point of deriving one interface from another, isn't it? In a
method that accepts IBase, there is no way to tell that it's just a part
of IDerived and not an IBase natively supported by the object. Playing
games like succeeding QI for IDerived but failing it for IBase violates
COM rules and can break clients even with no marshaling, just as f()
above breaks.

Igor Tandetnik

unread,
Apr 8, 2003, 11:28:25 AM4/8/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> Before talking about proxy and its strategy, you would test it. :) The
> interface with IID_IDispatch is marshalled independent on other
> interfaces (for example, with IID_IOne or IID_ITwo as shown below).

You know what - you seem to be right here. I cannot repoduce the
failure, even when running the client and the server on different
machines. I have created setup like this:

[
object, uuid(...), dual
]
interface IOne : IDispatch
{
[id(1)]
HRESULT test1([out, retval] long* pResult);
};

[
object, uuid(...), dual
]
interface ITwo : IDispatch
{
[id(1)]
HRESULT test2([out, retval] long* pResult);
};
[
uuid(...),
version(1.0)
]
library TESTLib


{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(6D9A8C10-6E49-4F8D-9799-C5168FC2FEDA),
helpstring("TestObj Class")
]

coclass TestObj


{
[default] interface IOne;
interface ITwo;
};
};


class ATL_NO_VTABLE CTestObj :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CTestObj, &CLSID_TestObj>,
public IDispatchImpl<IOne, &IID_IOne, &LIBID_TESTLib>,
public IDispatchImpl<ITwo, &IID_ITwo, &LIBID_TESTLib>
{
BEGIN_COM_MAP(CTestObj)
COM_INTERFACE_ENTRY(IOne)
COM_INTERFACE_ENTRY(ITwo)
COM_INTERFACE_ENTRY2(IDispatch, IOne)
END_COM_MAP()

public:
STDMETHODIMP test1(long* pResult)
{
*pResult = 1;
return S_OK;
}
STDMETHODIMP test2(long* pResult)
{
*pResult = 2;
return S_OK;
}
};


The client instantiates the component with CoCreateInstanceEx, then
queries for IOne, ITwo and IDispatch in different orders, and on each
interface calls Invoke(1). The return value determines which method
ended up being called.

No matter the sequence of QIs, I consistently get test1 called through
IOne and IDispatch, and test2 through ITwo. Even if I QI for ITwo first,
and then use ITwo interface to query for IOne and IDispatch. That should
break according to the theory, but it does not appear two.

Running the server on Win2K and the client on Win98.


Alexander, can you please comment on this? I can swear I have seen
things break in this scenario when I was testing it quite some time ago,
but I cannot get them to break now. Is it possible that this was a
problem in some early DCOM implementations and is corrected in recent
runtime version?

I still think that multiple dual interfaces don't make any sense and are
best avoided. They probably violate COM rules, they can lead to
confusion on the client, and they don't serve any useful purpose. But I
can't demonstrate anymore that they definitely absolutely don't work.

Alexander Nickolov

unread,
Apr 8, 2003, 2:36:09 PM4/8/03
to
You should know very well that this is an implementation detail.
In this case the implementation of the proxy and the stub managers.
It's quite possible they have changed it (in SP3 for example) to not
reuse the stub for the base interface for all derived interfaces.
This is _not_ a bugfix, rather an OS-level workaround though.

As to Vi2: an implementation detail does not substitute for legality.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Igor Tandetnik" <itand...@whenu.com> wrote in message news:O8svAOe$CHA....@TK2MSFTNGP10.phx.gbl...

Igor Tandetnik

unread,
Apr 8, 2003, 3:33:51 PM4/8/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> If you know where is this COM rule documented, then it will be better
> to look at. COM object supports only those interfaces which is
> successfully returned by QueryInterface. Not more.

BTW, if you are interested in the MS documentation that explicitly and
clearly says multiple dual interfaces are not legal, see

http://msdn.microsoft.com/library/en-us/wss/wss/_exchsv2k_frequently_asked_questions.asp

"How can CDO objects expose multiple dual interfaces?" section. The most
interesting part is the next to last paragraph, beginning with "You may
wonder why all of this is needed".

Vi2

unread,
Apr 9, 2003, 7:23:50 AM4/9/03
to
"Alexander Nickolov" <agnic...@mvps.org> wrote in message news:<#V8#31f$CHA....@TK2MSFTNGP10.phx.gbl>...

> You should know very well that this is an implementation detail.
> In this case the implementation of the proxy and the stub managers.
> It's quite possible they have changed it (in SP3 for example) to not
> reuse the stub for the base interface for all derived interfaces.

> This is not a bugfix, rather an OS-level workaround though.


>
> As to Vi2: an implementation detail does not substitute for legality.
>

I have read all 1997-2003 discussion about this theme. It seems to me
that nobody know why?!

This question is not related to the proxy. Because there are some
interfaces with like properties. Alike IUnknown with its
"per-interface-pointer notion" (from MSDN). IUnknown-part of any
interface can have the own implementation. Therefore several dual
interfaces are also permitted.

I have tested my example for this purpose and different clients - C++,
VBScript, JScript and VB6 (with these which I have). I do not detect
any problem with marshalling. The marshalling process of IID_IDispatch
and all IID_I*** dual interfaces is correct.

What is a problem then? Basically if the problem exist, then it links
with the VARIANT structure and its VT_DISPATCH for all dual
interfaces.

1. If your object have several dual interfaces and some method which
returns one of them as a result of the method according to some
in-parameter. It can be done by "IDispatch* *result" or "VARIANT*
*result".

In both cases, the REAL IDispatch implemenation will be returned (i.e.
that which can be obtained by QueryInterface(IID_Idispatch)). In first
case, it is made by the proxy immediately. In second case, by
specifacation of VT_DISPATCH of VARIANT's vt and further by the proxy.

Therefore, for all in-parameters such method will be return only ONE
interface (the "main" dispatch). VT_DISPATCH will convert all dual
pointers into single one.

It is neither good nor bad. The IID_IDispatch interface (as COM
interface) can be only in single instance.

2. VB's SET operator and "Object" type.

Because any dual interface has the IDispatch's functions, VB optimizes
the assignment of the objects (named or generic).

Dim o1 As IOne, obj As Object
Set o1 = CreateObject("Tandetnik.x") ' QI(IID_IDispatch) then
QI(IID_IOne)

The "o1" object is stored in VARIANT with VT_DISPATCH because it has
GetIDsOfNames and Invoke.

Set obj = o1 ' no QI(IID_IDispatch) => obj points to IOne
interface

While there is no QueryInterface, the pointer stays on the old
interface and only its methods can be Invoked.

It is also neither good nor bad. If you do not like this, you can
repair through VT_UNKNOWN, which forces VB to make QI.

Dim xxx As IUnknown
Set xxx = o1
Set obj = xxx ' QI(IID_IDispatch) of "main" dispatch

instead of

Set obj = o1 ' no QI(IID_IDispatch)

Vi2

unread,
Apr 9, 2003, 8:25:02 AM4/9/03
to
"Igor Tandetnik" <itand...@whenu.com> wrote in message news:<OmCzZAe$CHA....@TK2MSFTNGP11.phx.gbl>...

>
> Easy. QI must be symmetric, reflexive and transitive. See
>
> http://msdn.microsoft.com/library/en-us/com/htm/com_1j8l.asp
>
> <quote>
> If you have a pointer to an interface on an object, a call like the
> following to QueryInterface for that same interface must succeed
> </quote>

As you can see from QueryInterface description in the section above
"Rules for Implementing QueryInterface"
<quote>
Once you have an initial pointer to an interface on an object, COM has
a very simple mechanism to find out whether the object supports
another specific interface and, if so, to get a pointer to it. (For
information on getting an initial pointer to an interface on an
object, refer to Getting a Pointer to an Object.) This mechanism is
the QueryInterface method of the IUnknown interface.
</quote>
the "interface's pointer" from MSDN article can be obtained only due
to QueryInterface. So the text, quoted by you, means: "if you make the
successful call of QI(IID_IA), then all other calls of QI(IID_IA) must
be successful".

Any argument unrelated to QI cannot be acceptable.

>
> Now imagine:
>
> void f(IBase* pBase)
> {
> IBase* pBaseAgain = 0;
> pBase->QueryInterface(IID_IBase, (void**)&pBaseAgain);
> if (pBaseAgain) pBaseAgain->Release();
> }
>
> IDerived* pDerived = ...; // obtained somehow
> f(pDerived); // a legal call
>
> Inside f(), a reflexive QI will fail.

Inside C++ I can make a lot of such thing. For example,
f((IBase*)NULL)! :))

Your argument is not accepted, because IBase's pointer inside f() is
not obtained through QI. You don't check the
pDerived->QueryInterface(IID_IBase) anywhere. So if such query was
successful, then in f() it will be successful, and vice versa. It is
only your problem if you pass the problematic pointer into the
function with QI.

Also if you passed the pDerived to the remote object through method
with parameter "IBase*", then the proxy will make the check for you
and the QueryInterface(IID_Ibase) instead of you.

>
> How come? I can easily call IBase methods through IDerived pointer. I
> can pass IDerived pointer wherever IBase pointer is required. That's the
> whole point of deriving one interface from another, isn't it? In a
> method that accepts IBase, there is no way to tell that it's just a part
> of IDerived and not an IBase natively supported by the object. Playing
> games like succeeding QI for IDerived but failing it for IBase violates
> COM rules and can break clients even with no marshaling, just as f() above
> breaks.

In C++ it is OK, but in other language it is not OK.

I repeat again that COM interface is not C++ class. There is no
"derived" or "based" relation on COM interface because these
interfaces can be implemented by different ways.

There is a little difference between "I have a pointer to this
interface" and "I know how to call the functions of this interface".

"I have a pointer to this interface" means that I have retrieved this
pointer by QueryInterface with suitable IID.

"I know how to call the functions of this interface" means that if I
have the vtable, then I will be able to call some methods, published
by this interface.

Igor Tandetnik

unread,
Apr 9, 2003, 1:13:48 PM4/9/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> 1. If your object have several dual interfaces and some method which
> returns one of them as a result of the method according to some
> in-parameter. It can be done by "IDispatch* *result" or "VARIANT*
> *result".
>
> In both cases, the REAL IDispatch implemenation will be returned (i.e.
> that which can be obtained by QueryInterface(IID_Idispatch)). In first
> case, it is made by the proxy immediately. In second case, by
> specifacation of VT_DISPATCH of VARIANT's vt and further by the proxy.
>
> Therefore, for all in-parameters such method will be return only ONE
> interface (the "main" dispatch). VT_DISPATCH will convert all dual
> pointers into single one.

Let me see if I understand you correctly. Suppose I implement a COM
object with two dual interfaces, IOne and ITwo, and QI for IDispatch is
backed by IOne. Some method on the same or other object returns ITwo via
[out, retval] IDispach** parameter, like this:

HRESULT method(IDispatch** ppResult)
{
ITwo* pTwo = ...; // obtained somehow
(*ppResult = pTwo)->AddRef();
pTwo->Release();
return S_OK;
}

The client calls this method and immediately turns around and calls
Invoke through the returned pointer. If the client is in the same
apartment, Invoke call goes to ITwo (the client calls straight through
the vtable I've just handed out). But if the client is across apartment
boundary, Invoke call goes to IOne because the stub (or proxy) QI'ed me
for IDispatch when marshaling the pointer. If this analysis is correct,
then one of the following must be true:

1. COM rules are not violated in this scenario, but they are simply
inadequate to ensure location transparency. If this is the case, the
rules need to be amended by some, maybe unofficial, guideline. Such as
"don't implement multiple dual interfaces", or more generally, "don't
have two implementations of the same interface that behave differently".
Except for IUnknown, since it is treated as a special case by marshaling
layer (AddRef and Release are not even marshalable).

2. I violate COM rules by implementing two dual interfaces

3. I violate COM rules by upcasting from ITwo to IDispatch without an
explicit QI. If this is the case, then I can never return ITwo pointer
to a client that expects IDispatch, thus it is pointless to make ITwo
dual in the first place.

4. COM rules are violated in some other way - please explain.

It appears that items 1 through 3 above imply that multiple dual
interfaces are illegal, pointless, or both. Am I missing something
obvious? In your opinion, which of the four cases holds true?

> 2. VB's SET operator and "Object" type.
>
> Because any dual interface has the IDispatch's functions, VB optimizes
> the assignment of the objects (named or generic).
>
> Dim o1 As IOne, obj As Object
> Set o1 = CreateObject("Tandetnik.x") ' QI(IID_IDispatch) then
> QI(IID_IOne)
>
> The "o1" object is stored in VARIANT with VT_DISPATCH because it has
> GetIDsOfNames and Invoke.
>
> Set obj = o1 ' no QI(IID_IDispatch) => obj points to IOne
> interface
>
> While there is no QueryInterface, the pointer stays on the old
> interface and only its methods can be Invoked.

Aha. So you are saying that VB upcasts a dual interface to IDispatch
without explicit QI. In one of your other posts, you claimed that it is
illegal, that an interface pointer must always be obtained via QI.
Suppose I have object X that implements a dual interface IOne but fails
QI for IDispatch (you say this is legal). There is another object Y with
method test(IDispatch* pDisp). Inside test(), object Y queries pDisp for
IDispatch and crashes if this QI fails (it should never crash because QI
is required to be reflexive). Everything is in the same apartment, there
is no marshaling. Consider this code:

Dim x As IOne
Dim obj as Object
Dim y as Y

Set x = new X ' strongly typed, no QI for IDispatch
Set obj = x ' you say no QI for IDispatch either, direct upcast

Set y = new Y
y.test(obj) ' will it crash here?

In this scenario, one of the following must be true:

1. Set obj = x fails, because VB does a QI for IDispatch after all

2. y.test(obj) fails before even invoking the method, because VB does a
QI for IDispatch at this point to make sure obj holds a legal IDispatch
pointer (sounds unlikely)

3. y.test(obj) crashes because VB violated COM rules when upcasting from
IOne to IDispatch without a QI

4. y.test(obj) crashes because object X violated COM rules by succeeding
QI for IOne but failing it for IDispatch

5. Something else happens - please explain

In your opinion, which of the five cases holds true?

Alexander Nickolov

unread,
Apr 9, 2003, 6:14:25 PM4/9/03
to
The COM specification is much more reminiscent of a graduation
thesis paper than a formal spec, but that's what we have...
Below I include several excerpts relevant to the discussion
at hand.


Chapter 2 / Component Object Model Technical Overview / Objects and
Interfaces
=======================================
Interfaces and Inheritance

COM separates class hierarchy (or indeed any other implementation
technology) from interface hierarchy and both of those from any
implementation hierarchy. Therefore, interface inheritance is only
applied to reuse the definition of the contract associated with
the base interface. There is no selective inheritance in COM: if
one interface inherits from another, it includes all the functions
that the other interface defines, for the same reason than an object
must implement all interface functions it inherits.

Inheritance is used sparingly in the COM interfaces. Most of the
pre-defined interfaces inherit directly from IUnknown (to receive
the fundamental functions like QueryInterface), rather than inheriting
from another interface to add more functionality. Because COM interfaces
are inherited from IUnknown, they tend to be small and distinct from
one another. This keeps functionality in separate groups that can be
independently updated from the other interfaces, and can be recombined
with other interfaces in semantically useful ways.

In addition, interfaces only use single inheritance, never multiple
inheritance, to obtain functions from a base interface. Providing
otherwise would significantly complicate the interface method call
sequence, which is just an indirect function call, and, further,
the utility of multiple inheritance is subsumed within the capabilities
provided by QueryInterface.
=======================================

This is everything the COM spec has to say about interface derivation.
(Don't do it about sums it up...)


Chapter 3 / Interfaces / The Interface Binary Standard
=======================================
An interface implementor is free to use the memory before and beyond
the "as-specified-by-the-standard" vtbl for whatever purpose he may wish;
others cannot assume anything about such memory.
=======================================

This is an obscure way of saying the vtable can contain more entries
than the interface contract requires, and that the object may store
any data after the vtable pointer. In other words the C++ compiler
may do its regular job and you are fine.


Chapter 3 / The IUnknown Interface
=======================================
Any single object usually only requires a single implementation of the
IUnknown member functions. This means that by virtue of implementing any
interface on an object you completely implement the IUnknown functions.
You do not generally need to explicitly inherit from nor implement
IUnknown as its own interface: when queried for it, simply typecast
another interface pointer into an IUnknown* which is entirely legal
with polymorphism.
=======================================

The reason for including this excerpt is to illustrate how formally
incomplete the spec is... It implies that we should use the term
polymorphism as it stands in C++ towards interfaces, meaning at last
that a derived interface is polymorphic to its base. E.g. you can use
a derived interface anywhere a pointer to its base interface is
needed. Note the spec doesn't say it with an explicit statement,
but COM would be unusable otherwise - you need to be able to access
the IUnknown::QueryInterface method from any interface. The closest
the spec has to say is from the first excerpt: "Therefore, interface
inheritance is only applied to reuse the definition of the contract
associated with the base interface." This is another indirect way of
saying an interface upcast is acceptable in place of QI for the base
interface.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Igor Tandetnik" <itand...@whenu.com> wrote in message

news:ObIRktr$CHA....@TK2MSFTNGP12.phx.gbl...

Alexander Nickolov

unread,
Apr 9, 2003, 6:36:31 PM4/9/03
to
Ah, forgot to include the corollary: if you implement a derived interface,
you must expose all of its base interfaces via IUnknown::QueryInterface.
This follows from the symmetry rule: once you get a hold of any interface
on the object, you must be able to QI on that interface for itself. Getting
ahold of a derived interface also gives you all of its base interfaces, so
QI for any of the base interfaces must succeed.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Alexander Nickolov" <agnic...@mvps.org> wrote in message
news:uLDdgUu$CHA....@TK2MSFTNGP10.phx.gbl...

Igor Tandetnik

unread,
Apr 9, 2003, 7:05:00 PM4/9/03
to
"Alexander Nickolov" <agnic...@mvps.org> wrote in message
news:erks2gu$CHA....@TK2MSFTNGP12.phx.gbl...

> Ah, forgot to include the corollary: if you implement a derived
interface,
> you must expose all of its base interfaces via
IUnknown::QueryInterface.
> This follows from the symmetry rule: once you get a hold of any
interface
> on the object, you must be able to QI on that interface for itself.

That's reflexivity. Symmetry is that if you can get IA from IB, you must
be able to get IB from IA.

Vi2

unread,
Apr 10, 2003, 2:38:14 AM4/10/03
to
"Igor Tandetnik" <itand...@whenu.com> wrote in message news:<ObIRktr$CHA....@TK2MSFTNGP12.phx.gbl>...

I think that there is the items 3. You should not use the C++ casting
instead of QI. You break the COM rule by yourself - "there is only one
way to get the interface pointer: QueryInterface with its unique IID".
The "method" must return the IDispatch interface, therefore it is
IID_IDispatch, not IID_IBase that you should "obtain somehow" and pass
outside.

I agree with you that "multiple dual interfaces are /*illegal*/,
pointless, /*or both*/". But if I in my object must implement the
outer interface which published by @#$#@, I cannot tell that it is
"pointless".

VB select the item N2. I'm too. COM is also. This "y.test(obj)" call
cannot be even done, rather, cannot begin to try to be done.

In my opinion, in the same apartment (i.e. in your code LOCALLY) you
can make all what is in your mind and your hands. You can cast from
IDerived* to IBase* (and vice versa) and call both IBase and IDerived
methods on these pointers (but in fact it is the same pointer!). But
you should be aware of that you do not leave the bounds (or semantics)
of IDerived interface. So you cannot tell "I have the IBase pointer of
the object". It's an illusion. The vtable of IBase interface can be
placed elsewhere. But it does not mean that you cannot call the
IBase's methods through IDerived pointer.

Igor Tandetnik

unread,
Apr 10, 2003, 11:40:32 AM4/10/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03040...@posting.google.com...
> "Igor Tandetnik" <itand...@whenu.com> wrote in message
news:<ObIRktr$CHA....@TK2MSFTNGP12.phx.gbl>...
> > 3. I violate COM rules by upcasting from ITwo to IDispatch without
an
> > explicit QI. If this is the case, then I can never return ITwo
pointer
> > to a client that expects IDispatch, thus it is pointless to make
ITwo
> > dual in the first place.
>
> I think that there is the items 3. You should not use the C++ casting
> instead of QI. You break the COM rule by yourself - "there is only one
> way to get the interface pointer: QueryInterface with its unique IID".

I believe you choose to interpret the rules too narrowly. COM spec never
says "QI is the only way to get an interface pointer". What about
CoCreateInstance or CoGetClassObject? What about other API functions
that return interface pointers? What about interface methods that return
pointers as [out] parameters? I don't know how those have been obtained,
if QI was involved or not. Are such pointers exempt from COM rules? Are
they automatically suspect? Do I have to QI reflexively every time I
obtain such a pointer, just to confirm that the pointer is good? Ok,
reflexivity is easy to check, but how do I check symmetry and
transitivity? Programming COM under such assumptions would be a
nightmare, I can't believe it is the intention of COM spec to require
this.

Alexander makes a convincing case that COM spec allows interface
polymorphism, in spirit if not in letter. As we'll see shortly, there is
at least one widely used commercial client (VB) that performs upcasts
without QI (and this can be detected, I have a repro case). We can
assume that upcasts are an accepted practice, that they are here to
stay. I don't really care much that COM spec is ambiguous or silent on
the subject. Clients use upcasts, so I as a component writer have to
support upcasts in a way that satisfies all explicitly stated COM rules
(which real clients also rely on), or my component will have
interoperability problems. At the end of the day, it is the bottom line
that counts: if my component does not follow COM rules, whether official
or just widely accepted in practice, it can't be sold and I cannot put
food on the table. And I have this nasty expensive habit of eating three
times a day, which I can't rid of no matter how hard I try.

If you still insist on your narrow interpretation, I guess we'll just
have to agree to disagree.

> I agree with you that "multiple dual interfaces are /*illegal*/,
> pointless, /*or both*/". But if I in my object must implement the
> outer interface which published by @#$#@, I cannot tell that it is
> "pointless".

You'll have to implement this interface on a separate object, or make it
the only dual interface on your object, or choose a different software
vendor other than @#$#@ - vote against her bad design with your wallet,
or your clients will vote against yours. Implementing two dual
interfaces on the same object violates COM rules and/or existing
practice.

> > In this scenario, one of the following must be true:
> >
> > 1. Set obj = x fails, because VB does a QI for IDispatch after all
> >
> > 2. y.test(obj) fails before even invoking the method, because VB
does a
> > QI for IDispatch at this point to make sure obj holds a legal
IDispatch
> > pointer (sounds unlikely)
> >
> > 3. y.test(obj) crashes because VB violated COM rules when upcasting
from
> > IOne to IDispatch without a QI
> >
> > 4. y.test(obj) crashes because object X violated COM rules by
succeeding
> > QI for IOne but failing it for IDispatch
> >
> > 5. Something else happens - please explain
> >
> > In your opinion, which of the five cases holds true?
>
> VB select the item N2. I'm too. COM is also. This "y.test(obj)" call
> cannot be even done, rather, cannot begin to try to be done.

Wrong. I've actually tested it. y.test(obj) gets called, and reflexivity
rule violation is detected. I can publish or e-mail you the test case if
you wish, it is very small and simple.

Thus, it is either 3 or 4 - either VB is wrong to upcast, or X is wrong
to fail QI for IDispatch. As I stated earlier, I don't care what COM
spec says about whether VB is allowed to upcast (though I tend to think
it is legal). VB does it, and so do many other clients. So to ensure
interoperability, I must adopt a rule: whenever QI for a derived
interface succeeds, QI for all base interfaces must also succeed. It may
be a corollary of the official COM rules (I believe it is), or it may be
something I just impose on myself to stay successful in the market -
does not make much difference to me.

> In my opinion, in the same apartment (i.e. in your code LOCALLY) you
> can make all what is in your mind and your hands.

"In the same apartment" and "in my code locally" are two entirely
different things. My component and VB runtime may live in the same
apartment, but if I hope to interoperate, I must follow rules that VB
assumes to hold. Inside my C++ code, I can indeed play dirty tricks, but
they _must not_ be observable by the client making "legal" COM calls
(within an expanded definition of "legal", meaning techniques mandated
by COM spec and/or widely accepted in practice, seeing as COM spec is
not particularly straightforward, explicit nor unambiguous).

Once you follow this guideline, it should not matter whether the client
is in the same apartment or on a different machine. That's the whole
point of location transparency. If your component breaks location
transparency (the same legal client code behaves differently when in the
same apartment than when across apartment boundary), it must have broken
some rules, official or otherwise.

> You can cast from IDerived* to IBase*

Not just me - the client can "legally" do that.

> (and vice versa)

But the client cannot legally cast the other way round. Upcasts are
fine, downcasts can only "legally" be done with QI (in all senses of
"legal").

> and call both IBase and IDerived
> methods on these pointers (but in fact it is the same pointer!). But
> you should be aware of that you do not leave the bounds (or semantics)
> of IDerived interface. So you cannot tell "I have the IBase pointer of
> the object". It's an illusion.

Is it? Suppose I have a dual interface, IDual, and a pointer to it,
pDual. Now, COM is a binary specification, IDispatch interface pointer
is defined as follows: it is a pointer to a 32-bit value which is in
turn a pointer to a vtable. This vtable is an array of 7 (seven) 32-bit
values, each being a pointer to a function. Those functions must accept
stack frames built according to the definition of corresponding
IDispatch methods, and implement behavior consistent with general
semantics documented for IDispatch methods.

Given this definition, how exactly is pDual _not_ a valid IDispatch
pointer? It appears to satisfy all the requirements. Am I missing a
requirement that would disqualify pDual as an IDispatch pointer? What
would that be? Otherwise, why cannot I use pDual anywhere IDispatch* is
required, including passing it to other methods and returning it as
[out]IDispatch** parameter?

This question is mostly of academic interest though - VB has already
made the decision for us by performing upcasts without QI. Now any
component that exposes multiple dual interfaces can break location
transparency when executed by VB client. Bottom line: avoid multiple
dual interfaces.

Alexander Nickolov

unread,
Apr 10, 2003, 8:12:47 PM4/10/03
to
There's another argument going for the single dual interface per
object - the Automation spec itself (or, since there's no official
published spec available - the Automation documentation). In
Automation, an object is defined exposing a single Automation
interface - a set of properties and methods commonly known as
a dispinterface. And a dual interface is defined as a COM
interface deriving from IDispatch that exposes its methods
through IDispatch::Invoke and the methods available through
IDispatch::Invoke are exposed via the interface's vtable. That
means all dual interfaces implemented on an Automation
object must share the same dispinterface definition - the
set of properties and methods of the object - its Automation
definition. The dual interfaces then have to expose the
exact same set of properties/methods which isn't very
practical. It's actually not true that an object cannot implement
multiple dual interfces :) - an object is allowed to expose
up to n! different dual interfaces as long as they implement
the exact same set of properties/methods, but their vtable
layouts vary (where n is the total count of methods and property
accessors on the one and only dispinterface). For all practical
purposes though, this translates into multiple dual interfaces
are not allowed.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Igor Tandetnik" <itand...@whenu.com> wrote in message
news:ecTeGe3$CHA....@TK2MSFTNGP11.phx.gbl...

Vi2

unread,
Apr 11, 2003, 6:58:37 AM4/11/03
to
"Igor Tandetnik" <itand...@whenu.com> wrote in message news:<ecTeGe3$CHA....@TK2MSFTNGP11.phx.gbl>...

> >
> > VB select the item N2. I'm too. COM is also. This "y.test(obj)" call
> > cannot be even done, rather, cannot begin to try to be done.
>
> Wrong. I've actually tested it. y.test(obj) gets called, and reflexivity
> rule violation is detected. I can publish or e-mail you the test case if
> you wish, it is very small and simple.

Sorry, it's realy I was wrong. "Y" was placed in EXE module.

> ...


>
> This question is mostly of academic interest though - VB has already
> made the decision for us by performing upcasts without QI. Now any
> component that exposes multiple dual interfaces can break location
> transparency when executed by VB client. Bottom line: avoid multiple
> dual interfaces.

If you are addressing to VB's authority, then I give you the example
when VB itself tells: use multiple dual interfaces.

It's because each class in VB is the implementation of the dual
interface. And each class can Implements any interface. So the class
"Class3" having the statements "Implements Class1" and "Implements
Class2" have also 3 dual interfaces into the typelibrary. The "main"
dispatch will be the Class3 interface.

I do not know how to say about Caesar, but if MS VB can do it, then I
can do it too.

I think also that our discussion becames academic. I have too few
English words to carry on this polemic. If you wish lead it on
Russian, it can be continued. www.rsdn.ru is a place to meeting or by
private mail.

In conclusion only I can recommend to you to examine the aggregation
of 2 objects when each of them have a dual interface.

Also take a look in MSDN "Platform SDK: COM+ (Component Services)"
<QOUTE>
Client-Side Architecture
...
...The following rules are used when the Queued Components service
receives a QueryInterface for IDispatch:

1. If the default interface inherits from IDispatch, the default
interface is selected.

2. If no interface is marked as default but only one interface
inherits from IDispatch, that interface is selected.

3. If no interface is marked as default and multiple interfaces
inherit from IDispatch, the Queued Components recorder returns a NULL
interface pointer and E_NOINTERFACE error code. The Queued Components
recorder does not attempt to guess which interface should be selected.
</QOUTE>

I think that you will agree with me that there are only 3 strategies
to an object with multiple duals to implement the IDispatch interface:
1. reject the QueryInterface(IID_IDispatch) - no IDispatch at all
2. return a pointer to some partial dual interface's implementation -
IDispatch from IOne from your, Igor, example
3. make the full implementation of IDispatch - IDispatch from IDual
from my example.

All 3 strategies have a life. All of them are valid.

Nravitca li eto vam ili net. Sorry.

Igor Tandetnik

unread,
Apr 11, 2003, 10:24:29 AM4/11/03
to
"Vi2" <shar...@hotmail.com> wrote in message
news:e8f1a8be.03041...@posting.google.com...

> If you are addressing to VB's authority, then I give you the example
> when VB itself tells: use multiple dual interfaces.

It's not an authority argument, it's a practical consideration. VB is
one of the most popular automation clients and ActiveX control hosts. As
long as my component exposes automation-compatible interfaces, somebody
would want to use it from VB sooner or later. If I don't make my
components play nice with VB, I considerably reduce their marketability.

Besides, I do believe upcasts are legal in COM, and if you assume that
upcasts are formally allowed, then multiple dual interfaces are formally
disallowed. I believe we have established that the two are mutually
exclusive as it is impossible to ensure location transparency in the
presence of both. Programming COM without upcasts would be so darn
inconvenient that I'd much rather sacrifice multiple dual interfaces
(not to mention the fact that everybody is already using upcasts anyway,
they are a well-established practice).

Be honest: do you really, really always QI to get from derived interface
to base interface? If so, then you must have much higher power of
concentration than an average programmer, me included. The compiler is
of no help - it silently accepts a pointer to derived interface in place
of a pointer to base interface pretty much anywhere.

> It's because each class in VB is the implementation of the dual
> interface. And each class can Implements any interface. So the class
> "Class3" having the statements "Implements Class1" and "Implements
> Class2" have also 3 dual interfaces into the typelibrary. The "main"
> dispatch will be the Class3 interface.

What can I say? VB is nice as a client, but it sucks as a tool in which
to develop COM servers. The feature you mention kinda invites you to
produce broken components. However, you are by no means forced to do
that. VB can implement any automation-compatible interface, including
custom, it's just that it cannot (AFAIK) produce a type library with
anything but dual interfaces. You can always define your custom
[oleautomation] interface in an IDL file, compile it with MIDL to
produce a type library, reference it in VB project, and happily
implement your interface on a component written in VB.

Any tool can be misused. The fact that you _can_ produce components in
VB that implement multiple dual interfaces is no reason to actually _do_
so. You can produce such components in C++ too, but the whole point of
this discussion is that you should not.

> I do not know how to say about Caesar, but if MS VB can do it, then I
> can do it too.

I'm not sure which proverb you are referring to - maybe "when in Rome,
do as the Romans do"? Somehow, "Quod licet Jovi non licet bovi" comes to
my mind. Anyway, I have not seen any component produced by MS in VB that
would expose multiple dual interfaces - have you? Again, just because
you can produce broken components in VB is no reason to do so IMHO.

> I think also that our discussion becames academic. I have too few
> English words to carry on this polemic. If you wish lead it on
> Russian, it can be continued. www.rsdn.ru is a place to meeting or by
> private mail.

You are welcome to e-mail me privately, in Russian if you prefer.

> In conclusion only I can recommend to you to examine the aggregation
> of 2 objects when each of them have a dual interface.

Making an automation server aggregatable is just as misguided as making
dual an interface that is intended to be reusable. It is not illegal by
itself, but it creates considerable problems for whoever tries to
actually aggregate it. Aggregation is way overrated - there is seldom
any reason to make a component aggregatable, except in very special
cases (like providing system-level services a la free-threaded
marshaler) where automation does not make much sense.

> Also take a look in MSDN "Platform SDK: COM+ (Component Services)"
> <QOUTE>
> Client-Side Architecture
> ...
> ...The following rules are used when the Queued Components service
> receives a QueryInterface for IDispatch:
>
> 1. If the default interface inherits from IDispatch, the default
> interface is selected.
>
> 2. If no interface is marked as default but only one interface
> inherits from IDispatch, that interface is selected.
>
> 3. If no interface is marked as default and multiple interfaces
> inherit from IDispatch, the Queued Components recorder returns a NULL
> interface pointer and E_NOINTERFACE error code. The Queued Components
> recorder does not attempt to guess which interface should be selected.
> </QOUTE>

I wonder why it leaves another possibility not covered - what if there
is a default interface but it does not inherit from IDispatch? BTW, how
does COM+ even know which interfaces I implement? Reads them from the
type library's coclass statement? Does it reject QI's for interfaces
that I do implement but neglect to mention in the type library? I must
admit I know next to nothing about COM+.

> I think that you will agree with me that there are only 3 strategies
> to an object with multiple duals to implement the IDispatch interface:
> 1. reject the QueryInterface(IID_IDispatch) - no IDispatch at all

Illegal - detectably violates QI reflexivity rule (assuming upcasts are
legal).

> 2. return a pointer to some partial dual interface's implementation -
> IDispatch from IOne from your, Igor, example

Illegal - detectably breaks location transparency (assuming upcasts are
legal).

> 3. make the full implementation of IDispatch - IDispatch from IDual
> from my example.

Yes, that's legal. That's what I'm saying all along - you can have
multiple IDispatch implementations as long as they all behave exactly
the same. But then, if you have to write a hand-rolled IDispatch
implementation anyway, what's the point making the interfaces dual in
the first place?

> All 3 strategies have a life. All of them are valid.

No, only the third is valid. First two violate COM rules one way or
another.

Brian Muth

unread,
Apr 11, 2003, 5:07:31 PM4/11/03
to
Hey, don't switch to Russian. Some of us (or is it just me) are following
this with interest.


0 new messages