Is it possible to get a dual interface (not IDispatch!), passed as a
parameter to Invoke, marshaled correctly?
I've got a source dispinterface with a method defined like this:
[id(1), helpstring("method notify")] HRESULT notify([in] IMySender
*sender);
where IMySender is:
[
object,
uuid(...),
dual,
nonextensible,
pointer_default(unique)
]
interface IMySender : IDispatch
{
//...
}
So in the CP implementation I've got a code like this:
HRESULT Fire_Notify(int id, IMySender *sender)
{
//iterate through connections...
...
CComVariant avarParams[1];
avarParams[0] = sender;
avarParams[0].vt = VT_DISPATCH;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(id, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
...
}
However, this doesn't work when the event is fired into another
apartment: IMySender is not marshaled well (only IDispatch part of it
is actually marshaled), so that any call to its methods crashes.
"Dirty" workaround is to perform explicit QI:
void onNotify(IMySender *sender)
{
IDispatch *disp = sender;
CComQIPtr<IMySender> goodSender(disp);
goodSender->...;
}
My question is whether it's possible to pass "typed" interface
correctly in such situation?
No. All parameters to IDispatch::Invoke are packed into VARIANTs, and a VARIANT only knows about IUnknown and IDispatch. Of course, the server being called can then QI the pointer it receives for any interface it wants.
> I've got a source dispinterface with a method defined like this:
> [id(1), helpstring("method notify")] HRESULT notify([in] IMySender
> *sender);
Don't you get a MIDL warning for this?
--
With best wishes,
Igor Tandetnik
With sufficient thrust, pigs fly just fine. However, this is not necessarily a good idea. It is hard to be sure where they are going to land, and it could be dangerous sitting under them as they fly overhead. -- RFC 1925
So the only way to get it working correctly is to eleminate the need
for marshaling, i.e. to make the inproc server "both", right?
> Don't you get a MIDL warning for this?
No!
I totally missed this issue, and all my event interfaces are defined
with "typed" parameteres... The classes are registered as "apartment",
and since 99% of clients are also STA (incl. scripts and .NET), the
issue just never rose :(
This may eliminate some, but not all, scenarios where marshaling is involved.
It's a responsibility of IDispatch implementation of IMySender
interface. QI should be called during performing of call of "notify"
method. For example, ATL IDispEventSimpleImpl and IDispatchImpl do
this, but MFC doesn't.
>
> Don't you get a MIDL warning for this?
MIDL haven't to warn about this, because IMySender is a good interface.
I'm not quite sure I got the idea.
IMySender is an *argument* passed to notify(), so how IDispatch
implementation of IMySender can handle it? Or did you mean that
IDispatch of the client sink must call QI - but how can it know what
QI it needs? Could you please elaborate your idea a bit more?
> For example, ATL IDispEventSimpleImpl and IDispatchImpl do this, but MFC doesn't.
All the COM classes in my project are based on ATL framework, no MFC.
Are you sure about IDispEventSimpleImpl? I don't quite see how it can pull that off, since it doesn't use the type library. IDispEventImpl should indeed be able to do that.
Yes, that's what Vi2 meant.
> - but how can it know what QI it needs?
The same way it knows that, when IDispatch::Invoke is called with a particular DISPID, it should call onNotify method (which involves unpacking VARIANTs and pushing appropriate parameters onto the stack in the right order). For example, by reading the type library.
How do you implement the sink on the client side? Do you use ATL, MFC, something else? Could you show some code?
Aha, ok, that makes sense.
> The same way it knows that, when IDispatch::Invoke is called with a particular DISPID, it should call onNotify method (which involves unpacking VARIANTs and pushing appropriate parameters onto the stack in the right order). For example, by reading the type library.
>
> How do you implement the sink on the client side? Do you use ATL, MFC, something else? Could you show some code?
Well, usually the sinks are implemented by our clients, who use our
COM-based SDK, but I believe we can supply some auxiliary classes -
like a base class that they would inherit for their sinks. Currently
the sinks look like this:
class ATL_NO_VTABLE CServerSink :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CServerSink, &CLSID_ServerSink>,
public IDispatchImpl<IServerSink, &IID_IServerSink, &LIBID_SinkLib,
1, 0>,
public IDispEventImpl<1, CServerSink, & __uuidof
(SDKLib::_IServerEvents), &__uuidof(SDKLib::__SDKLib), 1, 0>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_SERVERSINK)
BEGIN_COM_MAP(CServerSink)
COM_INTERFACE_ENTRY(IServerSink)
COM_INTERFACE_ENTRY_IID(__uuidof(SDKLib::_IServerEvents), IDispatch)
END_COM_MAP()
BEGIN_SINK_MAP(CServerSink)
SINK_ENTRY_EX(1, __uuidof(ClientCoreCOMLib::_IServerEvents), 1,
connectResponded)
END_SINK_MAP()
HRESULT __stdcall connectResponded(SDKLib::IServer *sender);
};
As far as I see, the call goes through IDispEventSimpleImpl::Invoke()
--> IDispEventSimpleImpl::InvokeFromFuncInfo() --> DispCallFunc()
I can't see any argument type analysis there (or maybe it's done in
DispCallFunc(), implemented in a library?), so Vi2's assumption seems
to be wrong.
So IIUC, I have to implement my own IDispEventImpl2, which would parse
the TLB and perform QI for every relevant argument?
Don't put event sink into your interface map. IDispEventImpl provides its own IUnknown implementation separate from that of its derived class (in fact, the class derived from IDispEventImpl doesn't need to be a COM object in the first place).
> END_COM_MAP()
>
> BEGIN_SINK_MAP(CServerSink)
> SINK_ENTRY_EX(1, __uuidof(ClientCoreCOMLib::_IServerEvents), 1,
> connectResponded)
> END_SINK_MAP()
>
> HRESULT __stdcall connectResponded(SDKLib::IServer *sender);
> };
>
> As far as I see, the call goes through IDispEventSimpleImpl::Invoke()
> --> IDispEventSimpleImpl::InvokeFromFuncInfo() --> DispCallFunc()
Yes, now that I look at the source, I don't see where an automatic QI could possibly happen, either. It looks like I was right after all.
I believe automatic QI does happen with IDispatchImpl (which is based on ITypeInfo::Invoke), but not with IDispEvent[Simple]Impl.
> So IIUC, I have to implement my own IDispEventImpl2, which would parse
> the TLB and perform QI for every relevant argument?
That's the easy part. The hard part is building an appropriate stack frame for the method call. Unless you are thoroughly familiar with assembly language, you'll probably have a real difficult time with that. If it were easy, ATL authors would have done it.
You can hand-craft an IDispatch implementation specific to your particular sink interface. That would be much easier, though rather tedious.
Yes, I have a mistake. IDispEventImpl does instead of
IDispEventSimpleImpl. Sorry. But both IDispEventImpl and
IDispEventSimpleImpl can lead to problems because they use a macro
which doesn't check prototypes.
Could you show some declaration of
ClientCoreCOMLib::_IServerEvents::connectResponded and
SDKLib::_IServerEvents::connectResponded? I think that they have an
IDispatch parameter.
ClientCoreCOMLib::_IServerEvents and SDKLib::_IServerEvents - are the
same interface, I just tried to rename all the occurences of my real
namespaces for the purpose of the above exaple, but failed :).
> I think that they have an IDispatch parameter.
No they don't. All the parameters are of a correct type:
inline HRESULT _IServerEvents::connectResponded ( struct IServer *
sender);
IDispEventImpl just doesn't do any QI, you can see this in its source
file (at least in VC9.0).