SAMPLE: AtlSink Uses ATL to Create a Dispinterface Sink

ID: Q181277


The information in this article applies to:


SUMMARY

There are two approaches to implementing a dispinterface sink using ATL. The sample, AtlSink, uses both methods. The Readme.txt file included with the AtlSink file includes the project overview, and the "More Information" section of this article includes a description of how to implement both methods.


MORE INFORMATION

The following file is available for download from the Microsoft Software Library:

~ AtlSink.exe
For more information about downloading files from the Microsoft Software Library, please see the following article in the Microsoft Knowledge Base:
Q119591 How to Obtain Microsoft Support Files from Online Services


The AtlSink workspace consists of two projects, AtlSink and AtlConnectableObject. Use the -d option when you run AtlSink.exe:

   ATLSINK.EXE -d 

A Connectable Object

A connectable object is a Component Object Model (COM) object that exposes one or more source interfaces, implements the IConnectionPointContainer interface, and provides an IConnectionPoint implementation for each source interface. The connectable object normally publishes the specifications for the source interfaces in a type library. To do this, add the "source" attribute to the interface definition, within the object's coclass definition.

A connectable object provides no implementation for the source interface, Just a definition. The connectable object calls methods on the source interface (fires events) for each sink that is connected.

A Sink

A sink is a COM object that is implemented and instantiated by a client of one or more connectable objects. The sink object may implement other interfaces, but at a minimum, it must implement the source interface of the connectable object that the client wants to connect to.

Two Ways to Implement Your Sink

You must implement seven methods for a dispinterface, three IUnknown methods and four IDispatch methods. A sink uses only one of the IDispatch methods, Invoke. The other three IDispatch methods, GetTypeInfoCount, GetTypeInfo, and GetIDsOfNames just returns E_NOTIMPL. When you use ATL, the CComObject constructor implements the IUnknown methods for you.

You can implement a dispinterface sink in ATL in two ways:
  1. Use the ATL implementation of Idispatch


  2. Use ATL to implement IUnknown.


With the first approach, you must provide a dual interface implementation with a type library. With the second approach, you must provide a switch and case statement for every DISPID you want to handle. Also with the second approach, you must manually extract the variants packaged in the IDispatch::Invoke call. If the connection point has only a few methods or you want to handle only a few, and you do not have a type library for your client, then you may prefer the second approach.

The ItypeInfo interface provides a standard way of unpacking the parameters packed into the Invoke call. You also do not have to duplicate the code required. However, you must implement an .idl file, making sure each method you implement matches the DISPIDs specified in the connectable object's type library, and each call must determine how to unpack the parameter types at run time. If you want to handle only a few of the methods provided by a dispinterface connection point, then you may prefer the switch method.

Using ATL to Implement IDispatch

This approach uses the IDispatchImpl class to implement the IDispatch methods. Because IDispatchImpl::Invoke just defers to ItypeInfo::Invoke, you must have a registered type library with a coclass definition that describes your sink interface as a dual interface. You can implement a sink with IDispatchImpl by completing the following steps:
  1. Add a default (dual) ATL Simple Object to your project: With your ATL project selected in the ClassView window of Visual C++, right-click the project or, on the Insert menu, click New ATL Object. Click Simple Object on the ATL Object Wizard dialog box. You can use any name you want as long as you avoid naming conflicts, including the dispinterface name (in other words, the name can not be the same as the dispinterface name).


  2. Add the desired connection point methods:

    Add only the methods you want to handle. ATL will return DISP_E_MEMBERNOTFOUND for all DISPIDs not provided.


  3. Change all DISPIDs to match the connection point specification:

    All of the DISPIDs for the method must match the DISPIDs of the source. The connectable object just calls Invoke with the DISPID it published. All DISPIDs must match the connection point specification to ensure that the correct event handler is called.


  4. Add the dispinterface IID to the object's interface map:

    The ATL implementation of IConnectionPoint::Advise will perform a QueryIinterface for the dispinterface IID. You need to provide an entry that will map the dispinterface IID to your object's IDispatch. Use COM_INTERFACE_INTRY_IID to accomplish this mapping. Other implementations will perform a QueryInterface for IDispatch, which ATL has already provided.


  5. (OPTIONAL) Make object noncreatable:

    You do not need to create your sink object outside your project. The interface may be marshaled to another apartment, processor, or machine, but COM does not need to know how to create your object.

    To make your object noncreatable you need to perform the following three steps:

    1. In the .idl file, add the noncreatable attribute to the coclass.


    2. n the header file, remove the CComCoClass inheritance.


    3. In the project's main .cpp file, remove the object from the object map.




  6. Create an instance of the object:

    To create a local instance of your ATL object, complete the following three steps:

    1. Declare a pointer to your object, such as:
      
               CComObject<CMyObj>* pObj; 


    2. Call CreateInstance, such as:
      
               CcomObject<CMyObj>::CreateInstance(&pObj); 


    3. Perform a QueryInterface for the object's IUnknown, such as:
      
               pObj->QueryInterface(IID_IUnknown, &pUnknown); 




  7. Call the AtlAdvise function:

    Call AtlAdvise with the IUnknown* methods of the connectable object, the IUnknown* methods of your sink, the connectable object's source IID, and the address of a DWORD to store the cookie that AtlAdvise will return. Save the cookie for use with the AtlUnadvise function.


Using ATL to Implement Only IUnknown

When you create a sink using this approach, you do not need a type library nor do you need to provide support for external creation. Complete the following steps to create a simple sink:
  1. Derive your class from CComObjectRootEx and IDispatch:

    The sink class you create will be the template parameter for the CComObject constructor, so it must derive from CComObjectRoot. Since your sink is a dispinterface implementation, you must also derive from IDispatch.


  2. Add an interface map:

    Your class needs an interface map so that the QueryInterface function can find your IDispatch interface, with either the IDispatch IID or the dispinterface IID. The following code represents a typical interface map:
    
    BEGIN_COM_MAP(CDispatchSink)
       COM_INTERFACE_ENTRY(IDispatch)
       COM_INTERFACE_ENTRY_IID(DIID_DsomeEvents, IDispatch)
    END_COM_MAP() 
    The parameter for the BEGIN_COM_MAP macro is your sink class. The first parameter for the COM_INTERFACE_ENTRY_IID is the IID of the dispinterface.


  3. Provide implementations for the four IDispatch methods:

    The first three IDispatch methods need only return E_NOTIMPL. For the Invoke method, you need a switch with a case entry for each DISPID that provides notification. The following code represents a typical implementation for Invoke:

    SAMPLE CODE

    
    STDMETHODIMP CDispatchSink::Invoke(DISPID dispidMember, REFIID riid,
                                       LCID lcid, WORD wFlags,
                                       DISPPARAMS* pdispparams, VARIANT*
                                       pvarResult, EXCEPINFO* pexcepinfo,
                                       UINT* puArgErr)
    {
       HRESULT hr = S_OK;
       if (pdispparams)
       {
          switch (dispidMember)
          {
             case 2:
             {
                if (pdispparams->cArgs == 1)
                {
                   if (pdispparams->rgvarg[0].vt == VT_I2)
                      Event2(pdispparams->rgvarg[0].iVal);
                   else
                      hr = DISP_E_TYPEMISMATCH;
                }
                else
                   hr = DISP_E_BADPARAMCOUNT;
                break;
             }
    // Other desired case statements
             default:
             {
                hr = DISP_E_MEMBERNOTFOUND;
                break;
             }
          }
       }
       else
          hr = DISP_E_PARAMNOTFOUND;
       return hr;
    } 
    Each case statement should check the number of parameters and type of each parameter. After error checking, you can either handle the event in the case statement or call an event handler.


  4. Create an instance of the object:

    See the "Using ATL to Implement IDispatch" section for details.


  5. Call the AtlAdvise function:

    See the "Using ATL to Implement IDispatch" section for details.


(c) Microsoft Corporation 1998, All Rights Reserved. Contributions by Chuck Bell, Microsoft Corporation.

Additional query words: IconnectionPointImpl kbATL200 kbATL210 kbATL300 kbVC420 kbVC500 kbvc600 kbcodesam


Keywords          : kbcode kbfile kbsample kbATL200 kbATL210 kbVC420 kbVC500 kbVC600 kbATL300 
Version           : WINDOWS:2.0,2.1,3.0
Platform          : WINDOWS 
Issue type        : kbhowto 

Last Reviewed: July 29, 1999