
| Last Modification: April 01, 2003 |
HOWTO: Obtain data from the client via an outgoing event dispinterface in ATL
The first step as usual is to define the source interface. Let's say it is a
dispinterface having a method like the following:
[id(1), helpstring("Do you wish to continue?")]
void Continue([in, out] VARIANT_BOOL *pvntbContinue);
The important point is that the argument has the [in, out] attributes. Then as usual you process the IDL to get the type library and use the ATL's wizard to add a connection point to your source class. The code for this method generated by the wizard will look something like this:
VOID Fire_Continue(VARIANT_BOOL *pvntbContinue)
{
T* pT = static_cast<T*>(this);
int nConnectionIndex;
CComVariant* pvars = new CComVariant[1];
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0;
nConnectionIndex < nConnections;
nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
pvars[0] = pvntbContinue;
DISPPARAMS disp = { pvars, NULL, 1, 0 };
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &disp, NULL, NULL, NULL);
}
}
delete[] pvars;
}
The big problem in this code is the following line:
pvars[0] = pvntbContinue;
In order to return a value via an [in, out] argument from an IDispatch::Invoke call, the argument should be passed in a VARIANTARG (the same as VARIANT) containing VT_BYREF. However, ATL's CComVariant doesn't support VT_BYREF altogether. Simply put, this code won't work.
The solution is simple - either hack VT_BYREF into the existing CComVariant manually, or not use CComVariant and use a raw VARIANT instead. Before we go to the code, there's one more problem to discuss. It is always possible that multiple sinks can be attached to the same connection point. Therefore, even the fixed code will only return the result from the last sink and completely discard the results from the other sinks. The choice of a last sink is arbitrary - there's no order among the sinks, but the order they are registered with the connection point. So the code also shows how to sensibly interpret the result from multiple sinks. The semantics for this event is that any "False" returned from any sink means the server should stop. For other situations, the semantics may be different, hence the driving loop logic will be different. Now, on to the fixed code:
VOID Fire_Continue(VARIANT_BOOL *pvntbContinue)
{
T* pT = static_cast<T*>(this);
int nConnectionIndex;
VARIANT vntArg;
int nConnections = m_vec.GetSize();
VARIANT_BOOL vntbContinue = VARIANT_TRUE;
::VariantInit(&vntArg);
V_VT(&vntArg) = VT_BYREF | VT_BOOL;
V_BOOLREF(&vntArg) = &vntbContinue;
for (nConnectionIndex = 0;
vntbContinue && (nConnectionIndex < nConnections);
nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
DISPPARAMS disp = { &vntArg, NULL, 1, 0 };
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &disp, NULL, NULL, NULL);
}
}
*pvntbContinue = vntbContinue;
::VariantClear(&vntArg);
}