//=--------------------------------------------------------------------------=
// proxy.cpp
//=--------------------------------------------------------------------------=
// Copyright (c) 1999, Microsoft Corp.
//                 All Rights Reserved
// Information Contained Herein Is Proprietary and Confidential.
//=--------------------------------------------------------------------------=
//
// MMC Interfcace Proxy and Stub Functions
//
//=--------------------------------------------------------------------------=
// This file contains proxy and stub functions for MMC methods that are not
// remotable using a MIDL generated proxy and stub. Non-remotable methods have
// parameters that are ambiguous i.e. can be casted to different data types.
// For example, IComponentData::Notify() is passed an event and two additional
// LPARAM arguments that are interpreted according to the event. Sometimes an
// LPARAM contains a simple value such as a long or two BOOLs and sometimes it
// contains an IDataObject *. MIDL doesn't know the difference between
// MMCN_SELECT and MMCN_PRINT so we need to write some code to help out.
// 
// The version of MMC.IDL in the designer directory has added a [local]
// attribute to all non-remotable methods. In addition, an extra method has
// been added to the same interface that is a remotable version of the method.
// The remotable version has more parameters and represents the union of all
// possible interpretations of the ambiguous parameter. For example,
// IExtendControlbar::ControlbarNotify() is defined as:
// 
//    [helpstring("User actions"), local]
//    HRESULT ControlbarNotify([in] MMC_NOTIFY_TYPE event,
//                             [in] LPARAM arg, [in] LPARAM param);
// 
// This method can receive MMCN_SELECT, MMCN_BTN_CLICK, and MMCN_MENU_BTNCLICK.
// The union of all possible parameter types is used in the following method
// added to that interface:
// 
// 
//    HRESULT RemControlbarNotify([in] MMC_NOTIFY_TYPE event,
//                                [in] LPARAM lparam, 
//                                [in] IDataObject *piDataObject,
//                                [in] MENUBUTTONDATA *MenuButtonData);
// 
// Note, that normal, in-proc, non-remoted versions of IExtendControlbar do not
// have this extra method in their vtable because no one is going to call it.
// It is only used in the MILD generated proxy object.
// 
// In order to tell MIDL which method remotes ControlbarNotify() an attribute
// control file (ACF) is used. The entry in the ACF for IExtendControlbar is:
// 
// interface IExtendControlbar 
// {
//     [call_as (ControlbarNotify)]
//             RemControlbarNotify();
// 
// }
// 
// This says that RemControlbarNotify should be called when remoting
// ControlbarNotify(). MIDL generates the proxy/stub code as usual but it only
// generates the prototypes for ControlbarNotify proxy and stub. We have to
// write these routines. 
// 
// When a remote client has an IExtendControlbarNotify pointer it actually
// points into the proxy vtable. MIDL sets the ControlbarNotify entry pointing to
// our IExtendControlbar_ControlbarNotify_Proxy() function below. That function
// interprets the parameters and then calls the MIDL generated
// IExtendControlbar_RemControlbarNotify_Proxy() that packs up the parameters
// and sends them off to the server. If a parameters is not applicable, (e.g.
// MMCN_SELECT does not receive a pointer to a MENUBUTTONDATA stuct), then a
// pointer to an empty struct or zeroes are sent.
// 
// When the packet reaches the server side the MIDL generated
// IExtendControlbar_RemControlbarNotify_Stub() unpacks them and then calls our
// IExtendControlbar_ControlbarNotify_Stub() passing it the parameters and the
// IExtendControlbar pointer into the server. This function interprets the
// parameters and then calls ControlbarNotify in the server.
// 
//=--------------------------------------------------------------------------=

#include "mmc.h"

extern HRESULT GetClipboardFormat
(
    WCHAR      *pwszFormatName,
    CLIPFORMAT *pcfFormat
);

extern HRESULT CreateMultiSelDataObject
(
    IDataObject          **ppiDataObjects,
    long                   cDataObjects,
    IDataObject          **ppiMultiSelDataObject
);


static HRESULT MenuButtonClickProxy
( 
    IExtendControlbar __RPC_FAR *This,
    IDataObject                 *piDataObject,
    MENUBUTTONDATA              *pMenuButtonData
);

static HRESULT IsMultiSelect(IDataObject *piDataObject, BOOL *pfMultiSelect)
{
    HRESULT    hr = S_OK;
    DWORD     *pdwMultiSelect = NULL;
    BOOL       fGotData = FALSE;
    FORMATETC  FmtEtc;
    STGMEDIUM  StgMed;

    ZeroMemory(&FmtEtc, sizeof(FmtEtc));
    ZeroMemory(&StgMed, sizeof(StgMed));

    *pfMultiSelect = FALSE;

    if (NULL == piDataObject)
    {
        goto Cleanup;
    }

    if (IS_SPECIAL_DATAOBJECT(piDataObject))
    {
        goto Cleanup;
    }

    hr = GetClipboardFormat(CCF_MMC_MULTISELECT_DATAOBJECT, &FmtEtc.cfFormat);
    if (FAILED(hr))
    {
        goto Cleanup;
    }
    FmtEtc.dwAspect  = DVASPECT_CONTENT;
    FmtEtc.lindex = -1L;
    FmtEtc.tymed = TYMED_HGLOBAL;
    StgMed.tymed = TYMED_HGLOBAL;

    hr = piDataObject->lpVtbl->GetData(piDataObject, &FmtEtc, &StgMed);
    if (SUCCEEDED(hr))
    {
        fGotData = TRUE;
    }
    else
    {
        hr = S_OK;
    }

    // Ignore any failures and assume that it is not multi-select. Snap-ins
    // should return DV_E_FORMATETC or DV_E_CLIPFORMAT but in practice that
    // is not the case. For example, the IIS snap-in returns E_NOTIMPL.
    // It would be impossible to cover the range of reasonable return codes so
    // we treat any error as format not supported.

    if (fGotData)
    {
        pdwMultiSelect = (DWORD *)GlobalLock(StgMed.hGlobal);

        if ((DWORD)1 == *pdwMultiSelect)
        {
            *pfMultiSelect = TRUE;
        }
    }

Cleanup:
    if (NULL != pdwMultiSelect)
    {
        (void)GlobalUnlock(StgMed.hGlobal);
    }

    if (fGotData)
    {
        ReleaseStgMedium(&StgMed);
    }
    return hr;
}




static HRESULT InterpretMultiSelect
(
    IDataObject     *piDataObject,
    long            *pcDataObjects,
    IDataObject   ***pppiDataObjects
)
{
    HRESULT          hr = S_OK;
    SMMCDataObjects *pMMCDataObjects = NULL;
    BOOL             fGotData = FALSE;
    size_t           cbObjectTypes = 0;
    long             i = 0;
    FORMATETC        FmtEtc;
    STGMEDIUM        StgMed;

    ZeroMemory(&FmtEtc, sizeof(FmtEtc));
    ZeroMemory(&StgMed, sizeof(StgMed));

    *pcDataObjects = 0;
    *pppiDataObjects = NULL;

    // Get the SMMCDataObjects structure from MMC

    hr = GetClipboardFormat(CCF_MULTI_SELECT_SNAPINS, &FmtEtc.cfFormat);
    if (FAILED(hr))
    {
        goto Cleanup;
    }
    FmtEtc.dwAspect  = DVASPECT_CONTENT;
    FmtEtc.lindex = -1L;
    FmtEtc.tymed = TYMED_HGLOBAL;
    StgMed.tymed = TYMED_HGLOBAL;

    hr = piDataObject->lpVtbl->GetData(piDataObject, &FmtEtc, &StgMed);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    fGotData = TRUE;

    pMMCDataObjects = (SMMCDataObjects *)GlobalLock(StgMed.hGlobal);
    if (NULL == pMMCDataObjects)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    // Allocate an array of IDataObject and copy the IDataObjects to it

    *pcDataObjects = pMMCDataObjects->count;
    *pppiDataObjects = (IDataObject **)GlobalAlloc(GPTR,
                               pMMCDataObjects->count * sizeof(IDataObject *));

    if (NULL == *pppiDataObjects)
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }

    for (i = 0; i < *pcDataObjects; i++)
    {
        (*pppiDataObjects)[i] = pMMCDataObjects->lpDataObject[i];
    }

Cleanup:
    if (NULL != pMMCDataObjects)
    {
        (void)GlobalUnlock(StgMed.hGlobal);
    }

    if (fGotData)
    {
        ReleaseStgMedium(&StgMed);
    }

    return hr;
}


void CheckForSpecialDataObjects
(
    IDataObject **ppiDataObject,
    BOOL         *pfSpecialDataObject,
    long         *plSpecialDataObject
)
{
    long lSpecialDataObject = (long)(*ppiDataObject);

    if (IS_SPECIAL_DATAOBJECT(lSpecialDataObject))
    {
        *plSpecialDataObject = lSpecialDataObject;
        *ppiDataObject = NULL;
        *pfSpecialDataObject = TRUE;
    }
    else
    {
        *pfSpecialDataObject = FALSE;
    }
}

static HRESULT SetRemote(IUnknown *This)
{
    HRESULT     hr = S_OK;
    IMMCRemote *piMMCRemote = NULL;
    DWORD       cbFileName = 0;
    char        szModuleFileName[MAX_PATH] = "";

    // Call IMMCRemote methods: ObjectIsRemote and SetMMCExePath so that the
    // snap-in will know it is remote and so that it will have MMC.EXE's full
    // path in order to build taskpad display strings.

    hr = This->lpVtbl->QueryInterface(This, &IID_IMMCRemote,
                                      (void **)&piMMCRemote);
    if (FAILED(hr))
    {
        // If the object doesn't support IMMCRemote that is not an error.
        // The designer runtime will get this QI on both its main object and
        // its IComponent object but only the main object needs to support the
        // interface.
        hr = S_OK;
        goto Cleanup;
    }

    hr = piMMCRemote->lpVtbl->ObjectIsRemote(piMMCRemote);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    cbFileName = GetModuleFileName(NULL, // get executable that loaded us (MMC)
                                   szModuleFileName,
                                   sizeof(szModuleFileName));

    if (0 == cbFileName)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    hr = piMMCRemote->lpVtbl->SetMMCExePath(piMMCRemote, szModuleFileName);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    hr = piMMCRemote->lpVtbl->SetMMCCommandLine(piMMCRemote, GetCommandLine());
    if (FAILED(hr))
    {
        goto Cleanup;
    }

Cleanup:
    if (NULL != piMMCRemote)
    {
        piMMCRemote->lpVtbl->Release(piMMCRemote);
    }

    return hr;
}




HRESULT STDMETHODCALLTYPE IExtendControlbar_SetControlbar_Proxy
( 
    IExtendControlbar __RPC_FAR *This,
    LPCONTROLBAR                 pControlbar
)
{
    HRESULT hr = S_OK;

    // Make sure the snap-in knows we are remoted. We do this here because
    // this is the first opportunity for the proxy to inform a toolbar
    // extension that it is remote.

    hr = SetRemote((IUnknown *)This);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    hr = IExtendControlbar_RemSetControlbar_Proxy(This, pControlbar);

Cleanup:
    return hr;
}




HRESULT STDMETHODCALLTYPE IExtendControlbar_SetControlbar_Stub
( 
    IExtendControlbar __RPC_FAR  *This,
    LPCONTROLBAR                  pControlbar
)
{
    return This->lpVtbl->SetControlbar(This, pControlbar);
}


HRESULT STDMETHODCALLTYPE IExtendControlbar_ControlbarNotify_Proxy
( 
    IExtendControlbar __RPC_FAR *This,
    MMC_NOTIFY_TYPE              event,
    LPARAM                       arg,
    LPARAM                       param
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject  *piDataObject = NULL; // Not AddRef()ed
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // If this is not a menu button click then we can use the generated remoting
    // code with the arg and param unions

    if (MMCN_MENU_BTNCLICK == event)
    {
        hr = MenuButtonClickProxy(This,
                                  (IDataObject *)arg,
                                  (MENUBUTTONDATA *)param);
        goto Cleanup;
    }
    // Get any IDataObject associated with the event

    switch (event)
    {
        case MMCN_SELECT:
            piDataObject = (IDataObject *)param;
            break;

        case MMCN_BTN_CLICK:
            piDataObject = (IDataObject *)arg;
            break;

        default:
            piDataObject = NULL;
            break;
    }

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects,
                                  &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendControlbar_RemControlbarNotify_Proxy(This,
                                                     cDataObjects,
                                                     ppiDataObjects,
                                                     fSpecialDataObject,
                                                     lSpecialDataObject,
                                                     event, arg, param);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}




HRESULT STDMETHODCALLTYPE IExtendControlbar_ControlbarNotify_Stub
( 
    IExtendControlbar __RPC_FAR  *This,
    long                          cDataObjects,
    IDataObject                 **ppiDataObjects,
    BOOL                          fSpecialDataObject,
    long                          lSpecialDataObject,
    MMC_NOTIFY_TYPE               event,
    LPARAM                        arg,
    LPARAM                        param
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Put the IDataObject into the corresponding parameter for the event

    switch (event)
    {
        case MMCN_SELECT:
            param = (LPARAM)piDataObject;
            break;

        case MMCN_BTN_CLICK:
            arg = (LPARAM)piDataObject;
            break;

        default:
            break;
    }

    // Call into the snap-in with all parameters appearing as they would
    // when in-proc.

    hr = This->lpVtbl->ControlbarNotify(This, event, arg, param);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}


static HRESULT MenuButtonClickProxy
( 
    IExtendControlbar __RPC_FAR *This,
    IDataObject                 *piDataObject,
    MENUBUTTONDATA              *pMenuButtonData
)
{
    HRESULT                   hr = S_OK;
    POPUP_MENUDEF            *pPopupMenuDef = NULL;
    HMENU                     hMenu = NULL;
    UINT                      uiSelectedItemID = 0;
    IExtendControlbarRemote  *piECRemote = NULL;
    long                      i = 0;
    BOOL                      fIsMultiSelect = FALSE;
    long                      cDataObjects = 1L;
    IDataObject             **ppiDataObjects = NULL;


    // The generated remoting cannot easily handle what we need to do so we get
    // IExtendControlbarRemote on the snap-in. This interface has methods that
    // allow us to ask the snap-in for its popup menu items, display the menu
    // here on the MMC side, and then tell the snap-in which item was selected.

    hr = This->lpVtbl->QueryInterface(This, &IID_IExtendControlbarRemote,
                                      (void **)&piECRemote);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // Tell the snap-in about the menu button click and get back its list of
    // popup menu items.

    hr = piECRemote->lpVtbl->MenuButtonClick(piECRemote,
                                             piDataObject,
                                             pMenuButtonData->idCommand,
                                             &pPopupMenuDef);     

    if ( FAILED(hr) || (NULL == pPopupMenuDef) )
    {
        goto Cleanup;
    }

    // Create an empty Win32 menu

    hMenu = CreatePopupMenu();
    if (NULL == hMenu)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    // Iterate through each of the items and add them to the menu

    for (i = 0; i < pPopupMenuDef->cMenuItems; i++)
    {
        if (!AppendMenu(hMenu,
                        pPopupMenuDef->MenuItems[i].uiFlags,
                        pPopupMenuDef->MenuItems[i].uiItemID,
                        pPopupMenuDef->MenuItems[i].pszItemText))
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto Cleanup;
        }
    }

    // If the owner HWND is NULL then this is an extension and it does not have
    // access to IConsole2 on MMC to get the main frame HWND. In this case just
    // use the active window on this thread.

    if (NULL == pPopupMenuDef->hwndMenuOwner)
    {
        pPopupMenuDef->hwndMenuOwner = GetActiveWindow();
    }

    // Display the popup menu and wait for a selection.

    uiSelectedItemID = (UINT)TrackPopupMenu(
       hMenu,                        // menu to display
       TPM_LEFTALIGN |               // align left side of menu with x
       TPM_TOPALIGN  |               // align top of menu with y
       TPM_NONOTIFY  |               // don't send any messages during selection
       TPM_RETURNCMD |               // make the ret val the selected item
       TPM_LEFTBUTTON,               // allow selection with left button only
       pMenuButtonData->x,           // left side coordinate
       pMenuButtonData->y,           // top coordinate
       0,                            // reserved,
       pPopupMenuDef->hwndMenuOwner, // owner window, this comes from snap-in
                                     // as it can call IConsole2->GetMainWindow
       NULL);                        // not used

    // A zero return could indicate either an error or that the user hit
    // Escape or clicked off of the menu to cancel the operation. GetLastError()
    // determines whether there was an error. Either way we're done but set the
    // hr first.

    if (0 == uiSelectedItemID)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    // If i is non-zero then it contains the ID of the selected item.
    // Tell the snap-in what was selected and pass it the extra IUnknown it
    // included in its menu definition (this is snap-in defined and it allows
    // the snap-in to include some more identifying information to handle the
    // event).

    if (0 != uiSelectedItemID)
    {
        hr = piECRemote->lpVtbl->PopupMenuClick(
                                              piECRemote,
                                              piDataObject,
                                              uiSelectedItemID,
                                              pPopupMenuDef->punkSnapInDefined);
    }

Cleanup:

    if (NULL != piECRemote)
    {
        piECRemote->lpVtbl->Release(piECRemote);
    }
    if (NULL != hMenu)
    {
        (void)DestroyMenu(hMenu);
    }

    if (NULL != pPopupMenuDef)
    {
        for (i = 0; i < pPopupMenuDef->cMenuItems; i++)
        {
            if (NULL != pPopupMenuDef->MenuItems[i].pszItemText)
            {
                CoTaskMemFree(pPopupMenuDef->MenuItems[i].pszItemText);
            }
        }
        if (NULL != pPopupMenuDef->punkSnapInDefined)
        {
            pPopupMenuDef->punkSnapInDefined->lpVtbl->Release(pPopupMenuDef->punkSnapInDefined);
        }
        CoTaskMemFree(pPopupMenuDef);
    }

    return hr;
}



HRESULT STDMETHODCALLTYPE IExtendControlbarRemote_MenuButtonClick_Proxy
( 
    IExtendControlbarRemote __RPC_FAR  *This,
    IDataObject __RPC_FAR              *piDataObject,
    int                                 idCommand,
    POPUP_MENUDEF __RPC_FAR *__RPC_FAR *ppPopupMenuDef
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendControlbarRemote_RemMenuButtonClick_Proxy(This,
                                                          cDataObjects,
                                                          ppiDataObjects,
                                                          fSpecialDataObject,
                                                          lSpecialDataObject,
                                                          idCommand,
                                                          ppPopupMenuDef);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}




HRESULT STDMETHODCALLTYPE IExtendControlbarRemote_MenuButtonClick_Stub
( 
    IExtendControlbarRemote __RPC_FAR  *This,
    long                                cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR    ppiDataObjects[  ],
    BOOL                                fSpecialDataObject,
    long                                lSpecialDataObject,
    int                                 idCommand,
    POPUP_MENUDEF __RPC_FAR *__RPC_FAR *ppPopupMenuDef
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->MenuButtonClick(This, piDataObject,
                                       idCommand, ppPopupMenuDef);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendControlbarRemote_PopupMenuClick_Proxy
(
    IExtendControlbarRemote __RPC_FAR *This,
    IDataObject __RPC_FAR             *piDataObject,
    UINT                               uIDItem,
    IUnknown __RPC_FAR                *punkParam
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendControlbarRemote_RemPopupMenuClick_Proxy(This,
                                                         cDataObjects,
                                                         ppiDataObjects,
                                                         fSpecialDataObject,
                                                         lSpecialDataObject,
                                                         uIDItem,
                                                         punkParam);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendControlbarRemote_PopupMenuClick_Stub
(
    IExtendControlbarRemote __RPC_FAR *This,
    long                               cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR   ppiDataObjects[  ],
    BOOL                               fSpecialDataObject,
    long                               lSpecialDataObject,
    UINT                               uIDItem,
    IUnknown __RPC_FAR                *punkParam
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->PopupMenuClick(This, piDataObject, uIDItem, punkParam);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IComponentData_Initialize_Proxy
( 
    IComponentData  *This,
    LPUNKNOWN        pUnknown
)
{
    HRESULT hr = S_OK;

    // Tell the object it is remote and give the path to mmc.exe

    hr = SetRemote((IUnknown *)This);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // Now pass on the Initiaize call normally. Using this order allows a snap-in
    // to know it is remote prior to its IComponentData::Initialize in case it
    // needs that information up front.

    hr = IComponentData_RemInitialize_Proxy(This, pUnknown);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

Cleanup:

    return hr;
}


HRESULT STDMETHODCALLTYPE IComponentData_Initialize_Stub
( 
    IComponentData *This,
    LPUNKNOWN       pUnknown
)
{
    return This->lpVtbl->Initialize(This, pUnknown);
}



HRESULT STDMETHODCALLTYPE IComponentData_CreateComponent_Proxy
( 
    IComponentData *This,
    LPCOMPONENT    *ppComponent
)
{
    HRESULT hr = S_OK;

    // Tell the object it is remote and give the path to mmc.exe

    hr = SetRemote((IUnknown *)This);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // Now pass on the CreateComponent call normally. Using this order allows a
    // snap-in to know it is remote prior to its
    // IComponentData::CreateComponent in case it needs that information up
    // front.

    // We do this in IComponentData::Initialize and
    // IComponentData::CreateComponent. Most cases will use Initialize but in
    // MMC 1.1 a taskpad extension does not receive IComponentData::Initialize.
    // MMC only calls IComponentData::CreateComponent. As a taskpad extension
    // may need to resolve a res:// URL to use the mmc.exe path we need to do
    // it here as well.

    hr = IComponentData_RemCreateComponent_Proxy(This, ppComponent);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

Cleanup:

    return hr;
}


HRESULT STDMETHODCALLTYPE IComponentData_CreateComponent_Stub
( 
    IComponentData *This,
    LPCOMPONENT    *ppComponent
)
{
    return This->lpVtbl->CreateComponent(This, ppComponent);
}






HRESULT STDMETHODCALLTYPE IComponentData_Notify_Proxy
( 
    IComponentData __RPC_FAR *This,
    LPDATAOBJECT              piDataObject,
    MMC_NOTIFY_TYPE           event,
    LPARAM                    arg,
    LPARAM                    param
)
{
    BOOL fSpecialDataObject = FALSE;
    long lSpecialDataObject = 0;

    ICDNotifyParam ParamUnion;
    ZeroMemory(&ParamUnion, sizeof(ParamUnion));

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    ParamUnion.value = param;
    return IComponentData_RemNotify_Proxy(This, piDataObject,
                                          fSpecialDataObject,
                                          lSpecialDataObject,
                                          event, arg, &ParamUnion);
}


HRESULT STDMETHODCALLTYPE IComponentData_Notify_Stub
( 
    IComponentData __RPC_FAR *This,
    LPDATAOBJECT              piDataObject,
    BOOL                      fSpecialDataObject,
    long                      lSpecialDataObject,
    MMC_NOTIFY_TYPE           event,
    LPARAM                    arg,
    ICDNotifyParam           *pParamUnion
)
{
    if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    return This->lpVtbl->Notify(This, piDataObject,
                                event, arg, pParamUnion->value);
}


HRESULT STDMETHODCALLTYPE IComponentData_CompareObjects_Proxy
( 
    IComponentData __RPC_FAR *This,
    IDataObject              *piDataObjectA,
    IDataObject              *piDataObjectB
)
{
    HRESULT       hr = S_OK;

    BOOL          fIsMultiSelectA = FALSE;
    long          cDataObjectsA = 1L;
    IDataObject **ppiDataObjectsA = NULL;
    BOOL          fSpecialDataObjectA = FALSE;
    long          lSpecialDataObjectA = 0;

    BOOL          fIsMultiSelectB = FALSE;
    long          cDataObjectsB = 1L;
    IDataObject **ppiDataObjectsB = NULL;
    BOOL          fSpecialDataObjectB = FALSE;
    long          lSpecialDataObjectB = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObjectA, &fSpecialDataObjectA, &lSpecialDataObjectA);

    CheckForSpecialDataObjects(&piDataObjectB, &fSpecialDataObjectB, &lSpecialDataObjectB);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObjectA)
    {
        hr = IsMultiSelect(piDataObjectA, &fIsMultiSelectA);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (!fSpecialDataObjectB)
    {
        hr = IsMultiSelect(piDataObjectB, &fIsMultiSelectB);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelectA)
    {
        hr = InterpretMultiSelect(piDataObjectA, &cDataObjectsA, &ppiDataObjectsA);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjectsA = &piDataObjectA;
    }

    if (fIsMultiSelectB)
    {
        hr = InterpretMultiSelect(piDataObjectB, &cDataObjectsB, &ppiDataObjectsB);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjectsB = &piDataObjectB;
    }

    hr = IComponentData_RemCompareObjects_Proxy(This,
                                                cDataObjectsA,
                                                ppiDataObjectsA,
                                                fSpecialDataObjectA,
                                                lSpecialDataObjectA,

                                                cDataObjectsB,
                                                ppiDataObjectsB,
                                                fSpecialDataObjectB,
                                                lSpecialDataObjectB);
Cleanup:
    if ( fIsMultiSelectA && (NULL != ppiDataObjectsA) )
    {
        (void)GlobalFree(ppiDataObjectsA);
    }
    if ( fIsMultiSelectB && (NULL != ppiDataObjectsB) )
    {
        (void)GlobalFree(ppiDataObjectsB);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IComponentData_CompareObjects_Stub
( 
    IComponentData __RPC_FAR         *This,

    long                              cDataObjectsA,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjectsA[  ],
    BOOL                              fSpecialDataObjectA,
    long                              lSpecialDataObjectA,

    long                              cDataObjectsB,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjectsB[  ],
    BOOL                              fSpecialDataObjectB,
    long                              lSpecialDataObjectB
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObjectA = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObjectA = NULL;
    IDataObject *piDataObjectB = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObjectB = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjectsA > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjectsA, cDataObjectsA,
                                      &piMultiSelDataObjectA);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObjectA = piMultiSelDataObjectA;
    }
    else if (fSpecialDataObjectA)
    {
        piDataObjectA = (IDataObject *)lSpecialDataObjectA;
    }
    else
    {
        piDataObjectA = ppiDataObjectsA[0];
    }

    if (cDataObjectsB > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjectsB, cDataObjectsB,
                                      &piMultiSelDataObjectB);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObjectB = piMultiSelDataObjectB;
    }
    else if (fSpecialDataObjectB)
    {
        piDataObjectB = (IDataObject *)lSpecialDataObjectB;
    }
    else
    {
        piDataObjectB = ppiDataObjectsB[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->CompareObjects(This, piDataObjectA, piDataObjectB);

Cleanup:
    if (NULL != piMultiSelDataObjectA)
    {
        piMultiSelDataObjectA->lpVtbl->Release(piMultiSelDataObjectA);
    }
    if (NULL != piMultiSelDataObjectB)
    {
        piMultiSelDataObjectB->lpVtbl->Release(piMultiSelDataObjectB);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IComponent_Notify_Proxy
( 
    IComponent __RPC_FAR *This,
    LPDATAOBJECT          piDataObject,
    MMC_NOTIFY_TYPE       event,
    LPARAM                arg,
    LPARAM                param
)
{
    ICNotifyArg      ArgUnion;
    ICNotifyParam    ParamUnion;
    ICOutParam      *pOutParam = NULL;
    HRESULT          hr = S_OK;
    BOOL             fIsMultiSelect = FALSE;
    long             cDataObjects = 1L;
    IDataObject    **ppiDataObjects = NULL;
    BOOL             fSpecialDataObject = FALSE;
    long             lSpecialDataObject = 0;

    ZeroMemory(&ArgUnion, sizeof(ArgUnion));
    ZeroMemory(&ParamUnion, sizeof(ParamUnion));

    // Switch any potential multiselect data objects with arg/param so that
    // piDataObject always contains the potential multiselect.

    switch (event)
    {
        case MMCN_QUERY_PASTE:
            ArgUnion.pidoQueryPasteTarget = piDataObject;
            piDataObject = (IDataObject *)arg;
            ParamUnion.value = param;
            break;

        case MMCN_PASTE:
            ArgUnion.pidoPasteTarget = piDataObject;
            piDataObject = (IDataObject *)arg;
            // Pass through param as an LPARAM rather than the IDataObject **
            // it really is. This is just to let the stub know whether it is
            // a copy or a move. If it is a move the CUTORMOVE IDataObject will
            // be in the ICOutParam returned from the stub.
            ParamUnion.value = param;
            break;

        case MMCN_RESTORE_VIEW:
            ArgUnion.value = arg;
            // Don't pass param because it is a BOOL * that will not be
            // marshaled. The BOOL will be received in the ICOutParam returned
            // from the stub.
            break;
            
        default:
            ArgUnion.value = arg;
            ParamUnion.value = param;
    }

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IComponent_RemNotify_Proxy(This,
                                    cDataObjects, ppiDataObjects,
                                    fSpecialDataObject, lSpecialDataObject,
                                    event, &ArgUnion, &ParamUnion, &pOutParam);
Cleanup:
    if (NULL != pOutParam)
    {
        if (MMCN_PASTE == event)
        {
            *((IDataObject **)param) = pOutParam->pidoCutOrMove;
        }
        else if (MMCN_RESTORE_VIEW == event)
        {
            *((BOOL *)param) = pOutParam->fRestoreHandled;
        }
        CoTaskMemFree(pOutParam);
    }

    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }

    return hr;
}


HRESULT STDMETHODCALLTYPE IComponent_Notify_Stub
( 
    IComponent __RPC_FAR  *This,
    long                   cDataObjects,
    IDataObject          **ppiDataObjects,
    BOOL                   fSpecialDataObject,
    long                   lSpecialDataObject,
    MMC_NOTIFY_TYPE        event,
    ICNotifyArg           *pArgUnion,
    ICNotifyParam         *pParamUnion,
    ICOutParam           **ppOutParam
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;
    LPARAM       Arg = 0;
    LPARAM       Param = 0;

    *ppOutParam = (ICOutParam *)CoTaskMemAlloc(sizeof(ICOutParam));
    if (NULL == *ppOutParam)
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }

    ZeroMemory(*ppOutParam, sizeof(ICOutParam));

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // If the event required swaping Arg and IDataObject then swap it back
    // before calling into the object. For MMCN_QUERY_PASTE and
    // MMCN_RESTORE_VIEW we need to make Param contain the out pointer.
    
    switch (event)
    {
        case MMCN_PASTE:
            Arg = (LPARAM)piDataObject;
            piDataObject = pArgUnion->pidoPasteTarget;
            if (0 == pParamUnion->value)
            {
                // This is a copy, pass zero in Param so snap-in will know that
                Param = 0;
            }
            else
            {
                // This is a move. Pass the address of the IDataObject in
                // the ICOutParam that we will return to the proxy.
                Param = (LPARAM)&((*ppOutParam)->pidoCutOrMove);
            }
            break;

        case MMCN_QUERY_PASTE:
            Arg = (LPARAM)piDataObject;
            piDataObject = pArgUnion->pidoQueryPasteTarget;
            Param = pParamUnion->value;
            break;

        case MMCN_RESTORE_VIEW:
            Arg = pArgUnion->value;
            Param = (LPARAM)&((*ppOutParam)->fRestoreHandled);
            break;

        default:
            Arg = pArgUnion->value;
            Param = pParamUnion->value;
            break;
    }

    hr = This->lpVtbl->Notify(This, piDataObject, event, Arg, Param);

Cleanup:
    if (FAILED(hr))
    {
        if (NULL != *ppOutParam)
        {
            CoTaskMemFree(*ppOutParam);
            *ppOutParam = NULL;
        }
    }
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IComponent_CompareObjects_Proxy
( 
    IComponent __RPC_FAR *This,
    IDataObject          *piDataObjectA,
    IDataObject          *piDataObjectB
)
{
    HRESULT       hr = S_OK;

    BOOL          fIsMultiSelectA = FALSE;
    long          cDataObjectsA = 1L;
    IDataObject **ppiDataObjectsA = NULL;
    BOOL          fSpecialDataObjectA = FALSE;
    long          lSpecialDataObjectA = 0;

    BOOL          fIsMultiSelectB = FALSE;
    long          cDataObjectsB = 1L;
    IDataObject **ppiDataObjectsB = NULL;
    BOOL          fSpecialDataObjectB = FALSE;
    long          lSpecialDataObjectB = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObjectA, &fSpecialDataObjectA, &lSpecialDataObjectA);

    CheckForSpecialDataObjects(&piDataObjectB, &fSpecialDataObjectB, &lSpecialDataObjectB);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObjectA)
    {
        hr = IsMultiSelect(piDataObjectA, &fIsMultiSelectA);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (!fSpecialDataObjectB)
    {
        hr = IsMultiSelect(piDataObjectB, &fIsMultiSelectB);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelectA)
    {
        hr = InterpretMultiSelect(piDataObjectA, &cDataObjectsA, &ppiDataObjectsA);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjectsA = &piDataObjectA;
    }

    if (fIsMultiSelectB)
    {
        hr = InterpretMultiSelect(piDataObjectB, &cDataObjectsB, &ppiDataObjectsB);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjectsB = &piDataObjectB;
    }

    hr = IComponent_RemCompareObjects_Proxy(This,
                                            cDataObjectsA,
                                            ppiDataObjectsA,
                                            fSpecialDataObjectA,
                                            lSpecialDataObjectA,

                                            cDataObjectsB,
                                            ppiDataObjectsB,
                                            fSpecialDataObjectB,
                                            lSpecialDataObjectB);
Cleanup:
    if ( fIsMultiSelectA && (NULL != ppiDataObjectsA) )
    {
        (void)GlobalFree(ppiDataObjectsA);
    }
    if ( fIsMultiSelectB && (NULL != ppiDataObjectsB) )
    {
        (void)GlobalFree(ppiDataObjectsB);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IComponent_CompareObjects_Stub
( 
    IComponent __RPC_FAR             *This,

    long                              cDataObjectsA,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjectsA[  ],
    BOOL                              fSpecialDataObjectA,
    long                              lSpecialDataObjectA,

    long                              cDataObjectsB,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjectsB[  ],
    BOOL                              fSpecialDataObjectB,
    long                              lSpecialDataObjectB
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObjectA = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObjectA = NULL;
    IDataObject *piDataObjectB = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObjectB = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjectsA > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjectsA, cDataObjectsA,
                                      &piMultiSelDataObjectA);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObjectA = piMultiSelDataObjectA;
    }
    else if (fSpecialDataObjectA)
    {
        piDataObjectA = (IDataObject *)lSpecialDataObjectA;
    }
    else
    {
        piDataObjectA = ppiDataObjectsA[0];
    }

    if (cDataObjectsB > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjectsB, cDataObjectsB,
                                      &piMultiSelDataObjectB);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObjectB = piMultiSelDataObjectB;
    }
    else if (fSpecialDataObjectB)
    {
        piDataObjectB = (IDataObject *)lSpecialDataObjectB;
    }
    else
    {
        piDataObjectB = ppiDataObjectsB[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->CompareObjects(This, piDataObjectA, piDataObjectB);

Cleanup:
    if (NULL != piMultiSelDataObjectA)
    {
        piMultiSelDataObjectA->lpVtbl->Release(piMultiSelDataObjectA);
    }
    if (NULL != piMultiSelDataObjectB)
    {
        piMultiSelDataObjectB->lpVtbl->Release(piMultiSelDataObjectB);
    }
    return hr;
}

//=--------------------------------------------------------------------------=
//
//                      SCOPEDATAITEM Marshaling
//
//
//=--------------------------------------------------------------------------=
// Caveat: When returning a string in SCOPEDATAITEM MMC does not use a
// callee allocate/caller free strategy. When in-proc, the owner of the memory
// must insure that it stays alive for as long as the caller is expected to
// use it (e.g. scope item display name must remain valid for the life of the
// scope item). When out-of-proc, that returned string will be allocated by
// the proxy using CoTaskMemAlloc() and it will never be freed so there will
// be some leaks.
//=--------------------------------------------------------------------------=

static void SCOPEDATAITEM_TO_WIRE
(
    SCOPEDATAITEM      *psdi,
    WIRE_SCOPEDATAITEM *pwsdi
)
{
    pwsdi->mask = psdi->mask;
    pwsdi->nImage = psdi->nImage;
    pwsdi->nOpenImage = psdi->nOpenImage;
    pwsdi->nState = psdi->nState;
    pwsdi->cChildren = psdi->cChildren;
    pwsdi->lParam = psdi->lParam;
    pwsdi->relativeID = psdi->relativeID;
    pwsdi->ID = psdi->ID;

    if ( SDI_STR != (psdi->mask & SDI_STR) )
    {
        pwsdi->pwszDisplayName = NULL;
        pwsdi->fUsingCallbackForString = FALSE;
    }
    else if (MMC_CALLBACK == psdi->displayname)
    {
        pwsdi->pwszDisplayName = NULL;
        pwsdi->fUsingCallbackForString = TRUE;
    }
    else if (NULL == psdi->displayname)
    {
        pwsdi->pwszDisplayName = NULL;
        pwsdi->fUsingCallbackForString = FALSE;
    }
    else
    {
        // A string is being passed. Need to CoTaskMemAlloc() it so that
        // the MIDL generated stub can free it after transmission

        int cbString = (lstrlenW(psdi->displayname) + 1) * sizeof(psdi->displayname[0]);

        pwsdi->pwszDisplayName = (LPOLESTR)CoTaskMemAlloc(cbString);
        if (NULL == pwsdi->pwszDisplayName)
        {
            RpcRaiseException( E_OUTOFMEMORY );
        }
        else
        {
            memcpy(pwsdi->pwszDisplayName, psdi->displayname, cbString);
        }
        pwsdi->fUsingCallbackForString = FALSE;
    }
}


static void WIRE_TO_SCOPEDATAITEM
(
    WIRE_SCOPEDATAITEM *pwsdi,
    SCOPEDATAITEM      *psdi
)
{
    psdi->mask = pwsdi->mask;
    psdi->nImage = pwsdi->nImage;
    psdi->nOpenImage = pwsdi->nOpenImage;
    psdi->nState = pwsdi->nState;
    psdi->cChildren = pwsdi->cChildren;
    psdi->lParam = pwsdi->lParam;
    psdi->relativeID = pwsdi->relativeID;
    psdi->ID = pwsdi->ID;

    if ( SDI_STR != (psdi->mask & SDI_STR) )
    {
        psdi->displayname = NULL;
    }
    else if (pwsdi->fUsingCallbackForString)
    {
        psdi->displayname = MMC_CALLBACK;
    }
    else
    {
        psdi->displayname = pwsdi->pwszDisplayName;
    }
}


HRESULT STDMETHODCALLTYPE IComponentData_GetDisplayInfo_Proxy
( 
    IComponentData __RPC_FAR *This,
    SCOPEDATAITEM __RPC_FAR  *pScopeDataItem
)
{
    WIRE_SCOPEDATAITEM wsdi;
    HRESULT            hr;

    // Make sure the string pointer is NULL so that it is not marshaled as it
    // is never passed from MMC to the snap-in. (MMC might not have initialized
    // the pointer).

    pScopeDataItem->displayname = NULL;

    SCOPEDATAITEM_TO_WIRE(pScopeDataItem, &wsdi);

    hr =  IComponentData_RemGetDisplayInfo_Proxy(This, &wsdi);
    WIRE_TO_SCOPEDATAITEM(&wsdi, pScopeDataItem);

    return hr;
}


HRESULT STDMETHODCALLTYPE IComponentData_GetDisplayInfo_Stub
( 
    IComponentData __RPC_FAR     *This,
    WIRE_SCOPEDATAITEM __RPC_FAR *pwsdi
)
{
    SCOPEDATAITEM sdi;
    HRESULT       hr;

    WIRE_TO_SCOPEDATAITEM(pwsdi, &sdi);
    hr = This->lpVtbl->GetDisplayInfo(This, &sdi);
    SCOPEDATAITEM_TO_WIRE(&sdi, pwsdi);

    return hr;
}



HRESULT STDMETHODCALLTYPE IConsoleNameSpace_InsertItem_Proxy
( 
    IConsoleNameSpace __RPC_FAR *This,
    LPSCOPEDATAITEM              pItem
)
{
    WIRE_SCOPEDATAITEM  wsdi;
    HRESULT             hr;
    HSCOPEITEM          ItemID;

    SCOPEDATAITEM_TO_WIRE(pItem, &wsdi);
    hr = IConsoleNameSpace_RemInsertItem_Proxy(This, &wsdi, &ItemID);

    // The only returned field is the item ID so copy it from the wire
    // structure to the client structure

    pItem->ID = ItemID;

    return hr;
}


HRESULT STDMETHODCALLTYPE IConsoleNameSpace_InsertItem_Stub
( 
    IConsoleNameSpace __RPC_FAR  *This,
    WIRE_SCOPEDATAITEM __RPC_FAR *pwsdi,
    HSCOPEITEM __RPC_FAR         *pItemID
)
{
    SCOPEDATAITEM sdi;
    HRESULT       hr;

    WIRE_TO_SCOPEDATAITEM(pwsdi, &sdi);
    hr = This->lpVtbl->InsertItem(This, &sdi);

    // The only returned field is the itemID.

    *pItemID = sdi.ID;
    return hr;
}

HRESULT STDMETHODCALLTYPE IConsoleNameSpace_SetItem_Proxy
( 
    IConsoleNameSpace __RPC_FAR *This,
    LPSCOPEDATAITEM              pItem
)
{
    WIRE_SCOPEDATAITEM wsdi;

    SCOPEDATAITEM_TO_WIRE(pItem, &wsdi);
    return IConsoleNameSpace_RemSetItem_Proxy(This, &wsdi);
}


HRESULT STDMETHODCALLTYPE IConsoleNameSpace_SetItem_Stub
( 
    IConsoleNameSpace __RPC_FAR  *This,
    WIRE_SCOPEDATAITEM __RPC_FAR *pwsdi
)
{
    SCOPEDATAITEM sdi;

    WIRE_TO_SCOPEDATAITEM(pwsdi, &sdi);
    return This->lpVtbl->SetItem(This, &sdi);
}


HRESULT STDMETHODCALLTYPE IConsoleNameSpace_GetItem_Proxy
( 
    IConsoleNameSpace __RPC_FAR *This,
    LPSCOPEDATAITEM              pItem
)
{
    WIRE_SCOPEDATAITEM wsdi;
    HRESULT            hr;

    // Make sure the string pointer is NULL so that it is not marshaled as it
    // is never passed from the snap-in to MMC. (It might not have be
    // initialized).

    pItem->displayname = NULL;

    SCOPEDATAITEM_TO_WIRE(pItem, &wsdi);
    hr = IConsoleNameSpace_RemGetItem_Proxy(This, &wsdi);
    WIRE_TO_SCOPEDATAITEM(&wsdi, pItem);
    return hr;
}


HRESULT STDMETHODCALLTYPE IConsoleNameSpace_GetItem_Stub
( 
    IConsoleNameSpace __RPC_FAR  *This,
    WIRE_SCOPEDATAITEM __RPC_FAR *pwsdi
)
{
    SCOPEDATAITEM sdi;
    HRESULT       hr;

    WIRE_TO_SCOPEDATAITEM(pwsdi, &sdi);
    hr = This->lpVtbl->GetItem(This, &sdi);
    SCOPEDATAITEM_TO_WIRE(&sdi, pwsdi);

    return hr;
}






//=--------------------------------------------------------------------------=
//
//                      RESULTDATAITEM Marshaling
//
//
//=--------------------------------------------------------------------------=
// Caveat: When returning a string in RESULTDATAITEM MMC does not use a
// callee allocate/caller free strategy. When in-proc, the owner of the memory
// must insure that it stays alive for as long as the caller is expected to
// use it (e.g. list item column data must remain valid for the life of the
// list item). When out-of-proc, that returned string will be allocated by
// the proxy using SysAllocString() and it will never be freed so there will
// be some leaks.
//=--------------------------------------------------------------------------=

static void RESULTDATAITEM_TO_WIRE
(
    RESULTDATAITEM      *prdi,
    WIRE_RESULTDATAITEM *pwrdi
)
{
    pwrdi->mask = prdi->mask;
    pwrdi->bScopeItem = prdi->bScopeItem;
    pwrdi->itemID = prdi->itemID;
    pwrdi->nIndex = prdi->nIndex;
    pwrdi->nCol = prdi->nCol;
    pwrdi->nImage = prdi->nImage;
    pwrdi->nState = prdi->nState;
    pwrdi->lParam = prdi->lParam;
    pwrdi->iIndent = prdi->iIndent;

    if ( RDI_STR != (prdi->mask & RDI_STR) )
    {
        pwrdi->str = NULL;
        pwrdi->fUsingCallbackForString = FALSE;
    }
    else if (MMC_CALLBACK == prdi->str)
    {
        pwrdi->str = NULL;
        pwrdi->fUsingCallbackForString = TRUE;
    }
    else if (NULL == prdi->str)
    {
        pwrdi->str = NULL;
        pwrdi->fUsingCallbackForString = FALSE;
    }
    else
    {
        // A string is being passed. Need to CoTaskMemAlloc() it so that
        // the MIDL generated stub can free it after transmission

        int cbString = (lstrlenW(prdi->str) + 1) * sizeof(prdi->str[0]);

        pwrdi->str = (LPOLESTR)CoTaskMemAlloc(cbString);
        if (NULL == pwrdi->str)
        {
            RpcRaiseException( E_OUTOFMEMORY );
        }
        else
        {
            memcpy(pwrdi->str, prdi->str, cbString);
        }
        pwrdi->fUsingCallbackForString = FALSE;
    }
}



static void WIRE_TO_RESULTDATAITEM
(
    WIRE_RESULTDATAITEM *pwrdi,
    RESULTDATAITEM      *prdi
)
{
    prdi->mask = pwrdi->mask;
    prdi->bScopeItem = pwrdi->bScopeItem;
    prdi->itemID = pwrdi->itemID;
    prdi->nIndex = pwrdi->nIndex;
    prdi->nCol = pwrdi->nCol;
    prdi->nImage = pwrdi->nImage;
    prdi->nState = pwrdi->nState;
    prdi->lParam = pwrdi->lParam;
    prdi->iIndent = pwrdi->iIndent;

    if ( RDI_STR != (prdi->mask & RDI_STR) )
    {
        prdi->str = NULL;
    }
    else if (pwrdi->fUsingCallbackForString)
    {
        prdi->str = MMC_CALLBACK;
    }
    else
    {
        prdi->str = pwrdi->str;
    }
}

 
HRESULT STDMETHODCALLTYPE IComponent_GetDisplayInfo_Proxy
( 
    IComponent __RPC_FAR     *This,
    RESULTDATAITEM __RPC_FAR *pResultDataItem
)
{
    WIRE_RESULTDATAITEM wrdi;
    HRESULT             hr;

    // Make sure the string pointer is NULL so that it is not marshaled as it
    // is never passed from MMC to the snap-in. (MMC might not have initialized
    // the pointer).

    pResultDataItem->str = NULL;

    RESULTDATAITEM_TO_WIRE(pResultDataItem, &wrdi);

    hr =  IComponent_RemGetDisplayInfo_Proxy(This, &wrdi);
    WIRE_TO_RESULTDATAITEM(&wrdi, pResultDataItem);

    return hr;
}


HRESULT STDMETHODCALLTYPE IComponent_GetDisplayInfo_Stub
( 
    IComponent __RPC_FAR          *This,
    WIRE_RESULTDATAITEM __RPC_FAR *pwrdi
)
{
    RESULTDATAITEM rdi;
    HRESULT        hr;

    WIRE_TO_RESULTDATAITEM(pwrdi, &rdi);
    hr = This->lpVtbl->GetDisplayInfo(This, &rdi);
    RESULTDATAITEM_TO_WIRE(&rdi, pwrdi);

    return hr;
}


HRESULT STDMETHODCALLTYPE IResultData_InsertItem_Proxy
( 
    IResultData __RPC_FAR *This,
    LPRESULTDATAITEM       pItem
)
{
    WIRE_RESULTDATAITEM wrdi;
    HRESULT             hr;
    HRESULTITEM         ItemID;

    RESULTDATAITEM_TO_WIRE(pItem, &wrdi);
    hr = IResultData_RemInsertItem_Proxy(This, &wrdi, &ItemID);

    // The only returned field is the itemID so copy it from the wire
    // structure to the client structure

    pItem->itemID = ItemID;

    return hr;
}


HRESULT STDMETHODCALLTYPE IResultData_InsertItem_Stub
( 
    IResultData __RPC_FAR         *This,
    WIRE_RESULTDATAITEM __RPC_FAR *pwrdi,
    HRESULTITEM __RPC_FAR         *pItemID
)
{
    RESULTDATAITEM rdi;
    HRESULT        hr;

    WIRE_TO_RESULTDATAITEM(pwrdi, &rdi);
    hr = This->lpVtbl->InsertItem(This, &rdi);

    // The only returned field is the itemID.

    *pItemID = rdi.itemID;
    return hr;
}

HRESULT STDMETHODCALLTYPE IResultData_SetItem_Proxy
( 
    IResultData __RPC_FAR *This,
    LPRESULTDATAITEM       pItem
)
{
    WIRE_RESULTDATAITEM wrdi;

    RESULTDATAITEM_TO_WIRE(pItem, &wrdi);
    return IResultData_RemSetItem_Proxy(This, &wrdi);
}


HRESULT STDMETHODCALLTYPE IResultData_SetItem_Stub
( 
    IResultData __RPC_FAR         *This,
    WIRE_RESULTDATAITEM __RPC_FAR *pwrdi
)
{
    RESULTDATAITEM rdi;

    WIRE_TO_RESULTDATAITEM(pwrdi, &rdi);
    return This->lpVtbl->SetItem(This, &rdi);
}

HRESULT STDMETHODCALLTYPE IResultData_GetItem_Proxy
( 
    IResultData __RPC_FAR *This,
    LPRESULTDATAITEM       pItem
)
{
    WIRE_RESULTDATAITEM wrdi;
    HRESULT             hr;

    // Make sure the string pointer is NULL so that it is not marshaled as it
    // is never passed from the snap-in to MMC. (It might not have be
    // initialized).

    pItem->str = NULL;

    RESULTDATAITEM_TO_WIRE(pItem, &wrdi);
    hr = IResultData_RemGetItem_Proxy(This, &wrdi);
    WIRE_TO_RESULTDATAITEM(&wrdi, pItem);
    return hr;
}


HRESULT STDMETHODCALLTYPE IResultData_GetItem_Stub
( 
    IResultData __RPC_FAR         *This,
    WIRE_RESULTDATAITEM __RPC_FAR *pwrdi
)
{
    RESULTDATAITEM rdi;
    HRESULT        hr;

    WIRE_TO_RESULTDATAITEM(pwrdi, &rdi);
    hr = This->lpVtbl->GetItem(This, &rdi);
    RESULTDATAITEM_TO_WIRE(&rdi, pwrdi);

    return hr;
}

HRESULT STDMETHODCALLTYPE IResultData_GetNextItem_Proxy
( 
    IResultData __RPC_FAR *This,
    LPRESULTDATAITEM       pItem
)
{
    WIRE_RESULTDATAITEM wrdi;
    HRESULT             hr;

    // Make sure the string pointer is NULL so that it is not marshaled as it
    // is never passed from the snap-in to MMC. (It might not have be
    // initialized).

    pItem->str = NULL;

    RESULTDATAITEM_TO_WIRE(pItem, &wrdi);
    hr = IResultData_RemGetNextItem_Proxy(This, &wrdi);
    WIRE_TO_RESULTDATAITEM(&wrdi, pItem);
    return hr;
}


HRESULT STDMETHODCALLTYPE IResultData_GetNextItem_Stub
( 
    IResultData __RPC_FAR         *This,
    WIRE_RESULTDATAITEM __RPC_FAR *pwrdi
)
{
    RESULTDATAITEM rdi;
    HRESULT        hr;

    WIRE_TO_RESULTDATAITEM(pwrdi, &rdi);
    hr = This->lpVtbl->GetNextItem(This, &rdi);
    RESULTDATAITEM_TO_WIRE(&rdi, pwrdi);
    return hr;
}


//=--------------------------------------------------------------------------=
//
//                              HICON Marshaling
//
//=--------------------------------------------------------------------------=
// In wtypes.idl HICON is defined with the wire_marshal attribute with its
// 'on-the-wire' type as a pointer to a RemotableHandle. RemotableHandle is
// defined in wtypes.idl as
// 
// typedef union _RemotableHandle switch( long fContext ) u
// {
//     case WDT_INPROC_CALL:   long   hInproc;
//     case WDT_REMOTE_CALL:   long   hRemote;
// } RemotableHandle;
// 
// A wire_marshal type must supply routines to size, marhsal, unmarshal, and
// free marshaling data. Those routines are in ole32.dll but someone forgot to
// export them. (ole32 also has routines to marshal bitmaps, hwnds, etc. that
// are all exported). The code has been plagiarized here from ole32. The source
// is in \\savik\cairo\src\ole32\oleprx32\proxy\transmit.cxx with some macros in
// transmit.h in that same directory.
//
//=--------------------------------------------------------------------------=


//
// The following defines and macros are from transmit.h. Note that
// USER_CALL_CTXT_MASK is from rpcndr.h and WDT_REMOTE_CALL is from wtypes.idl.
//

#define ALIGN( pStuff, cAlign ) \
        pStuff = (unsigned char *)((ULONG_PTR)((pStuff) + (cAlign)) & ~ (cAlign))

#define LENGTH_ALIGN( Length, cAlign ) \
                                    Length = (((Length) + (cAlign)) & ~ (cAlign))

#define PULONG_LV_CAST   *(unsigned long __RPC_FAR * __RPC_FAR *)&


#define DIFFERENT_MACHINE_CALL( Flags)  \
                          (USER_CALL_CTXT_MASK(Flags) == MSHCTX_DIFFERENTMACHINE)

#define WDT_HANDLE_MARKER      WDT_INPROC_CALL




//=--------------------------------------------------------------------------=
// HICON_UserSize
//=--------------------------------------------------------------------------=
//
// Parameters:
//  unsigned long __RPC_FAR *pFlags       [in] data format & context (see below)
//  unsigned long            StartingSize [in] current buffer size
//  HICON __RPC_FAR         *hIcon        [in] HICON to be marshaled
//    
//
// Flags Layout
// ============
//
//----------------------------------------------------------------------
// Bits     Flag                            Value
//----------------------------------------------------------------------
// 31-24    Floating-point representation   0 = IEEE
//                                          1 = VAX
//                                          2 = Cray
//                                          3 = IBM 
//----------------------------------------------------------------------
// 23-20    Integer and floating-point byte
//          order                           0 = Big-endian
//                                          1 = Little-endian 
//----------------------------------------------------------------------
// 19-16    Character representation        0 = ASCII
//                                          1 = EBCDIC 
//----------------------------------------------------------------------
// 15-0     Marshaling context flag         0 = MSHCTX_LOCAL
//                                          1 = MSHCTX_NOSHAREDMEM
//                                          2 = MSHCTX_DIFFERENTMSCHINE
//                                          3 = MSHCTX_INPROC 
//----------------------------------------------------------------------
//
// Output:
//      New buffer size after adding amount needed for HICON marshaled data
//
// Notes:
//
// Called from MIDL generated proxy to determine buffer size needed for 
// marhaled data.
//

unsigned long __RPC_USER HICON_UserSize
(
    unsigned long __RPC_FAR *pFlags,
    unsigned long            StartingSize,
    HICON __RPC_FAR         *hIcon
)
{
    if (NULL == hIcon)
    {
        return StartingSize;
    }

    // If marshaling context is to a different machine then we don't support
    // that.

    if ( DIFFERENT_MACHINE_CALL(*pFlags) )
    {
        RpcRaiseException( RPC_S_INVALID_TAG );
    }

    // Make sure that our data will fall at a long boundary

    LENGTH_ALIGN( StartingSize, 3 );

    //Add the length
    
    return StartingSize + 8;
}


//=--------------------------------------------------------------------------=
// HICON_UserMarhsal
//=--------------------------------------------------------------------------=
//
// Parameters:
//  unsigned long __RPC_FAR  *pFlags  [in] data format & context (see above)
//  unsigned char  __RPC_FAR *pBuffer [in] current buffer size
//  HICON __RPC_FAR          *hIcon   [in] HICON to be marshaled
//    
//
// Output:
//      Pointer to buffer location following HICON's marshaling data
//
// Notes:
//
// Called from MIDL generated proxy to marshal an HICON
//

unsigned char __RPC_FAR * __RPC_USER HICON_UserMarshal
(
    unsigned long __RPC_FAR  *pFlags,
    unsigned char  __RPC_FAR *pBuffer,
    HICON __RPC_FAR          *hIcon
)
{
    if (NULL == hIcon)
    {
        return pBuffer;
    }

    if ( DIFFERENT_MACHINE_CALL(*pFlags) )
    {
        RpcRaiseException( RPC_S_INVALID_TAG );
    }

    // Make sure that our data will fall at a long boundary

    ALIGN( pBuffer, 3 );

    *( PULONG_LV_CAST pBuffer)++ = WDT_HANDLE_MARKER;
    *( PULONG_LV_CAST pBuffer)++ = *((long *)hIcon);

    return pBuffer;
}

//=--------------------------------------------------------------------------=
// HICON_UserUnmarhsal
//=--------------------------------------------------------------------------=
//
// Parameters:
//  unsigned long __RPC_FAR  *pFlags  [in] data format & context (see above)
//  unsigned char  __RPC_FAR *pBuffer [in] current buffer size
//  HICON __RPC_FAR          *hIcon   [in] HICON to be marshaled
//    
//
// Output:
//      Pointer to buffer location following HICON's marshaling data
//
// Notes:
//
// Called from MIDL generated stub to unmarshal an HICON
//

unsigned char __RPC_FAR *__RPC_USER HICON_UserUnmarshal
(
    unsigned long __RPC_FAR  *pFlags,
    unsigned char  __RPC_FAR *pBuffer,
    HICON __RPC_FAR          *hIcon
)
{
    unsigned long HandleMarker;

    ALIGN( pBuffer, 3 );

    HandleMarker = *( PULONG_LV_CAST pBuffer)++;

    if ( HandleMarker == WDT_HANDLE_MARKER )
        *((long *)hIcon) = *( PULONG_LV_CAST pBuffer)++;
    else
        RpcRaiseException( RPC_S_INVALID_TAG );

    return pBuffer;
}



//=--------------------------------------------------------------------------=
// HICON_UserUnmarhsal
//=--------------------------------------------------------------------------=
//
// Parameters:
//  unsigned long __RPC_FAR  *pFlags  [in] data format & context (see above)
//  HICON __RPC_FAR          *hIcon   [in] HICON that was unmarshaled
//    
//
// Output:
//      None
//
// Notes:
//
// Called from MIDL generated stub to free any associated marshaling data
// allocated during unmarshaling for embedded pointers. Not used for HICON.
//

void __RPC_USER HICON_UserFree
(
    unsigned long __RPC_FAR *pFlags,
    HICON __RPC_FAR         *hIcon
)
{
}



//=--------------------------------------------------------------------------=
//
//                          IImageList Marshaling
//
//
//=--------------------------------------------------------------------------=
// These methods needed call_as because the HICON and HBITMAP parameters are
// specified as long pointers in the original IDL.
//=--------------------------------------------------------------------------=

HRESULT STDMETHODCALLTYPE IImageList_ImageListSetIcon_Proxy
( 
    IImageList __RPC_FAR *This,
    LONG_PTR __RPC_FAR   *pIcon,
    long                  nLoc
)
{
    return IImageList_RemImageListSetIcon_Proxy(This, (HICON)pIcon, nLoc);
}


HRESULT STDMETHODCALLTYPE IImageList_ImageListSetIcon_Stub
( 
    IImageList __RPC_FAR *This,
    HICON                 hIcon,
    long                  nLoc
)
{
    return This->lpVtbl->ImageListSetIcon(This, (LONG_PTR __RPC_FAR*)hIcon, nLoc);
}

HRESULT STDMETHODCALLTYPE IImageList_ImageListSetStrip_Proxy
( 
    IImageList __RPC_FAR *This,
    LONG_PTR __RPC_FAR   *pBMapSm,
    LONG_PTR __RPC_FAR   *pBMapLg,
    long                  nStartLoc,
    COLORREF              cMask
)
{
    return IImageList_RemImageListSetStrip_Proxy(This,
                                                 (HBITMAP)pBMapSm,
                                                 (HBITMAP)pBMapLg,
                                                 nStartLoc,
                                                 cMask);
}


HRESULT STDMETHODCALLTYPE IImageList_ImageListSetStrip_Stub
( 
    IImageList __RPC_FAR *This,
    HBITMAP               hbmSmall,
    HBITMAP               hbmLarge,
    long                  nStartLoc,
    COLORREF              cMask
)
{
    return This->lpVtbl->ImageListSetStrip(This,
                                           (LONG_PTR __RPC_FAR*)hbmSmall,
                                           (LONG_PTR __RPC_FAR*)hbmLarge,
                                           nStartLoc,
                                           cMask);
}

HRESULT STDMETHODCALLTYPE IExtendPropertySheet_CreatePropertyPages_Proxy
( 
    IExtendPropertySheet __RPC_FAR *This,
    LPPROPERTYSHEETCALLBACK         lpProvider,
    LONG_PTR                        handle,
    LPDATAOBJECT                    lpIDataObject
)
{
    HRESULT                      hr = S_OK;
    WIRE_PROPERTYPAGES          *pPages = NULL;
    WIRE_PROPERTYPAGE           *pPage = NULL;
    ULONG                        i = 0;
    ULONG                        j = 0;
    IExtendPropertySheetRemote  *piExtendPropertySheetRemote = NULL;
    IRemotePropertySheetManager *piRemotePropertySheetManager = NULL;

    // Make sure the snap-in knows we are remoted. We do this here because
    // this is the first opportunity for the proxy to inform a property page
    // extension that it is remote and pass it data such as the MMC.exe path
    // and the MMC command line.

    hr = SetRemote((IUnknown *)This);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // Call the IExtendPropertySheetRemote method which will return a filled in
    // WIRE_PROPERTYPAGES from the remoted snap-in.

    hr = This->lpVtbl->QueryInterface(This, &IID_IExtendPropertySheetRemote,
                                      (void **)&piExtendPropertySheetRemote);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    hr = piExtendPropertySheetRemote->lpVtbl->CreatePropertyPageDefs(
                            piExtendPropertySheetRemote, lpIDataObject, &pPages);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // If there are pages (snap-in might not have added any) then
    // CoCreateInstance the remote property sheet manager using the clsid
    // returned in WIRE_PROPERTYPAGES. This object will be created in-proc here
    // on the MMC side.

    if (0 == pPages->cPages)
    {
        goto Cleanup;
    }

    hr = CoCreateInstance(&pPages->clsidRemotePropertySheetManager,
                          NULL, // no aggregation,
                          CLSCTX_INPROC_SERVER,
                          &IID_IRemotePropertySheetManager,
                          (void **)&piRemotePropertySheetManager);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // Pass the remote property sheet manager the WIRE_PROPERTYPAGES and let it
    // actually create the property pages and add them to the sheet here on the
    // MMC side.

    hr = piRemotePropertySheetManager->lpVtbl->CreateRemotePages(
                                                    piRemotePropertySheetManager,
                                                    lpProvider,
                                                    handle,
                                                    lpIDataObject,
                                                    pPages);
Cleanup:
    if (NULL != piRemotePropertySheetManager)
    {
        piRemotePropertySheetManager->lpVtbl->Release(piRemotePropertySheetManager);
    }

    if (NULL != piExtendPropertySheetRemote)
    {
        piExtendPropertySheetRemote->lpVtbl->Release(piExtendPropertySheetRemote);
    }

    // Free the WIRE_PROPERTYPAGES and all of its contents.

    if (NULL != pPages)
    {
        // Release the object and free the title for each individual page
        
        for (i = 0, pPage = &pPages->aPages[0]; i < pPages->cPages; i++, pPage++)
        {
            if (NULL != pPage->apunkObjects)
            {
                for (j = 0; j < pPage->cObjects; j++)
                {
                    if (NULL != pPage->apunkObjects[j])
                    {
                        pPage->apunkObjects[j]->lpVtbl->Release(pPage->apunkObjects[j]);
                    }
                }
                CoTaskMemFree(pPage->apunkObjects);
            }
            if (NULL != pPage->pwszTitle)
            {
                CoTaskMemFree(pPage->pwszTitle);
            }
        }

        // Free the ProgID prefix

        if (NULL != pPages->pwszProgIDStart)
        {
            CoTaskMemFree(pPages->pwszProgIDStart);
        }

        // Free all of the snap-in's property page info

        if (NULL != pPages->pPageInfos)
        {
            for (i = 0; i < pPages->pPageInfos->cPages; i++)
            {
                if (NULL != pPages->pPageInfos->aPageInfo[i].pwszTitle)
                {
                    CoTaskMemFree(pPages->pPageInfos->aPageInfo[i].pwszTitle);
                }
                if (NULL != pPages->pPageInfos->aPageInfo[i].pwszProgID)
                {
                    CoTaskMemFree(pPages->pPageInfos->aPageInfo[i].pwszProgID);
                }
            }
            CoTaskMemFree(pPages->pPageInfos);
        }

        // Free all of the objects associated with the sheet

        if (NULL != pPages->apunkObjects)
        {
            for (i = 0; i < pPages->cObjects; i++)
            {
                if (NULL != pPages->apunkObjects[i])
                {
                    pPages->apunkObjects[i]->lpVtbl->Release(pPages->apunkObjects[i]);
                }
            }
            CoTaskMemFree(pPages->apunkObjects);
        }


        // Release the extra object and the WIRE_PROPERTYPAGES struct itself

        if (NULL != pPages->punkExtra)
        {
            pPages->punkExtra->lpVtbl->Release(pPages->punkExtra);
        }
        CoTaskMemFree(pPages);
    }

    return hr;
}


//=--------------------------------------------------------------------------=
// IExtendPropertySheet_CreatePropertyPages_Stub
//=--------------------------------------------------------------------------=
//
// Parameters:
//      IExtendPropertySheet __RPC_FAR *This [in] this pointer
//
// Output:
//
// Notes:
//
// This stub is never called because
// IExtendPropertySheet_CreatePropertyPages_Proxy() (see above) reroutes the
// call to IExtendPropertySheetRemote::CreatePropertyPageDefs().
//

HRESULT STDMETHODCALLTYPE IExtendPropertySheet_CreatePropertyPages_Stub
( 
    IExtendPropertySheet __RPC_FAR *This
)
{
    return S_OK;
}



HRESULT STDMETHODCALLTYPE IExtendPropertySheet_QueryPagesFor_Proxy
( 
    IExtendPropertySheet __RPC_FAR *This,
    IDataObject                    *piDataObject
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendPropertySheet_RemQueryPagesFor_Proxy(This,
                                                     cDataObjects,
                                                     ppiDataObjects,
                                                     fSpecialDataObject,
                                                     lSpecialDataObject);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendPropertySheet_QueryPagesFor_Stub
( 
    IExtendPropertySheet __RPC_FAR   *This,
    long                              cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjects[  ],
    BOOL                              fSpecialDataObject,
    long                              lSpecialDataObject
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->QueryPagesFor(This, piDataObject);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}




HRESULT STDMETHODCALLTYPE IExtendPropertySheet2_GetWatermarks_Proxy
( 
    IExtendPropertySheet2 __RPC_FAR *This,
    IDataObject                     *piDataObject,
    HBITMAP                         *lphWatermark,
    HBITMAP                         *lphHeader,
    HPALETTE                        *lphPalette,
    BOOL                            *bStretch
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendPropertySheet2_RemGetWatermarks_Proxy(This,
                                                      cDataObjects,
                                                      ppiDataObjects,
                                                      fSpecialDataObject,
                                                      lSpecialDataObject,
                                                      lphWatermark,
                                                      lphHeader,
                                                      lphPalette,
                                                      bStretch);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendPropertySheet2_GetWatermarks_Stub
( 
    IExtendPropertySheet2 __RPC_FAR   *This,
    long                              cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjects[  ],
    BOOL                              fSpecialDataObject,
    long                              lSpecialDataObject,
    HBITMAP                          *lphWatermark,
    HBITMAP                          *lphHeader,
    HPALETTE                         *lphPalette,
    BOOL                             *bStretch
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->GetWatermarks(This,
                                     piDataObject,
                                     lphWatermark,
                                     lphHeader,
                                     lphPalette,
                                     bStretch);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendPropertySheetRemote_CreatePropertyPageDefs_Proxy
( 
    IExtendPropertySheetRemote __RPC_FAR  *This,
    IDataObject                           *piDataObject,
    WIRE_PROPERTYPAGES                   **ppPages
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendPropertySheetRemote_RemCreatePropertyPageDefs_Proxy(
                                                             This,
                                                             cDataObjects,
                                                             ppiDataObjects,
                                                             fSpecialDataObject,
                                                             lSpecialDataObject,
                                                             ppPages);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendPropertySheetRemote_CreatePropertyPageDefs_Stub
( 
    IExtendPropertySheetRemote __RPC_FAR  *This,
    long                                   cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR       ppiDataObjects[  ],
    BOOL                                   fSpecialDataObject,
    long                                   lSpecialDataObject,
    WIRE_PROPERTYPAGES                   **ppPages
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->CreatePropertyPageDefs(This, piDataObject, ppPages);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}



HRESULT STDMETHODCALLTYPE IPropertySheetProvider_CreatePropertySheet_Proxy
( 
    IPropertySheetProvider __RPC_FAR *This,
    LPCWSTR                           title,
    boolean                           type,
    MMC_COOKIE                        cookie,
    IDataObject                      *piDataObject,
    DWORD                             dwOptions
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IPropertySheetProvider_RemCreatePropertySheet_Proxy(This,
                                                             title,
                                                             type,
                                                             cookie,
                                                             cDataObjects,
                                                             ppiDataObjects,
                                                             fSpecialDataObject,
                                                             lSpecialDataObject,
                                                             dwOptions);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IPropertySheetProvider_CreatePropertySheet_Stub
( 
    IPropertySheetProvider __RPC_FAR *This,
    LPCWSTR                           title,
    boolean                           type,
    MMC_COOKIE                        cookie,
    long                              cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjects[  ],
    BOOL                              fSpecialDataObject,
    long                              lSpecialDataObject,
    DWORD                             dwOptions
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->CreatePropertySheet(This, title, type, cookie, piDataObject, dwOptions);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}



HRESULT STDMETHODCALLTYPE IPropertySheetProvider_FindPropertySheet_Proxy
( 
    IPropertySheetProvider __RPC_FAR *This,
    MMC_COOKIE                        cookie,
    IComponent                       *piComponent,
    IDataObject                      *piDataObject
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IPropertySheetProvider_RemFindPropertySheet_Proxy(This,
                                                           cookie,
                                                           piComponent,
                                                           cDataObjects,
                                                           ppiDataObjects,
                                                           fSpecialDataObject,
                                                           lSpecialDataObject);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IPropertySheetProvider_FindPropertySheet_Stub
( 
    IPropertySheetProvider __RPC_FAR *This,
    MMC_COOKIE                        cookie,
    IComponent                       *piComponent,
    long                              cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjects[  ],
    BOOL                              fSpecialDataObject,
    long                              lSpecialDataObject
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->FindPropertySheet(This, cookie, piComponent, piDataObject);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}







HRESULT STDMETHODCALLTYPE IExtendContextMenu_AddMenuItems_Proxy
( 
    IExtendContextMenu __RPC_FAR *This,
    LPDATAOBJECT                  piDataObject,
    LPCONTEXTMENUCALLBACK         piCallback,
    long __RPC_FAR               *pInsertionAllowed
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Make sure the snap-in knows we are remoted. We do this here because
    // this is the first opportunity for the proxy to inform a context menu
    // extension that it is remote.

    hr = SetRemote((IUnknown *)This);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendContextMenu_RemAddMenuItems_Proxy(This,
                                                  cDataObjects,
                                                  ppiDataObjects,
                                                  fSpecialDataObject,
                                                  lSpecialDataObject,
                                                  piCallback,
                                                  pInsertionAllowed);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendContextMenu_AddMenuItems_Stub
( 
    IExtendContextMenu __RPC_FAR     *This,
    long                              cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjects[  ],
    BOOL                              fSpecialDataObject,
    long                              lSpecialDataObject,
    LPCONTEXTMENUCALLBACK             piCallback,
    long __RPC_FAR                   *pInsertionAllowed
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->AddMenuItems(This, piDataObject,
                                    piCallback, pInsertionAllowed);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}

HRESULT STDMETHODCALLTYPE IExtendContextMenu_Command_Proxy
( 
    IExtendContextMenu __RPC_FAR *This,
    long                          lCommandID,
    LPDATAOBJECT                  piDataObject
)
{
    HRESULT       hr = S_OK;
    BOOL          fIsMultiSelect = FALSE;
    long          cDataObjects = 1L;
    IDataObject **ppiDataObjects = NULL;
    BOOL          fSpecialDataObject = FALSE;
    long          lSpecialDataObject = 0;

    // Check for special data objects such DOBJ_CUSTOMWEB etc.

    CheckForSpecialDataObjects(&piDataObject, &fSpecialDataObject, &lSpecialDataObject);

    // If this is a mutliple selection then we need to extract the data
    // objects in the HGLOBAL

    if (!fSpecialDataObject)
    {
        hr = IsMultiSelect(piDataObject, &fIsMultiSelect);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }

    if (fIsMultiSelect)
    {
        hr = InterpretMultiSelect(piDataObject, &cDataObjects, &ppiDataObjects);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
    }
    else
    {
        ppiDataObjects = &piDataObject;
    }

    hr = IExtendContextMenu_RemCommand_Proxy(This,
                                             cDataObjects,
                                             ppiDataObjects,
                                             fSpecialDataObject,
                                             lSpecialDataObject,
                                             lCommandID);
Cleanup:
    if ( fIsMultiSelect && (NULL != ppiDataObjects) )
    {
        (void)GlobalFree(ppiDataObjects);
    }
    return hr;
}


HRESULT STDMETHODCALLTYPE IExtendContextMenu_Command_Stub
( 
    IExtendContextMenu __RPC_FAR     *This,
    long                              cDataObjects,
    IDataObject __RPC_FAR *__RPC_FAR  ppiDataObjects[  ],
    BOOL                              fSpecialDataObject,
    long                              lSpecialDataObject,
    long                              lCommandID
)
{
    HRESULT      hr = S_OK;
    IDataObject *piDataObject = NULL; // Not AddRef()ed
    IDataObject *piMultiSelDataObject = NULL;

    // If there is more than one data object then we need to pack them into a
    // a separate data object that appears as a multi-select data object.

    if (cDataObjects > 1L)
    {
        hr = CreateMultiSelDataObject(ppiDataObjects, cDataObjects,
                                      &piMultiSelDataObject);
        if (FAILED(hr))
        {
            goto Cleanup;
        }
        piDataObject = piMultiSelDataObject;
    }
    else if (fSpecialDataObject)
    {
        piDataObject = (IDataObject *)lSpecialDataObject;
    }
    else
    {
        piDataObject = ppiDataObjects[0];
    }

    // Call the snap-in

    hr = This->lpVtbl->Command(This, lCommandID, piDataObject);

Cleanup:
    if (NULL != piMultiSelDataObject)
    {
        piMultiSelDataObject->lpVtbl->Release(piMultiSelDataObject);
    }
    return hr;
}



HRESULT STDMETHODCALLTYPE IColumnData_GetColumnConfigData_Proxy
( 
    IColumnData __RPC_FAR                    *This,
    SColumnSetID __RPC_FAR                   *pColID,
    MMC_COLUMN_SET_DATA __RPC_FAR *__RPC_FAR *ppColSetData
)
{
    HRESULT              hr = S_OK;
    size_t               cbColData = 0;
    MMC_COLUMN_SET_DATA *pColSetData = NULL;

    // Call the proxy and get the returned data from MMC

    hr = IColumnData_RemGetColumnConfigData_Proxy(This, pColID, ppColSetData);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // If no data was returned then there is nothing to do

    if (NULL == *ppColSetData)
    {
        goto Cleanup;
    }

    if (NULL == (*ppColSetData)->pColData)
    {
        goto Cleanup;
    }

    // At this point the MIDL-generated proxy has returned an MMC_COLUMN_SET_DATA
    // in which the embedded pointer pColData points to a separate block of
    // memory which must be freed independently. The snap-in thinks that
    // pColData points into the same block of memory so it will only call
    // CoTaskMemFree for the MMC_COLUMN_SET_DATA. We need to allocate a new single
    // block in the format the snap-in is expecting and free the memory returned
    // from the proxy.

    cbColData = sizeof(MMC_COLUMN_DATA) * (*ppColSetData)->nNumCols;

    pColSetData = (MMC_COLUMN_SET_DATA *)CoTaskMemAlloc(
                                        sizeof(MMC_COLUMN_SET_DATA) + cbColData);
    
    if (NULL == pColSetData)
    {
        CoTaskMemFree((*ppColSetData)->pColData);
        CoTaskMemFree(*ppColSetData);
        *ppColSetData = NULL;
        hr = E_OUTOFMEMORY;
    }
    else
    {
        // Copy the column set data

        memcpy(pColSetData, (*ppColSetData), sizeof(MMC_COLUMN_SET_DATA));

        // Set the embedded pointer to point immediately following the
        // MMC_COLUMN_SET_DATA
        
        pColSetData->pColData = (MMC_COLUMN_DATA *)(pColSetData + 1);

        // Copy the column data

        memcpy(pColSetData->pColData, (*ppColSetData)->pColData, cbColData);

        // Free the data returned from the proxy
        
        CoTaskMemFree((*ppColSetData)->pColData);
        CoTaskMemFree(*ppColSetData);

        // Return the new single block pointer to the snap-in
        
        *ppColSetData = pColSetData;
    }

Cleanup:
    return hr;
}


HRESULT STDMETHODCALLTYPE IColumnData_GetColumnConfigData_Stub
( 
    IColumnData __RPC_FAR                    *This,
    SColumnSetID __RPC_FAR                   *pColID,
    MMC_COLUMN_SET_DATA __RPC_FAR *__RPC_FAR *ppColSetData
)
{
    HRESULT          hr = S_OK;
    size_t           cbColData = 0;
    MMC_COLUMN_DATA *pColData = NULL;

    // Call into MMC and get the returned data

    hr = This->lpVtbl->GetColumnConfigData(This, pColID, ppColSetData);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // If no data was returned then there is nothing to do

    if (NULL == *ppColSetData)
    {
        goto Cleanup;
    }

    if (NULL == (*ppColSetData)->pColData)
    {
        goto Cleanup;
    }

    // At this point MMC has returned a pointer to an MMC_COLUMN_SET_DATA
    // that contains an embedded pointer into the same block of memory (pColData).
    // MMC expects that the caller will make a single call to CoTaskMemFree().
    // The MIDL-generated stub thinks the embedded pointer needs to be freed
    // separately so we need to reconstruct the output to use two separate
    // blocks.

    // Allocate a new MMC_COLUMN_DATA array

    cbColData = sizeof(MMC_COLUMN_DATA) * (*ppColSetData)->nNumCols;

    pColData = (MMC_COLUMN_DATA *)CoTaskMemAlloc(cbColData);
    if (NULL == pColData)
    {
        CoTaskMemFree(*ppColSetData);
        *ppColSetData = NULL;
        hr = E_OUTOFMEMORY;
    }
    else
    {
        // Copy the column data
        memcpy(pColData, (*ppColSetData)->pColData, cbColData);

        // Overwrite the existing embedded pointer. There will be no memory leak
        // because that pointer pointed into the same block as the
        // MMC_COLUMN_SET_DATA and the stub will free the MMC_COLUMN_SET_DATA pointer.
        // Now both pointers can be freed independently as the stub expects.

        (*ppColSetData)->pColData = pColData;
    }

Cleanup:
    return hr;
}



HRESULT STDMETHODCALLTYPE IColumnData_GetColumnSortData_Proxy
( 
    IColumnData __RPC_FAR                  *This,
    SColumnSetID __RPC_FAR                 *pColID,
    MMC_SORT_SET_DATA __RPC_FAR *__RPC_FAR *ppColSortData
)
{
    HRESULT            hr = S_OK;
    size_t             cbSortData = 0;
    MMC_SORT_SET_DATA *pColSortData = NULL;

    // Call the proxy and get the returned data from MMC

    hr = IColumnData_RemGetColumnSortData_Proxy(This, pColID, ppColSortData);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // If no data was returned then there is nothing to do

    if (NULL == *ppColSortData)
    {
        goto Cleanup;
    }

    if (NULL == (*ppColSortData)->pSortData)
    {
        goto Cleanup;
    }

    // At this point the MIDL-generated proxy has returned an MMC_SORT_SET_DATA
    // in which the embedded pointer pSortData points to a separate block of
    // memory which must be freed independently. The snap-in thinks that
    // pSortData points into the same block of memory so it will only call
    // CoTaskMemFree for the MMC_SORT_SET_DATA. We need to allocate a new single
    // block in the format the snap-in is expecting and free the memory returned
    // from the proxy.

    cbSortData = sizeof(MMC_SORT_DATA) * (*ppColSortData)->nNumItems;

    pColSortData = (MMC_SORT_SET_DATA *)CoTaskMemAlloc(
                                         sizeof(MMC_SORT_SET_DATA) + cbSortData);

    if (NULL == pColSortData)
    {
        CoTaskMemFree((*ppColSortData)->pSortData);
        CoTaskMemFree(*ppColSortData);
        *ppColSortData = NULL;
        hr = E_OUTOFMEMORY;
    }
    else
    {
        // Copy the sort set data

        memcpy(pColSortData, (*ppColSortData), sizeof(MMC_SORT_SET_DATA));

        // Set the embedded pointer to point immediately following the
        // MMC_SORT_SET_DATA

        pColSortData->pSortData = (MMC_SORT_DATA *)(pColSortData + 1);

        // Copy the sort data

        memcpy(pColSortData->pSortData, (*ppColSortData)->pSortData, cbSortData);

        // Free the data returned from the proxy

        CoTaskMemFree((*ppColSortData)->pSortData);
        CoTaskMemFree(*ppColSortData);

        // Return the new single block pointer to the snap-in

        *ppColSortData = pColSortData;
    }

Cleanup:
    return hr;
}


HRESULT STDMETHODCALLTYPE IColumnData_GetColumnSortData_Stub
( 
    IColumnData __RPC_FAR                  *This,
    SColumnSetID __RPC_FAR                 *pColID,
    MMC_SORT_SET_DATA __RPC_FAR *__RPC_FAR *ppColSortData
)
{
    HRESULT        hr = S_OK;
    size_t         cbSortData = 0;
    MMC_SORT_DATA *pSortData = NULL;

    // Call into MMC and get the returned data

    hr = This->lpVtbl->GetColumnSortData(This, pColID, ppColSortData);
    if (FAILED(hr))
    {
        goto Cleanup;
    }

    // If no data was returned then there is nothing to do

    if (NULL == *ppColSortData)
    {
        goto Cleanup;
    }

    if (NULL == (*ppColSortData)->pSortData)
    {
        goto Cleanup;
    }

    // At this point MMC has returned a pointer to an MMC_SORT_SET_DATA
    // that contains an embedded pointer into the same block of memory (pSortData).
    // MMC expects that the caller will make a single call to CoTaskMemFree().
    // The MIDL-generated stub thinks the embedded pointer needs to be freed
    // separately so we need to reconstruct the output to use two separate
    // blocks.

    // Allocate a new MMC_SORT_DATA array

    cbSortData = sizeof(MMC_SORT_DATA) * (*ppColSortData)->nNumItems;

    pSortData = (MMC_SORT_DATA *)CoTaskMemAlloc(cbSortData);
    if (NULL == pSortData)
    {
        CoTaskMemFree(*ppColSortData);
        *ppColSortData = NULL;
        hr = E_OUTOFMEMORY;
    }
    else
    {
        // Copy the column data
        memcpy(pSortData, (*ppColSortData)->pSortData, cbSortData);

        // Overwrite the existing embedded pointer. There will be no memory leak
        // because that pointer pointed into the same block as the
        // MMC_SORT_SET_DATA and the stub will free the MMC_SORT_SET_DATA pointer.
        // Now both pointers can be freed independently as the stub expects.

        (*ppColSortData)->pSortData = pSortData;
    }

Cleanup:
    return hr;
}