//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
//=--------------------------------------------------------------------------=
// AutomationObject.Cpp
//=--------------------------------------------------------------------------=
//
// all of our objects will inherit from this class to share as much of the same
// code as possible.  this super-class contains the unknown, dispatch and
// error info implementations for them.
//
#include "IPServer.H"
#include "LocalSrv.H"

#include "AutoObj.H"
#include "Globals.H"
#include "StdEnum.H"
#include "Util.H"

// for ASSERT and FAIL
//
SZTHISFILE

// private function prototypes
//
void WINAPI CopyAndAddRefObject(void *, const void *, DWORD);

// used in FireEvent
//=---------------------------------------------------------------------------=
// this table is used for copying data around, and persisting properties.
// basically, it contains the size of a given data type
//

const BYTE g_rgcbDataTypeSize[] = {
    0,                      // VT_EMPTY= 0,
    0,                      // VT_NULL= 1,
    sizeof(short),          // VT_I2= 2,
    sizeof(long),           // VT_I4 = 3,
    sizeof(float),          // VT_R4  = 4,
    sizeof(double),         // VT_R8= 5,
    sizeof(CURRENCY),       // VT_CY= 6,
    sizeof(DATE),           // VT_DATE = 7,
    sizeof(BSTR),           // VT_BSTR = 8,
    sizeof(IDispatch *),    // VT_DISPATCH    = 9,
    sizeof(SCODE),          // VT_ERROR    = 10,
    sizeof(VARIANT_BOOL),   // VT_BOOL    = 11,
    sizeof(VARIANT),        // VT_VARIANT= 12,
    sizeof(IUnknown *),     // VT_UNKNOWN= 13,
};

//=--------------------------------------------------------------------------=
// CAutomationObject::CAutomationObject
//=--------------------------------------------------------------------------=
// create the object and initialize the refcount
//
// Parameters:
//    IUnknown *      - [in] controlling Unknown
//    int             - [in] the object type that we are
//    void *          - [in] the VTable of of the object we really are.
//
// Notes:
//
CAutomationObject::CAutomationObject 
(
    IUnknown *pUnkOuter,
    int   ObjType,
    void *pVTable
)
: CUnknownObject(pUnkOuter, pVTable), m_ObjectType (ObjType)
{
    m_fLoadedTypeInfo = FALSE;
    InitializeCriticalSection(&m_CSlocal);
}


//=--------------------------------------------------------------------------=
// CAutomationObject::~CAutomationObject
//=--------------------------------------------------------------------------=
// "I have a rendezvous with Death, At some disputed barricade"
// - Alan Seeger (1888-1916)
//
// Notes:
//
CAutomationObject::~CAutomationObject ()
{
    // if we loaded up a type info, release our count on the globally stashed
    // type infos, and release if it becomes zero.
    //
    if (m_fLoadedTypeInfo) {

        // we have to crit sect this since it's possible to have more than
        // one thread partying with this object.
        //
        EnterCriticalSection(&g_CriticalSection);
        ASSERT(CTYPEINFOOFOBJECT(m_ObjectType), L"Bogus ref counting on the Type Infos");
        CTYPEINFOOFOBJECT(m_ObjectType)--;

        // if we're the last one, free that sucker!
        //
        if (!CTYPEINFOOFOBJECT(m_ObjectType)) {
            PTYPEINFOOFOBJECT(m_ObjectType)->Release();
            PTYPEINFOOFOBJECT(m_ObjectType) = NULL;
        }
        LeaveCriticalSection(&g_CriticalSection);
    }

    return;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::InternalQueryInterface
//=--------------------------------------------------------------------------=
// the controlling unknown will call this for us in the case where they're
// looking for a specific interface.
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CAutomationObject::InternalQueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    ASSERT(ppvObjOut, L"controlling Unknown should be checking this!");

    // start looking for the guids we support, namely IDispatch, and the
    //
    if (DO_GUIDS_MATCH(riid, IID_IDispatch)) {
        *ppvObjOut = (void *)(IDispatch *)m_pvInterface;
        ((IUnknown *)(*ppvObjOut))->AddRef();
        return S_OK;
    }

    // just get our parent class to process it from here on out.
    //
    return CUnknownObject::InternalQueryInterface(riid, ppvObjOut);
}

//=--------------------------------------------------------------------------=
// CAutomationObject::GetTypeInfoCount
//=--------------------------------------------------------------------------=
// returns the number of type information interfaces that the object provides
//
// Parameters:
//    UINT *            - [out] the number of interfaces supported.
//
// Output:
//    HRESULT           - S_OK, E_NOTIMPL, E_INVALIDARG
//
// Notes:
//
STDMETHODIMP CAutomationObject::GetTypeInfoCount
(
    UINT *pctinfo
)
{
    // arg checking
    //
    if (!pctinfo)
        return E_INVALIDARG;

    // we support GetTypeInfo, so we need to return the count here.
    //
    *pctinfo = 1;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::GetTypeInfo
//=--------------------------------------------------------------------------=
// Retrieves a type information object, which can be used to get the type
// information for an interface.
//
// Parameters:
//    UINT              - [in]  the type information they'll want returned
//    LCID              - [in]  the LCID of the type info we want
//    ITypeInfo **      - [out] the new type info object.
//
// Output:
//    HRESULT           - S_OK, E_INVALIDARG, etc.
//
// Notes:
//
STDMETHODIMP CAutomationObject::GetTypeInfo
(
    UINT        itinfo,
    LCID        lcid,
    ITypeInfo **ppTypeInfoOut
)
{
#if 1 // we are loading the type library from resources
    DWORD       dwPathLen;
    WCHAR       wszDllPath[MAX_PATH];
#endif // 1

    HRESULT     hr;
    ITypeLib   *pTypeLib;
    ITypeInfo **ppTypeInfo =NULL;

    // arg checking
    //
    if (itinfo != 0)
        return DISP_E_BADINDEX;

    if (!ppTypeInfoOut)
        return E_POINTER;

    *ppTypeInfoOut = NULL;

    // ppTypeInfo will point to our global holder for this particular
    // type info.  if it's null, then we have to load it up. if it's not
    // NULL, then it's already loaded, and we're happy.
    // crit sect this entire nightmare so we're okay with multiple
    // threads trying to use this object.
    //
    EnterCriticalSection(&g_CriticalSection);
    ppTypeInfo = PPTYPEINFOOFOBJECT(m_ObjectType);

    if (*ppTypeInfo == NULL) {

        ITypeInfo *pTypeInfoTmp;
        HREFTYPE   hrefType;

        // we don't have the type info around, so go load the sucker.
        //
        hr = LoadRegTypeLib(*g_pLibid, (USHORT)VERSIONOFOBJECT(m_ObjectType), 0,
                            LANG_NEUTRAL, &pTypeLib);

        // if, for some reason, we failed to load the type library this
        // way, we're going to try and load the type library directly out of
        // our resources.  this has the advantage of going and re-setting all
        // the registry information again for us.
        //
        if (FAILED(hr)) {
#if 1 // we don't actually have the type library in our resources...
            dwPathLen = GetModuleFileName(g_hInstance, wszDllPath, MAX_PATH);
            if (!dwPathLen) {
                hr = E_FAIL;
                goto CleanUp;
            }

            // MAKE_WIDEPTR_FROMANSI(pwsz, szDllPath);
            hr = LoadTypeLib(wszDllPath, &pTypeLib);
#endif // 0 
            CLEANUP_ON_FAILURE(hr);
        }

        // we've got the Type Library now, so get the type info for the interface
        // we're interested in.
        //
        hr = pTypeLib->GetTypeInfoOfGuid((REFIID)INTERFACEOFOBJECT(m_ObjectType), &pTypeInfoTmp);
        pTypeLib->Release();
        CLEANUP_ON_FAILURE(hr);

        // the following couple of lines of code are to dereference the dual
        // interface stuff and take us right to the dispatch portion of the
        // interfaces.
        //
        hr = pTypeInfoTmp->GetRefTypeOfImplType(0xffffffff, &hrefType);
        if (FAILED(hr)) {
            pTypeInfoTmp->Release();
            goto CleanUp;
        }

        hr = pTypeInfoTmp->GetRefTypeInfo(hrefType, ppTypeInfo);
        pTypeInfoTmp->Release();
        CLEANUP_ON_FAILURE(hr);

        // add an extra reference to this object.  if it ever becomes zero, then
        // we need to release it ourselves.  crit sect this since more than
        // one thread can party on this object.
        //
        CTYPEINFOOFOBJECT(m_ObjectType)++;
        m_fLoadedTypeInfo = TRUE;
    }


    // we still have to go and addref the Type info object, however, so that
    // the people using it can release it.
    //
    (*ppTypeInfo)->AddRef();
    *ppTypeInfoOut = *ppTypeInfo;
    hr = S_OK;

  CleanUp:
    LeaveCriticalSection(&g_CriticalSection);
    return hr;
}



//=--------------------------------------------------------------------------=
// CAutomationObject::GetIDsOfNames
//=--------------------------------------------------------------------------=
// Maps a single member and an optional set of argument names to a
// corresponding set of integer DISPIDs
//
// Parameters:
//    REFIID            - [in]  must be IID_NULL
//    OLECHAR **        - [in]  array of names to map.
//    UINT              - [in]  count of names in the array.
//    LCID              - [in]  LCID on which to operate
//    DISPID *          - [in]  place to put the corresponding DISPIDs.
//
// Output:
//    HRESULT           - S_OK, E_OUTOFMEMORY, DISP_E_UNKNOWNNAME,
//                        DISP_E_UNKNOWNLCID
//
// Notes:
//    - we're just going to use DispGetIDsOfNames to save us a lot of hassle,
//      and to let this superclass handle it.
//
STDMETHODIMP CAutomationObject::GetIDsOfNames
(
    REFIID    riid,
    OLECHAR **rgszNames,
    UINT      cNames,
    LCID      lcid,
    DISPID   *rgdispid
)
{
    HRESULT     hr;
    ITypeInfo  *pTypeInfo;

    if (!DO_GUIDS_MATCH(riid, IID_NULL))
        return E_INVALIDARG;

    // get the type info for this dude!
    //
    hr = GetTypeInfo(0, lcid, &pTypeInfo);
    RETURN_ON_FAILURE(hr);

    // use the standard provided routines to do all the work for us.
    //
    hr = pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgdispid);
    pTypeInfo->Release();

    return hr;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::Invoke
//=--------------------------------------------------------------------------=
// provides access to the properties and methods on this object.
//
// Parameters:
//    DISPID            - [in]  identifies the member we're working with.
//    REFIID            - [in]  must be IID_NULL.
//    LCID              - [in]  language we're working under
//    USHORT            - [in]  flags, propput, get, method, etc ...
//    DISPPARAMS *      - [in]  array of arguments.
//    VARIANT *         - [out] where to put result, or NULL if they don't care.
//    EXCEPINFO *       - [out] filled in in case of exception
//    UINT *            - [out] where the first argument with an error is.
//
// Output:
//    HRESULT           - tonnes of them.
//
// Notes:
//    
STDMETHODIMP CAutomationObject::Invoke
(
    DISPID      dispid,
    REFIID      riid,
    LCID        lcid,
    WORD        wFlags,
    DISPPARAMS *pdispparams,
    VARIANT    *pvarResult,
    EXCEPINFO  *pexcepinfo,
    UINT       *puArgErr
)
{
    HRESULT    hr;
    ITypeInfo *pTypeInfo;

    if (!DO_GUIDS_MATCH(riid, IID_NULL))
        return E_INVALIDARG;

    // get our typeinfo first!
    //
    hr = GetTypeInfo(0, lcid, &pTypeInfo);
    RETURN_ON_FAILURE(hr);

    // Clear exceptions
    //
    SetErrorInfo(0L, NULL);

    // This is exactly what DispInvoke does--so skip the overhead.
    //
    hr = pTypeInfo->Invoke(m_pvInterface, dispid, wFlags,
                           pdispparams, pvarResult,
                           pexcepinfo, puArgErr);
    pTypeInfo->Release();
    return hr;

}

//=--------------------------------------------------------------------------=
// CAutomationObject::Exception
//=--------------------------------------------------------------------------=
// fills in the rich error info object so that both our vtable bound interfaces
// and calls through ITypeInfo::Invoke get the right error informaiton.
//
// Parameters:
//    HRESULT          - [in] the SCODE that should be associated with this err
//    WORD             - [in] the RESOURCE ID of the error message.
//    DWORD            - [in] helpcontextid for the error
//
// Output:
//    HRESULT          - the HRESULT that was passed in.
//
// Notes:
//
HRESULT CAutomationObject::Exception
(
    HRESULT hrExcep,
    WORD    idException,
    DWORD   dwHelpContextID
)
{
    ICreateErrorInfo *pCreateErrorInfo;
    IErrorInfo *pErrorInfo;
    WCHAR   wszTmp[256];
   // char    szTmp[256];
    HRESULT hr;


    // first get the createerrorinfo object.
    //
    hr = CreateErrorInfo(&pCreateErrorInfo);
    if (FAILED(hr)) return hrExcep;

 //   MAKE_WIDEPTR_FROMANSI(wszHelpFile, HELPFILEOFOBJECT(m_ObjectType));

    // set up some default information on it.
    //
    pCreateErrorInfo->SetGUID((REFIID)INTERFACEOFOBJECT(m_ObjectType));
 //   pCreateErrorInfo->SetHelpFile(wszHelpFile);
    pCreateErrorInfo->SetHelpContext(dwHelpContextID);

    // load in the actual error string value.  max of 256.
    //
    LoadString(GetResourceHandle(), idException, wszTmp, 256);
    // MultiByteToWideChar(CP_ACP, 0, szTmp, -1, wszTmp, 256);
    pCreateErrorInfo->SetDescription(wszTmp);

    // load in the source
    //
    // MultiByteToWideChar(CP_ACP, 0, NAMEOFOBJECT(m_ObjectType), -1, szTmp, 256);
    pCreateErrorInfo->SetSource(wszTmp);

    // now set the Error info up with the system
    //
    hr = pCreateErrorInfo->QueryInterface(IID_IErrorInfo, (void **)&pErrorInfo);
    CLEANUP_ON_FAILURE(hr);

    SetErrorInfo(0, pErrorInfo);
    pErrorInfo->Release();

  CleanUp:
    pCreateErrorInfo->Release();
    return hrExcep;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::InterfaceSupportsErrorInfo
//=--------------------------------------------------------------------------=
// indicates whether or not the given interface supports rich error information
//
// Parameters:
//    REFIID        - [in] the interface we want the answer for.
//
// Output:
//    HRESULT       - S_OK = Yes, S_FALSE = No.
//
// Notes:
//
HRESULT CAutomationObject::InterfaceSupportsErrorInfo
(
    REFIID riid
)
{
    // see if it's the interface for the type of object that we are.
    //
    if (riid == (REFIID)INTERFACEOFOBJECT(m_ObjectType))
        return S_OK;

    return S_FALSE;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::GetResourceHandle    [helper]
//=--------------------------------------------------------------------------=
// virtual routine to get the resource handle.  virtual, so that inheriting
// objects, such as COleControl can use theirs instead, which goes and gets
// the Host's version ...
//
// Output:
//    HINSTANCE
//
// Notes:
//
HINSTANCE CAutomationObject::GetResourceHandle
(
    void
)
{
    return NULL;
}


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//                      CAutomationObjectWEvents                            //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////


//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CAutomationObjectWEvents
//=--------------------------------------------------------------------------=
// constructor
//
// Parameters:
//
//    IUnknown *      - [in] controlling Unknown
//    int             - [in] the object type that we are
//    void *          - [in] the VTable of of the object we really are.
//
// Notes:
//
CAutomationObjectWEvents::CAutomationObjectWEvents
(
    IUnknown *pUnkOuter,
    int   ObjType,
    void *pVTable
)
: CAutomationObject(pUnkOuter, ObjType, pVTable),
  m_cpEvents(SINK_TYPE_EVENT),
  m_cpPropNotify(SINK_TYPE_PROPNOTIFY)

{
    // not much to do yet.
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::~CAutomationObjectWEvents
//=--------------------------------------------------------------------------=
// virtual destructor
//
// Notes:
//
CAutomationObjectWEvents::~CAutomationObjectWEvents()
{
    // homey don't play that
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::InternalQueryInterface
//=--------------------------------------------------------------------------=
// our internal query interface routine.  we only add IConnectionPtContainer
// on top of CAutomationObject
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CAutomationObjectWEvents::InternalQueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    // we only add one interface
    //

    if (DO_GUIDS_MATCH(riid, IID_IConnectionPointContainer)) {
        *ppvObjOut = (IConnectionPointContainer *)this;
        ((IUnknown *)(*ppvObjOut))->AddRef();
        return S_OK;
    }

    // just get our parent class to process it from here on out.
    //
    return CAutomationObject::InternalQueryInterface(riid, ppvObjOut);
}


//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::FindConnectionPoint    [IConnectionPointContainer]
//=--------------------------------------------------------------------------=
// given an IID, find a connection point sink for it.
//
// Parameters:
//    REFIID              - [in]  interfaces they want
//    IConnectionPoint ** - [out] where the cp should go
//
// Output:
//    HRESULT
//
// Notes:
//


STDMETHODIMP CAutomationObjectWEvents::FindConnectionPoint
(
    REFIID             riid,
    IConnectionPoint **ppConnectionPoint
)
{
    CHECK_POINTER(ppConnectionPoint);

    // we support the event interface, and IDispatch for it, and we also
    // support IPropertyNotifySink.
    //
    if (DO_GUIDS_MATCH(riid, EVENTIIDOFOBJECT(m_ObjectType)) || DO_GUIDS_MATCH(riid, IID_IDispatch))
        *ppConnectionPoint = &m_cpEvents;
    else if (DO_GUIDS_MATCH(riid, IID_IPropertyNotifySink))
        *ppConnectionPoint = &m_cpPropNotify;
    else
        return E_NOINTERFACE;

    // generic post-processing.
    //
    (*ppConnectionPoint)->AddRef();
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::EnumConnectionPoints    [IConnectionPointContainer]
//=--------------------------------------------------------------------------=
// creates an enumerator for connection points.
//
// Parameters:
//    IEnumConnectionPoints **    - [out]
//
// Output:
//    HRESULT
//
// Notes:
//

STDMETHODIMP CAutomationObjectWEvents::EnumConnectionPoints
(
    IEnumConnectionPoints **ppEnumConnectionPoints
)
{

	IConnectionPoint **rgConnectionPoints;

    CHECK_POINTER(ppEnumConnectionPoints);

    // HeapAlloc an array of connection points [since our standard enum
    // assumes this and HeapFree's it later ]
    //
    rgConnectionPoints = (IConnectionPoint **)HeapAlloc(g_hHeap, 0, sizeof(IConnectionPoint *) * 2);
    RETURN_ON_NULLALLOC(rgConnectionPoints);

    // we support the event interface for this dude as well as IPropertyNotifySink
    //
    rgConnectionPoints[0] = &m_cpEvents;
    rgConnectionPoints[1] = &m_cpPropNotify;

    *ppEnumConnectionPoints = (IEnumConnectionPoints *)(IEnumGeneric *) new CStandardEnum(IID_IEnumConnectionPoints,
                                2, sizeof(IConnectionPoint *), (void *)rgConnectionPoints,
                                CopyAndAddRefObject);
    if (!*ppEnumConnectionPoints) {
        HeapFree(g_hHeap, 0, rgConnectionPoints);
        return E_OUTOFMEMORY;
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::m_pObject
//=--------------------------------------------------------------------------=
// returns a pointer to the control in which we are nested.
//
// Output:
//    CAutomationObjectWEvents *
//
// Notes:
//


inline CAutomationObjectWEvents *CAutomationObjectWEvents::CConnectionPoint::m_pObject
(
    void
)
{
    return (CAutomationObjectWEvents *)((BYTE *)this - ((m_bType == SINK_TYPE_EVENT)
                                          ? offsetof(CAutomationObjectWEvents, m_cpEvents)
                                          : offsetof(CAutomationObjectWEvents, m_cpPropNotify)));
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::QueryInterface
//=--------------------------------------------------------------------------=
// standard qi
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
STDMETHODIMP CAutomationObjectWEvents::CConnectionPoint::QueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    if (DO_GUIDS_MATCH(riid, IID_IConnectionPoint) || DO_GUIDS_MATCH(riid, IID_IUnknown)) {
        *ppvObjOut = (IConnectionPoint *)this;
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::AddRef
//=--------------------------------------------------------------------------=
//
// Output:
//    ULONG        - the new reference count
//
// Notes:
//
ULONG CAutomationObjectWEvents::CConnectionPoint::AddRef
(
    void
)
{
    return m_pObject()->ExternalAddRef();
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::Release
//=--------------------------------------------------------------------------=
//
// Output:
//    ULONG         - remaining refs
//
// Notes:
//
ULONG CAutomationObjectWEvents::CConnectionPoint::Release
(
    void
)
{
    return m_pObject()->ExternalRelease();
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::GetConnectionInterface
//=--------------------------------------------------------------------------=
// returns the interface we support connections on.
//
// Parameters:
//    IID *        - [out] interface we support.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CAutomationObjectWEvents::CConnectionPoint::GetConnectionInterface
(
    IID *piid
)
{
    if (m_bType == SINK_TYPE_EVENT)
        *piid = EVENTIIDOFOBJECT(m_pObject()->m_ObjectType);
    else
        *piid = IID_IPropertyNotifySink;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::GetConnectionPointContainer
//=--------------------------------------------------------------------------=
// returns the connection point container
//
// Parameters:
//    IConnectionPointContainer **ppCPC
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CAutomationObjectWEvents::CConnectionPoint::GetConnectionPointContainer
(
    IConnectionPointContainer **ppCPC
)
{
    return m_pObject()->ExternalQueryInterface(IID_IConnectionPointContainer, (void **)ppCPC);
}


//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectiontPoint::Advise
//=--------------------------------------------------------------------------=
// someboyd wants to be advised when something happens.
//
// Parameters:
//    IUnknown *        - [in]  guy who wants to be advised.
//    DWORD *           - [out] cookie
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CAutomationObjectWEvents::CConnectionPoint::Advise
(
    IUnknown *pUnk,
    DWORD    *pdwCookie
)
{
    HRESULT    hr;
    void      *pv;

    CHECK_POINTER(pdwCookie);

    // first, make sure everybody's got what they thinks they got
    //
    if (m_bType == SINK_TYPE_EVENT) {

        // CONSIDER: 12.95 -- this theoretically is broken -- if they do a find
        // connection point on IDispatch, and they just happened to also support
        // the Event IID, we'd advise on that.  this is not awesome, but will
        // prove entirely acceptable short term.
        //
        hr = pUnk->QueryInterface(EVENTIIDOFOBJECT(m_pObject()->m_ObjectType), &pv);
        if (FAILED(hr))
            hr = pUnk->QueryInterface(IID_IDispatch, &pv);
    }
    else
        hr = pUnk->QueryInterface(IID_IPropertyNotifySink, &pv);
    RETURN_ON_FAILURE(hr);

    // finally, add the sink.  it's now been cast to the correct type and has
    // been AddRef'd.
    //
    return AddSink(pv, pdwCookie);
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::AddSink
//=--------------------------------------------------------------------------=
// in some cases, we'll already have done the QI, and won't need to do the
// work that is done in the Advise routine above.  thus, these people can
// just call this instead. [this stems really from IQuickActivate]
//
// Parameters:
//    void *        - [in]  the sink to add. it's already been addref'd
//    DWORD *       - [out] cookie
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT CAutomationObjectWEvents::CConnectionPoint::AddSink
(
    void  *pv,
    DWORD *pdwCookie
)
{
    IUnknown **rgUnkNew;

    // we optimize the case where there is only one sink to not allocate
    // any storage.  turns out very rarely is there more than one.
    //
    switch (m_cSinks) {

        case 0:
            ASSERT(!m_rgSinks, L"this should be null when there are no sinks");
            m_rgSinks = (IUnknown **)pv;
            break;

        case 1:
            // go ahead and do the initial allocation.  we'll get 8 at a time
            //
            rgUnkNew = (IUnknown **)HeapAlloc(g_hHeap, 0, 8 * sizeof(IUnknown *));
            RETURN_ON_NULLALLOC(rgUnkNew);
            rgUnkNew[0] = (IUnknown *)m_rgSinks;
            rgUnkNew[1] = (IUnknown *)pv;
            m_rgSinks = rgUnkNew;
            break;

        default:
            // if we're out of sinks, then we have to increase the size
            // of the array
            //
            if (!(m_cSinks & 0x7)) {
                rgUnkNew = (IUnknown **)HeapReAlloc(g_hHeap, 0, m_rgSinks, (m_cSinks + 8) * sizeof(IUnknown *));
                RETURN_ON_NULLALLOC(rgUnkNew);
                m_rgSinks = rgUnkNew;
            } else
                rgUnkNew = m_rgSinks;

            rgUnkNew[m_cSinks] = (IUnknown *)pv;
            break;
    }

    *pdwCookie = (DWORD)pv;
    m_cSinks++;
    return S_OK;
}


//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::Unadvise
//=--------------------------------------------------------------------------=
// they don't want to be told any more.
//
// Parameters:
//    DWORD        - [in]  the cookie we gave 'em.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CAutomationObjectWEvents::CConnectionPoint::Unadvise
(
    DWORD dwCookie
)
{
    IUnknown *pUnk;
    int       x;

    if (!dwCookie)
        return S_OK;

    // see how many sinks we've currently got, and deal with things based
    // on that.
    //
    switch (m_cSinks) {
        case 1:
            // it's the only sink.  make sure the ptrs are the same, and
            // then free things up
            //
            if ((DWORD)m_rgSinks != dwCookie)
                return CONNECT_E_NOCONNECTION;
            m_rgSinks = NULL;
            break;

        case 2:
            // there are two sinks.  go back down to one sink scenario
            //
            if ((DWORD)m_rgSinks[0] != dwCookie && (DWORD)m_rgSinks[1] != dwCookie)
                return CONNECT_E_NOCONNECTION;

            pUnk = ((DWORD)m_rgSinks[0] == dwCookie)
                   ? m_rgSinks[1]
                   : ((DWORD)m_rgSinks[1] == dwCookie) ? m_rgSinks[0] : NULL;

            if (!pUnk) return CONNECT_E_NOCONNECTION;

            HeapFree(g_hHeap, 0, m_rgSinks);
            m_rgSinks = (IUnknown **)pUnk;
            break;

        default:
            // there are more than two sinks.  just clean up the hole we've
            // got in our array now.
            //
            for (x = 0; x < m_cSinks; x++) {
                if ((DWORD)m_rgSinks[x] == dwCookie)
                    break;
            }
            if (x == m_cSinks) return CONNECT_E_NOCONNECTION;
            if (x < m_cSinks - 1) 
                memcpy(&(m_rgSinks[x]), &(m_rgSinks[x + 1]), (m_cSinks -1 - x) * sizeof(IUnknown *));
            else
                m_rgSinks[x] = NULL;
            break;
    }


    // we're happy
    //
    m_cSinks--;
    ((IUnknown *)dwCookie)->Release();
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::EnumConnections
//=--------------------------------------------------------------------------=
// enumerates all current connections
//
// Paramters:
//    IEnumConnections ** - [out] new enumerator object
//
// Output:
//    HRESULT
//
// NOtes:
//
STDMETHODIMP CAutomationObjectWEvents::CConnectionPoint::EnumConnections
(
    IEnumConnections **ppEnumOut
)
{
    CONNECTDATA *rgConnectData = NULL;
    int i;

    if (m_cSinks) {
        // allocate some memory big enough to hold all of the sinks.
        //
        rgConnectData = (CONNECTDATA *)HeapAlloc(g_hHeap, 0, m_cSinks * sizeof(CONNECTDATA));
        RETURN_ON_NULLALLOC(rgConnectData);

        // fill in the array
        //
        if (m_cSinks == 1) {
            rgConnectData[0].pUnk = (IUnknown *)m_rgSinks;
            rgConnectData[0].dwCookie = (DWORD)m_rgSinks;
        } else {
            // loop through all available sinks.
            //
            for (i = 0; i < m_cSinks; i++) {
                rgConnectData[i].pUnk = m_rgSinks[i];
                rgConnectData[i].dwCookie = (DWORD)m_rgSinks[i];
            }
        }
    }

    // create yon enumerator object.
    //
    *ppEnumOut = (IEnumConnections *)(IEnumGeneric *)new CStandardEnum(IID_IEnumConnections,
                        m_cSinks, sizeof(CONNECTDATA), rgConnectData, CopyAndAddRefObject);
    if (!*ppEnumOut) {
        HeapFree(g_hHeap, 0, rgConnectData);
        return E_OUTOFMEMORY;
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::~CConnectionPoint
//=--------------------------------------------------------------------------=
// cleans up
//
// Notes:
//
CAutomationObjectWEvents::CConnectionPoint::~CConnectionPoint ()
{
    int x;

    // clean up some memory stuff
    //
    if (!m_cSinks)
        return;
    else if (m_cSinks == 1)
        ((IUnknown *)m_rgSinks)->Release();
    else {
        for (x = 0; x < m_cSinks; x++)
            QUICK_RELEASE(m_rgSinks[x]);
        HeapFree(g_hHeap, 0, m_rgSinks);
    }
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPiont::DoInvoke
//=--------------------------------------------------------------------------=
// fires an event to all listening on our event interface.
//
// Parameters:
//    DISPID            - [in] event to fire.
//    DISPPARAMS        - [in]
//
// Notes:
//
void CAutomationObjectWEvents::CConnectionPoint::DoInvoke
(
    DISPID      dispid,
    DISPPARAMS *pdispparams
)
{
    int iConnection;

    // if we don't have any sinks, then there's nothing to do.  we intentionally
    // ignore errors here.
    //
    if (m_cSinks == 0)
        return;
    else if (m_cSinks == 1)
        ((IDispatch *)m_rgSinks)->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, pdispparams, NULL, NULL, NULL);
    else
        for (iConnection = 0; iConnection < m_cSinks; iConnection++)
            ((IDispatch *)m_rgSinks[iConnection])->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, pdispparams, NULL, NULL, NULL);
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::DoOnChanged
//=--------------------------------------------------------------------------=
// fires the OnChanged event for IPropertyNotifySink listeners.
//
// Parameters:
//    DISPID            - [in] dude that changed.
//
// Output:
//    none
//
// Notes:
//
void CAutomationObjectWEvents::CConnectionPoint::DoOnChanged
(
    DISPID dispid
)
{
    int iConnection;

    // if we don't have any sinks, then there's nothing to do.
    //
    if (m_cSinks == 0)
        return;
    else if (m_cSinks == 1)
        ((IPropertyNotifySink *)m_rgSinks)->OnChanged(dispid);
    else
        for (iConnection = 0; iConnection < m_cSinks; iConnection++)
            ((IPropertyNotifySink *)m_rgSinks[iConnection])->OnChanged(dispid);
}

//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::CConnectionPoint::DoOnRequestEdit
//=--------------------------------------------------------------------------=
// fires the OnRequestEdit for IPropertyNotifySinkListeners
//
// Parameters:
//    DISPID             - [in] dispid user wants to change.
//
// Output:
//    BOOL               - false means you cant
//
// Notes:
//
BOOL CAutomationObjectWEvents::CConnectionPoint::DoOnRequestEdit
(
    DISPID dispid
)
{
    HRESULT hr;
    int     iConnection;

    // if we don't have any sinks, then there's nothing to do.
    //
    if (m_cSinks == 0)
        hr = S_OK;
    else if (m_cSinks == 1)
        hr =((IPropertyNotifySink *)m_rgSinks)->OnRequestEdit(dispid);
    else {
        for (iConnection = 0; iConnection < m_cSinks; iConnection++) {
            hr = ((IPropertyNotifySink *)m_rgSinks[iConnection])->OnRequestEdit(dispid);
            if (hr != S_OK) break;
        }
    }

    return (hr == S_OK) ? TRUE : FALSE;
}


//=--------------------------------------------------------------------------=
// CAutomationObjectWEvents::FireEvent
//=--------------------------------------------------------------------------=
// fires an event.  handles arbitrary number of arguments.
//
// Parameters:
//    EVENTINFO *        - [in] struct that describes the event.
//    ...                - arguments to the event
//
// Output:
//    none
//
// Notes:
//    - use stdarg's va_* macros.
//
void __cdecl CAutomationObjectWEvents::FireEvent
(
    EVENTINFO *pEventInfo,
    ...
)
{
    va_list    valist;
    DISPPARAMS dispparams;
    VARIANT    rgvParameters[MAX_ARGS];
    VARIANT   *pv;
    VARTYPE    vt;
    int        iParameter;
    int        cbSize;

    ASSERT(pEventInfo->cParameters <= MAX_ARGS, L"Don't support more than MAX_ARGS params.  sorry.");

    va_start(valist, pEventInfo);

    // copy the Parameters into the rgvParameters array.  make sure we reverse
    // them for automation
    //
    pv = &(rgvParameters[pEventInfo->cParameters - 1]);
    for (iParameter = 0; iParameter < pEventInfo->cParameters; iParameter++) {

        vt = pEventInfo->rgTypes[iParameter];

        // if it's a by value variant, then just copy the whole
        // dang thing
        //
        if (vt == VT_VARIANT)
            *pv = va_arg(valist, VARIANT);
        else {
            // copy the vt and the data value.
            //
            pv->vt = vt;
            if (vt & VT_BYREF)
                cbSize = sizeof(void *);
            else
                cbSize = g_rgcbDataTypeSize[vt];

            // small optimization -- we can copy 2/4 bytes over quite
            // quickly.
            //
            if (cbSize == sizeof(short))
                V_I2(pv) = va_arg(valist, short);
            else if (cbSize == 4)
                V_I4(pv) = va_arg(valist, long);
            else {
                // copy over 8 bytes
                //
                ASSERT(cbSize == 8, L"don't recognize the type!!");
                V_CY(pv) = va_arg(valist, CURRENCY);
            }
        }

        pv--;
    }

    // fire the event
    //
    dispparams.rgvarg = rgvParameters;
    dispparams.cArgs = pEventInfo->cParameters;
    dispparams.rgdispidNamedArgs = NULL;
    dispparams.cNamedArgs = 0;

    m_cpEvents.DoInvoke(pEventInfo->dispid, &dispparams);

    va_end(valist);
}

//=--------------------------------------------------------------------------=
// CopyAndAddRefObject
//=--------------------------------------------------------------------------=
// copies an object pointer, and then addref's the object.
//
// Parameters:
//    void *        - [in] dest.
//    const void *  - [in] src
//    DWORD         - [in] size, ignored, since it's always 4
//
// Notes:
//
void WINAPI CopyAndAddRefObject
(
    void       *pDest,
    const void *pSource,
    DWORD       dwSize
)
{
    ASSERT(pDest && pSource, L"Bogus Pointer(s) passed into CopyAndAddRefObject!!!!");

    *((IUnknown **)pDest) = *((IUnknown **)pSource);
    ADDREF_OBJECT(*((IUnknown **)pDest));

    return;
}

