// // IConnectionPoint/IDispatch helper functions // #include "priv.h" #include // // 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 - // 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; }