windows-nt/Source/XPSP1/NT/shell/shlwapi/connect.cpp
2020-09-26 16:20:57 +08:00

761 lines
23 KiB
C++

//
// 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;
}
//
// Worker function for many classes of IConnectionPoint_Invoke
// clients. Just pass the parameters and we'll pack them up for you.
//
// 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).
//
// ... - A collection of (VARENUM, blah) pairs of arguments.
// See SHPackDispParams for details.
//
// Example:
//
// VARIANTARG args[1];
// IConnectionPoint_InvokeParam(pcp, DISPID_PROPERTYCHANGE,
// args, 1,
// VT_BSTR, bstrProperty);
LWSTDAPIV IConnectionPoint_InvokeParam(IConnectionPoint *pcp, DISPID dispidMember,
VARIANTARG *rgvarg, UINT cArgs, ...)
{
HRESULT hr;
va_list ap;
va_start(ap, cArgs);
hr = IConnectionPoint_InvokeParamV(pcp, dispidMember, rgvarg, cArgs, ap);
va_end(ap);
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;
}