|
|
//
// IConnectionPoint/IDispatch helper functions
//
#include "priv.h"
#include <shlobj.h>
//
// IDispatch helper functions
//
// Takes a variable number of parameters for IDispatch, packages
// them up.
//
// pdispparams - The DISPPARAMS structure that receives the result
// of the packaging.
//
// rgvarg - Array of length cArgs.
// It will be used to hold the parameters.
//
// cArgs - Number of pairs of generic arguments.
//
// ap - va_list of parameters to package. We package up the
// first (2 * cArgs) of them. See SHPackDispParams
// for details.
typedef struct FAKEBSTR { ULONG cb; WCHAR wsz[1]; } FAKEBSTR;
const FAKEBSTR c_bstrNULL = { 0, L"" };
LWSTDAPI SHPackDispParamsV(DISPPARAMS *pdispparams, VARIANTARG *rgvarg, UINT cArgs, va_list ap) { HRESULT hr = S_OK;
ZeroMemory(rgvarg, cArgs * SIZEOF(VARIANTARG));
// fill out DISPPARAMS structure
pdispparams->rgvarg = rgvarg; pdispparams->rgdispidNamedArgs = NULL; pdispparams->cArgs = cArgs; pdispparams->cNamedArgs = 0;
// parameters are ordered in ap with the right-most parameter
// at index zero and the left-most parameter at the highest index; essentially,
// the parameters are pushed from right to left. Put the first argument we
// encounter at the highest index.
// pVarArg points to the argument structure in the array we are currently
// filling in. Initialize this to point at highest argument (zero-based,
// hence the -1). For each passed-in argument we process, *decrement*
// the pVarArg pointer to achieve the "push from right-to-left" effect.
VARIANTARG * pVarArg = &rgvarg[cArgs - 1];
int nCount = cArgs; while (nCount) { VARENUM vaType = va_arg(ap,VARENUM);
// We don't have to call VariantInit because we zerod out
// the entire array before entering this loop
V_VT(pVarArg) = vaType;
// the next field is a union, so we can be smart about filling it in
//
if (vaType & VT_BYREF) { // All byrefs can be packed the same way
V_BYREF(pVarArg) = va_arg(ap, LPVOID); } else { switch (vaType) { case VT_BSTR: { // parameter is a BSTR
// MFC doesn't like it when you pass NULL for a VT_BSTR type
V_BSTR(pVarArg) = va_arg(ap, BSTR); if (V_BSTR(pVarArg) == NULL) V_BSTR(pVarArg) =(BSTR)c_bstrNULL.wsz; #ifdef DEBUG
// Check if this BSTR is a valid BSTR
FAKEBSTR *bstr = CONTAINING_RECORD(V_BSTR(pVarArg), FAKEBSTR, wsz); ASSERT(bstr->cb == lstrlenW(bstr->wsz) * SIZEOF(WCHAR)); #endif
break; } case VT_BOOL: V_BOOL(pVarArg) = va_arg(ap, VARIANT_BOOL); break;
case VT_DISPATCH: V_DISPATCH(pVarArg) = va_arg(ap, LPDISPATCH); break;
case VT_UNKNOWN: V_UNKNOWN(pVarArg) = va_arg(ap, LPUNKNOWN); break;
default: AssertMsg(0, TEXT("Packing unknown variant type 0x%x as VT_I4"), vaType); // if we don't know what it is treat it as VT_I4.
// Hopefully it's not a pointer or a VT_R8 or that sort of
// thing, or we're in trouble.
V_VT(pVarArg) = VT_I4;
case VT_I4: V_I4(pVarArg) = va_arg(ap, LONG); break;
} }
nCount--; pVarArg--; }
return hr; }
//
// Takes a variable number of generic parameters, packages
// them up.
//
// pdispparams - The DISPPARAMS structure that receives the result
// of the packaging.
//
// rgvarg - Array of length cArgs.
// It will be used to hold the parameters.
//
// cArgs - Number of pairs of generic arguments (below).
//
// ... - A collection of (VARNUM, LPVOID) pairs of arguments.
// The first is the type of the argument, and the
// second is the corresponding value.
//
// As a special case, a null VT_BSTR can be passed
// as a NULL pointer and we will turn it into a
// genuine null BSTR.
//
// The following VARENUMs are supported:
//
// VT_BYREF - Anything that is VT_BYREF is okay
// VT_BSTR
// VT_BOOL
// VT_DISPATCH
// VT_UNKNOWN
// VT_I4
//
// Any other type will be packaged randomly, so don't do that.
//
// Example:
//
// DISPPARAMS dispparams;
// VARIANTARG args[4]; // room for 4 parameters
// SHPackDispParams(&dispparams, args, 4, // and here they are
// VT_BSTR, bstrURL,
// VT_I4, dwFlags,
// VT_BSTR, NULL, // no post data
// VT_BSTR, bstrHeaders);
//
LWSTDAPI SHPackDispParams(DISPPARAMS *pdispparams, VARIANTARG *rgvarg, UINT cArgs, ...) { va_list ap; va_start(ap, cArgs);
HRESULT hr = SHPackDispParamsV(pdispparams, rgvarg, cArgs, ap);
va_end(ap); return hr; }
//=============================================================================
//
// IConnectionPoint helper functions
//-----------------------------------------------------------------------------
//
// INVOKECALLBACK
//
// Allows clients to customize the the invoke process. The callback
// receives the following parameters:
//
// pdisp - The IDispatch that is about to receive an invoke.
//
// pinv - SHINVOKEPARAMS structure that describes the invoke
// that is about to occur.
//
// The callback function is called before each sink is dispatched.
// The callback can return any of the following values:
//
// S_OK Proceed with the invoke
// S_FALSE Skip this invoke but keep invoking others
// E_FAIL Stop invoking
//
// A client can do lazy-evaluation of dispatch arguments by installing
// a callback that sets up the dispatch arguments on the first callback.
//
// A client can support a "Cancel" flag by returning E_FAIL once the
// cancel has occurred.
//
// A client can pre-validate an IDispatch for compatibility reasons
// and either touch up the arguments and return S_OK, or decide that
// the IDispatch should be skipped and return S_FALSE.
//
// A client can append custom information to the end of the SHINVOKEPARAMS
// structure to allow it to determine additional context.
//
// A client can do post-invoke goo by doing work on the pre-invoke
// of the subsequent callback (plus one final bout of work when the
// entire enumeration completes).
//
//
// Obtaining a connection point sink is supposed to be easy. You just
// QI for the interface. Unfortunately, too many components are buggy.
//
// mmc.exe faults if you QI for IDispatch
// and punkCB is non-NULL. And if you do pass in NULL,
// it returns S_OK but fills punkCB with NULL anyway.
// Somebody must've had a rough day.
//
// Java responds only to its dispatch ID and not IID_IDispatch, even
// though the dispatch ID is derived from IID_IDispatch.
//
// The Explorer Band responds only to IID_IDispatch and not to
// the dispatch ID.
//
HRESULT GetConnectionPointSink(IUnknown *pUnk, const IID *piidCB, IUnknown **ppunkCB) { HRESULT hr = E_NOINTERFACE; *ppunkCB = NULL; // Pre-zero it to work around MMC
if (piidCB) // Optional interface (Java/ExplBand)
{ hr = pUnk->QueryInterface(*piidCB, (void **) ppunkCB); if (*ppunkCB == NULL) // Clean up behind MMC
hr = E_NOINTERFACE; } return hr; }
//
// Enumerate the connection point sinks, calling the callback for each one
// found.
//
// The callback function is called once for each sink. The IUnknown is
// whatever interface we could get from the sink (either piidCB or piidCB2).
//
typedef HRESULT (CALLBACK *ENUMCONNECTIONPOINTSPROC)( /* [in, iid_is(*piidCB)] */ IUnknown *psink, LPARAM lParam);
HRESULT EnumConnectionPointSinks( IConnectionPoint *pcp, // IConnectionPoint victim
const IID *piidCB, // Interface for callback
const IID *piidCB2, // Alternate interface for callback
ENUMCONNECTIONPOINTSPROC EnumProc, // Callback procedure
LPARAM lParam) // Refdata for callback
{ HRESULT hr; IEnumConnections * pec;
if (pcp) hr = pcp->EnumConnections(&pec); else hr = E_NOINTERFACE;
if (SUCCEEDED(hr)) { CONNECTDATA cd; ULONG cFetched;
while (S_OK == (hr = pec->Next(1, &cd, &cFetched))) { IUnknown *punkCB;
ASSERT(1 == cFetched);
hr = GetConnectionPointSink(cd.pUnk, piidCB, &punkCB); if (FAILED(hr)) hr = GetConnectionPointSink(cd.pUnk, piidCB2, &punkCB);
if (EVAL(SUCCEEDED(hr))) { hr = EnumProc(punkCB, lParam); punkCB->Release(); } else { hr = S_OK; // Pretend callback succeeded
} cd.pUnk->Release(); if (FAILED(hr)) break; // Callback asked to stop
} pec->Release(); hr = S_OK; }
return hr; }
//
// Send out the callback (if applicable) and then do the invoke if the
// callback said that was a good idea.
//
// Parameters:
//
// pcp - IConnectionPoint whose sinks are to be Invoke()d.
// If this parameter is NULL, the function does nothing.
// pinv - Structure containing parameters to INVOKE.
HRESULT CALLBACK EnumInvokeCallback(IUnknown *psink, LPARAM lParam) { IDispatch *pdisp = (IDispatch *)psink; LPSHINVOKEPARAMS pinv = (LPSHINVOKEPARAMS)lParam; HRESULT hr;
if (pinv->Callback) { // Now see if the callback wants to do pre-vet the pdisp.
// It can return S_FALSE to skip this callback or E_FAIL to
// stop the invoke altogether
hr = pinv->Callback(pdisp, pinv); if (hr != S_OK) return hr; }
pdisp->Invoke(pinv->dispidMember, *pinv->piid, pinv->lcid, pinv->wFlags, pinv->pdispparams, pinv->pvarResult, pinv->pexcepinfo, pinv->puArgErr);
return S_OK; }
//
// IConnectionPoint_InvokeIndirect
//
// Given a connection point, call the IDispatch::Invoke for each
// connected sink.
//
// The return value merely indicates whether the command was dispatched.
// If any particular sink fails the IDispatch::Invoke, we will still
// return S_OK, since the command was indeed dispatched.
//
// Parameters:
//
// pcp - IConnectionPoint whose sinks are to be Invoke()d.
// If this parameter is NULL, the function does nothing.
// pinv - Structure containing parameters to INVOKE.
// The pdispparams field can be NULL; we will turn it
// into a real DISPPARAMS for you.
//
// The SHINVOKEPARAMS.flags field can contain the following flags.
//
// IPFL_USECALLBACK - The callback field contains a callback function
// Otherwise, it will be set to NULL.
// IPFL_USEDEFAULT - Many fields in the SHINVOKEPARAMS will be set to
// default values to save the caller effort:
//
// riid = IID_NULL
// lcid = 0
// wFlags = DISPATCH_METHOD
// pvarResult = NULL
// pexcepinfo = NULL
// puArgErr = NULL
//
LWSTDAPI IConnectionPoint_InvokeIndirect( IConnectionPoint *pcp, SHINVOKEPARAMS *pinv) { HRESULT hr; DISPPARAMS dp = { 0 }; IID iidCP;
if (pinv->pdispparams == NULL) pinv->pdispparams = &dp;
if (!(pinv->flags & IPFL_USECALLBACK)) { pinv->Callback = NULL; }
if (pinv->flags & IPFL_USEDEFAULTS) { pinv->piid = &IID_NULL; pinv->lcid = 0; pinv->wFlags = DISPATCH_METHOD; pinv->pvarResult = NULL; pinv->pexcepinfo = NULL; pinv->puArgErr = NULL; }
// Try both the interface they actually connected on,
// as well as IDispatch. Apparently Java responds only to
// the connecting interface, and ExplBand responds only to
// IDispatch, so we have to try both. (Sigh. Too many buggy
// components in the system.)
hr = EnumConnectionPointSinks(pcp, (pcp->GetConnectionInterface(&iidCP) == S_OK) ? &iidCP : NULL, &IID_IDispatch, EnumInvokeCallback, (LPARAM)pinv);
// Put the original NULL back so the caller can re-use the SHINVOKEPARAMS.
if (pinv->pdispparams == &dp) pinv->pdispparams = NULL;
return hr; }
//
// Wrapper around IConnectionPoint_InvokeIndirect with special Cancel
// semantics.
//
// Parameters:
//
// pcp - IConnectionPoint whose sinks are to be Invoke()d.
// If this parameter is NULL, the function does nothing.
// dispid - The DISPID to invoke
// pdispparams - The DISPPARAMS for the invoke
// pfCancel - Optional BOOL to cancel the invoke
// ppvCancel - Optional LPVOID to cancel the invoke
//
// If either *pfCancel or *ppvCancel is nonzero/non-NULL, we stop the invoke
// process. This allows a sink to "handle" the event and prevent other
// sinks from receiving it. The ppvCancel parameter is for dispid's which
// are queries that are asking for somebody to create an object and return it.
//
// It is the caller's responsibility to check the values of *pfCancel
// and/or *ppvCancel to determine if the operation was cancelled.
//
typedef struct INVOKEWITHCANCEL { SHINVOKEPARAMS inv; LPBOOL pfCancel; void **ppvCancel; } INVOKEWITHCANCEL;
HRESULT CALLBACK InvokeWithCancelProc(IDispatch *psink, SHINVOKEPARAMS *pinv) { INVOKEWITHCANCEL *piwc = CONTAINING_RECORD(pinv, INVOKEWITHCANCEL, inv);
if ((piwc->pfCancel && *piwc->pfCancel) || (piwc->ppvCancel && *piwc->ppvCancel)) return E_FAIL;
return S_OK; }
LWSTDAPI IConnectionPoint_InvokeWithCancel( IConnectionPoint *pcp, DISPID dispidMember, DISPPARAMS * pdispparams, LPBOOL pfCancel, void **ppvCancel) { INVOKEWITHCANCEL iwc;
iwc.inv.flags = IPFL_USECALLBACK | IPFL_USEDEFAULTS; iwc.inv.dispidMember = dispidMember; iwc.inv.pdispparams = pdispparams; iwc.inv.Callback = InvokeWithCancelProc; iwc.pfCancel = pfCancel; iwc.ppvCancel = ppvCancel;
return IConnectionPoint_InvokeIndirect(pcp, &iwc.inv); }
//
// Wrapper around IConnectionPoint_InvokeIndirect with IPFL_USEDEFAULTS.
//
LWSTDAPI IConnectionPoint_SimpleInvoke(IConnectionPoint *pcp, DISPID dispidMember, DISPPARAMS *pdispparams) { SHINVOKEPARAMS inv;
inv.flags = IPFL_USEDEFAULTS; inv.dispidMember = dispidMember; inv.pdispparams = pdispparams;
return IConnectionPoint_InvokeIndirect(pcp, &inv); }
//
// Takes a variable number of parameters for IDispatch, packages
// them up, and invokes them.
//
// The parameters to the IDispatch::Invoke will be
//
// dispidMember - dispidMember
// riid - IID_NULL
// lcid - 0
// wFlags - DISPATCH_METHOD
// pdispparams - <parameters to this function>
// pvarResult - NULL
// pexcepinfo - NULL
// puArgErr - NULL
//
// The parameters to this function are
//
// pcp - IConnectionPoint whose sinks should be Invoke()d.
// If this parameter is NULL, the function does nothing.
// dispidMember - The DISPID to invoke.
// rgvarg - Array of length cArgs.
// It will be used to hold the parameters.
// cArgs - Number of pairs of generic arguments (below).
//
// ap - va_list of parameters to package. We package up the
// first (2 * cArgs) of them. See SHPackDispParams
// for details.
//
LWSTDAPI IConnectionPoint_InvokeParamV(IConnectionPoint *pcp, DISPID dispidMember, VARIANTARG *rgvarg, UINT cArgs, va_list ap) { HRESULT hr;
if (pcp) { DISPPARAMS dp; hr = SHPackDispParamsV(&dp, rgvarg, cArgs, ap); if (EVAL(SUCCEEDED(hr))) { hr = IConnectionPoint_SimpleInvoke(pcp, dispidMember, &dp); } } else hr = E_NOINTERFACE;
return hr; }
//
// Given a connection point that represents IPropertyNotifySink,
// call the IPropertyNotifySink::OnChanged for each connected sink.
//
// Parameters:
//
// pcp - IConnectionPoint whose sinks are to be notified.
// If this parameter is NULL, the function does nothing.
// dispid - To pass to IPropertyNotifySink::OnChanged.
HRESULT CALLBACK OnChangedCallback(IUnknown *psink, LPARAM lParam) { IPropertyNotifySink *pns = (IPropertyNotifySink *)psink; DISPID dispid = (DISPID)lParam;
pns->OnChanged(dispid);
return S_OK; }
LWSTDAPI IConnectionPoint_OnChanged(IConnectionPoint *pcp, DISPID dispid) { #ifdef DEBUG
// Make sure it really is an IPropertyNotifySink connection point.
if (pcp) { IID iid; HRESULT hr = pcp->GetConnectionInterface(&iid); ASSERT(SUCCEEDED(hr) && iid == IID_IPropertyNotifySink); } #endif
return EnumConnectionPointSinks(pcp, &IID_IPropertyNotifySink, NULL, OnChangedCallback, (LPARAM)dispid); }
//=============================================================================
//
// IConnectionPointContainer helper functions
//
// QI's for IConnectionPointContainer and then does the FindConnectionPoint.
//
// Parameters:
//
// punk - The object who might be an IConnectionPointContainer.
// This parameter may be NULL, in which case the
// operation fails.
// riidCP - The connection point interface to locate.
// pcpOut - Receives the IConnectionPoint, if any.
LWSTDAPI IUnknown_FindConnectionPoint(IUnknown *punk, REFIID riidCP, IConnectionPoint **pcpOut) { HRESULT hr;
*pcpOut = NULL;
if (punk) { IConnectionPointContainer *pcpc; hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **)&pcpc); if (SUCCEEDED(hr)) { hr = pcpc->FindConnectionPoint(riidCP, pcpOut); pcpc->Release(); } } else hr = E_NOINTERFACE;
return hr; }
//
// Given an IUnknown, query for its connection point container,
// find the corresponding connection point, package up the
// invoke parameters, and call the IDispatch::Invoke for each
// connected sink.
//
// See IConnectionPoint_InvokeParam for additional semantics.
//
// Parameters:
//
// punk - Object that might be an IConnectionPointContainer
// riidCP - ConnectionPoint interface to request
// pinv - Arguments for the Invoke.
//
LWSTDAPI IUnknown_CPContainerInvokeIndirect(IUnknown *punk, REFIID riidCP, SHINVOKEPARAMS *pinv) { IConnectionPoint *pcp; HRESULT hr = IUnknown_FindConnectionPoint(punk, riidCP, &pcp); if (SUCCEEDED(hr)) { hr = IConnectionPoint_InvokeIndirect(pcp, pinv); pcp->Release(); } return hr; }
//
// This is the ultimate in one-stop shopping.
//
// Given an IUnknown, query for its connection point container,
// find the corresponding connection point, package up the
// invoke parameters, and call the IDispatch::Invoke for each
// connected sink.
//
// See IConnectionPoint_InvokeParam for additional semantics.
//
// Parameters:
//
// punk - Object that might be an IConnectionPointContainer
// riidCP - ConnectionPoint interface to request
// dispidMember - The DISPID to invoke.
// rgvarg - Array of length cArgs.
// It will be used to hold the parameters.
// cArgs - Number of pairs of generic arguments (below).
// ... - A collection of (VARNUM, LPVOID) pairs of arguments.
// See SHPackDispParams for details.
//
// Example:
//
// IUnknown_CPContainerInvokeParam(punk, DIID_DShellFolderViewEvents,
// DISPID_SELECTIONCHANGED, NULL, 0);
LWSTDAPIV IUnknown_CPContainerInvokeParam( IUnknown *punk, REFIID riidCP, DISPID dispidMember, VARIANTARG *rgvarg, UINT cArgs, ...) { IConnectionPoint *pcp; HRESULT hr = IUnknown_FindConnectionPoint(punk, riidCP, &pcp);
if (SUCCEEDED(hr)) { va_list ap; va_start(ap, cArgs); hr = IConnectionPoint_InvokeParamV(pcp, dispidMember, rgvarg, cArgs, ap); va_end(ap); pcp->Release(); }
return hr; }
//
// Given an IUnknown, query for its connection point container,
// find the corresponding connection point, and call the
// IPropertyNotifySink::OnChanged for each connected sink.
//
// Parameters:
//
// punk - Object that might be an IConnectionPointContainer
// dispid - To pass to IPropertyNotifySink::OnChanged.
LWSTDAPI IUnknown_CPContainerOnChanged(IUnknown *punk, DISPID dispid) { IConnectionPoint *pcp; HRESULT hr = IUnknown_FindConnectionPoint(punk, IID_IPropertyNotifySink, &pcp); if (SUCCEEDED(hr)) { hr = IConnectionPoint_OnChanged(pcp, dispid); pcp->Release(); } return hr; }
|