Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Pluggable Components using Component Categories - Part II

0.00/5 (No votes)
18 Sep 2003 1  
An article on using component categories to create pluggable components

Initialization

Initialization

Application

Introduction

In part 1 of this series, I talked about creating a very basic component from a simple category. The category dictated that the component implement a single method (Draw). In this article, I will take this a step further. Prepare to enter into the realm of connection points and type libraries.

Background (optional)

Once again, to save time and space I will assume that you have a basic understanding of COM and ATL. Also, I will not discuss the implementation of the communication classes that are part of these controls. They are classes that I have either created or modified over the years and that I keep in my collection of useful objects. Since I no longer remember where I saw some of the code that is used within them, I will offer my thanks to anyone who helped me directly or that I borrowed code from, when developing these classes.

If you are trying to develop components using the model discussed in part 1, it will not be long, before you run into a component that requires a little more functionality. For example, say you wanted to create a control that will allow you to send and receive text data over a serial port. Sending the data is a matter of calling a method; however, receiving data poses a problem. Do you want to constantly poll the control to see if there is data? Probably not. A much cleaner way would be to have the control alert you, when it has received something. To accomplish this, we need to use connection points. Note that connection points are synonymous with ActiveX events. So, how do we set up connection points that will be consistent with every component that is a member of the category? That is, after all, the purpose of a category isn't it? Thankfully, setting up your category interface is no more difficult than it was before.

The code

Communication.idl

import "oaidl.idl";
import "ocidl.idl";

[
    object,
    uuid(5BCB2257-94DF-48ce-AC2B-1786DEFD3831),
    dual,
    helpstring("ICommunication Interface"),
    pointer_default(unique)
]
interface ICommunication : IDispatch
{
    [id(1), helpstring("method Initialize")]
                         HRESULT Initialize(BSTR bstrParameters);
    [propget, id(2), helpstring("property Connected")]
                         HRESULT Connected([out, retval] BOOL *pVal);
    [propput, id(2), helpstring("property Connected")]
                         HRESULT Connected([in] BOOL newVal);
    [id(3), helpstring("method Send")] HRESULT Send(BSTR bstrData);
};

[
    uuid(01E10F11-5209-4b2c-9A3B-2A8AD47413CB),
    version(1.0),
    helpstring("Communications 1.0 Type Library")
]
library COMMUNICATIONSLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [
        uuid(BB8E1BD4-1C7C-4bc9-AD6D-3A919EA0497D),
        helpstring("_ICommunicationEvents Interface")
    ]
    dispinterface _ICommunicationEvents
    {
        properties:
        methods:
        [id(1)] void OnConnected();
        [id(2)] void OnSend();
        [id(3)] void OnReceive(BSTR bstrData);
        [id(4)] void OnClose();
    };

    [
        uuid(17CC2111-9772-4F9C-8EDB-7FD2A82F1772),
        helpstring("Communication Class")
    ]
    coclass Communication
    {
        [default] interface ICommunication;
        [default, source] dispinterface _ICommunicationEvents;
    };
};

Here we have created an IDL that describes an interface, ICommunication, that is derived from IDispatch and has 2 methods and 1 property. In addition, it also has an event interface that has 4 methods. This is no different from any other dual interface declaration. Do not add this file to your project yet. Visual C++ does not like to add a new type library to a project when it sees that there is one that exists for it. We also need to create a category id. This is done just as before using GUIDGEN.

CommunicationCategory.h

/////////////////////////////////////////////////////////

// Communications Category Definition


// {697CB498-27A8-48b2-8439-F98B835FABFB}

static const GUID CATID_COMMUNICATION = { 0x697cb498, 0x27a8, 0x48b2,
                 { 0x84, 0x39, 0xf9, 0x8b, 0x83, 0x5f, 0xab, 0xfb } };

Now time to use this interface. For the rest of the article, I will use the SerialCommunications object as the example; however, the socket and telnet versions are created in similar fashion. First, create a Full Control. It is easy to use the ATL COM AppWizard for this, but you can do this by hand if you like. Here is the IDL for the SerialCommunications component.

SerialCommunications.idl

import "oaidl.idl";
import "ocidl.idl";
import "Communications.idl";
#include "olectl.h"


    [
        object,
        uuid(0840E4A3-DB93-4C5E-836C-B88A0714C882),
        dual,
        helpstring("ISerialCommunication Interface"),
        pointer_default(unique)
    ]
    interface ISerialCommunication : ICommunication
    {
    };

[
    uuid(F31566E5-6922-4F43-888E-4BD200E752AA),
    version(1.0),
    helpstring("SerialCommunications 1.0 Type Library")
]
library SERIALCOMMUNICATIONSLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [
        uuid(0B320826-FFBC-419A-A58B-A6CF7F581C5A),
        helpstring("_ISerialCommunicationEvents Interface")
    ]
    dispinterface _ISerialCommunicationEvents
    {
        properties:
        methods:
    };

    [
        uuid(81FBDE8E-9217-489D-82C1-74F8CA598DC3),
        helpstring("SerialCommunication Class")
    ]
    coclass SerialCommunication
    {
        [default] interface ISerialCommunication;
        [default, source] dispinterface _ICommunicationEvents;
        [source] dispinterface _ISerialCommunicationEvents;
    };
};

There are a few things that need to be noted in this file. First, we imported the Communications.idl file. This will enable use to derive our interface from the ICommunication interface. It also will cause MIDL to create Communication_i.c and Communication.h files that contain the C/C++ version of the interface definition and the GUIDs. Next, we inherited our interface, ISerialCommunication, from ICommunication. Finally, in the type library definition, we modified the coclass definition. Declaring the _ICommunicationEvents interface as the default source interface for events, allows us to always get those events. It also allows us to add specialized events to the _ISerialCommunicationEvents interface if we wanted to.

Right now, our code won't compile. The implementation class for the ISerialCommunication interface is missing some function declarations. Here is what we need to change.

SerialCommuncation.h

#include "resource.h"       // main 

symbols
#include <atlctl.h>

#include "Serial.h"

#include "CommunicationCategory.h"


/////////////////////////////////////////////////////////////////////////

// CSerialCommunication

class ATL_NO_VTABLE CSerialCommunication :
    public CComObjectRootEx<CComSingleThreadModel>,
    public IDispatchImpl<ISerialCommunication, 
&IID_ISerialCommunication,
                                      
&LIBID_SERIALCOMMUNICATIONSLib>,
    public CComControl<CSerialCommunication>,
    public IPersistStreamInitImpl<CSerialCommunication>,
    public IOleControlImpl<CSerialCommunication>,
    public IOleObjectImpl<CSerialCommunication>,
    public IOleInPlaceActiveObjectImpl<CSerialCommunication>,
    public IViewObjectExImpl<CSerialCommunication>,
    public IOleInPlaceObjectWindowlessImpl<CSerialCommunication>,
    public IConnectionPointContainerImpl<CSerialCommunication>,
    public IPersistStorageImpl<CSerialCommunication>,
    public ISpecifyPropertyPagesImpl<CSerialCommunication>,
    public IQuickActivateImpl<CSerialCommunication>,
    public IDataObjectImpl<CSerialCommunication>,
    public IProvideClassInfo2Impl<&CLSID_SerialCommunication,
       &DIID__ISerialCommunicationEvents, 
&LIBID_SERIALCOMMUNICATIONSLib>,
    public IPropertyNotifySinkCP<CSerialCommunication>,
    public CComCoClass<CSerialCommunication, 
&CLSID_SerialCommunication>,
    public IDispatchImpl<ICommunication, &IID_ICommunication,
                                        
&LIBID_SERIALCOMMUNICATIONSLib>
{
public:
    CSerialCommunication();

DECLARE_REGISTRY_RESOURCEID(IDR_SERIALCOMMUNICATION)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CSerialCommunication)
    COM_INTERFACE_ENTRY(ISerialCommunication)
//  COM_INTERFACE_ENTRY(ICommunication)

//  COM_INTERFACE_ENTRY(IDispatch)

    COM_INTERFACE_ENTRY(IViewObjectEx)
    COM_INTERFACE_ENTRY(IViewObject2)
    COM_INTERFACE_ENTRY(IViewObject)
    COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
    COM_INTERFACE_ENTRY(IOleInPlaceObject)
    COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
    COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
    COM_INTERFACE_ENTRY(IOleControl)
    COM_INTERFACE_ENTRY(IOleObject)
    COM_INTERFACE_ENTRY(IPersistStreamInit)
    COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
    COM_INTERFACE_ENTRY(IConnectionPointContainer)
    COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
    COM_INTERFACE_ENTRY(IQuickActivate)
    COM_INTERFACE_ENTRY(IPersistStorage)
    COM_INTERFACE_ENTRY(IDataObject)
    COM_INTERFACE_ENTRY(IProvideClassInfo)
    COM_INTERFACE_ENTRY(IProvideClassInfo2)
    COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
    COM_INTERFACE_ENTRY2(IDispatch, ISerialCommunication)
    COM_INTERFACE_ENTRY2(ICommunication, ISerialCommunication)
END_COM_MAP()

BEGIN_CATEGORY_MAP(CSerialCommunication)
    IMPLEMENTED_CATEGORY(CATID_COMMUNICATION)
END_CATEGORY_MAP()

BEGIN_PROP_MAP(CSerialCommunication)
    PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
    PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
    // Example entries

    // PROP_ENTRY("Property Description", dispid, clsid)

    // PROP_PAGE(CLSID_StockColorPage)

END_PROP_MAP()

BEGIN_CONNECTION_POINT_MAP(CSerialCommunication)
    CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
END_CONNECTION_POINT_MAP()

BEGIN_MSG_MAP(CSerialCommunication)
    CHAIN_MSG_MAP(CComControl<CSerialCommunication>)
    DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
// Handler prototypes:

//  LRESULT MessageHandler(UINT uMsg,

//       WPARAM wParam, LPARAM lParam, BOOL& bHandled);

//  LRESULT CommandHandler(WORD wNotifyCode,

//       WORD wID, HWND hWndCtl, BOOL& bHandled);

//  LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);




// IViewObjectEx

    DECLARE_VIEW_STATUS(0)

// ISerialCommunication

public:
    HRESULT OnDraw(ATL_DRAWINFO& di);

// ICommunication

public:
    STDMETHOD(Initialize)(BSTR bstrParameters);
    STDMETHOD(Send)(BSTR bstrData);
    STDMETHOD(get_Connected)(/*[out, retval]*/ BOOL *pVal);
    STDMETHOD(put_Connected)(/*[in]*/ BOOL newVal);

protected:
    CString m_strPortName;
    CSerial::BaudRate m_BaudRate;
    CSerial::DataBits m_DataBits;
    CSerial::StopBits m_StopBits;
    CSerial::Parity m_Parity;
    CSerial::HandShaking m_HandShaking;
    CSerial m_Serial;
public:
    static void CALLBACK SerialCallback(WPARAM wParam1,
                          WPARAM wParam2, LPARAM lParam);
};

We need to include our header file with the CATID definition it it. That enables us to use the CATEGORY_MAP macros to tie our component to the category. We also need to add a dispatch implementation for the ICommunication interface to the inheritance list. This gives us access to the methods and properties in our implementation class. Also, notice that the interface entries for IDispatch and ICommunication are modified to use the COM_INTERFACE_ENTRY2 macro. We had to do this because of the multiple inheritance. If we tried to use the COM_INTERFACE_ENTRY macro, there are multiple inheritance paths to the ICommunication and IDispatch interfaces. To avoid this, we specifically tell the compiler how to access those interfaces. Finally, we need to add the declarations of our methods. The variables and the static callback function at the bottom of the class declaration are used for the CSerial class.

We also need to add implementation for these functions.

#include 

"stdafx.h"
#include "SerialCommunications.h"

#include "SerialCommunication.h"

#include "Split.h"


//////////////////////////////////////////////////////////////////////////

// CSerialCommunication

CSerialCommunication::CSerialCommunication()
{
    m_strPortName = _T("");
    m_BaudRate = CSerial::EBAUDRATE_38400;
    m_DataBits = CSerial::EDATABITS_8;
    m_Parity = CSerial::EPARITY_NONE;
    m_StopBits = CSerial::ESTOPBITS_1;
    m_HandShaking = CSerial::EHANDSHAKE_OFF;

    m_bAutoSize = TRUE;
    m_bResizeNatural = TRUE;
    SIZEL sPix, sHiM;
    sPix.cx = 32;
    sPix.cy = 32;
    AtlPixelToHiMetric(&sPix, &sHiM);
    m_sizeExtent = sHiM;
    m_sizeNatural = sHiM;
}

HRESULT CSerialCommunication::OnDraw(ATL_DRAWINFO& di)
{
    RECT& rc = *(RECT*)di.prcBounds;
//  Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);

//  SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);

//  LPCTSTR pszText = _T("ATL 3.0 : SerialCommunication");

//  TextOut(di.hdcDraw, (rc.left + rc.right) / 2,

        (rc.top + rc.bottom) / 2, pszText, lstrlen(pszText));
    HICON hIcon = ::LoadIcon(_Module.GetModuleInstance(),
        MAKEINTRESOURCE(IDI_SERIAL));
    DrawIcon(di.hdcDraw, rc.left, rc.top, hIcon);

    return S_OK;
}

STDMETHODIMP CSerialCommunication::get_Connected(BOOL *pVal)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())

    if (NULL == pVal)
        return E_POINTER;

    *pVal = (TRUE == m_Serial.IsOpen() ? VARIANT_TRUE : VARIANT_FALSE);
    return S_OK;
}

STDMETHODIMP CSerialCommunication::put_Connected(BOOL newVal)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())

    BOOL bVal = newVal;
    if (FALSE != bVal && FALSE == m_Serial.IsOpen())
    {
        if (ERROR_SUCCESS == m_Serial.Open(m_strPortName,
                              (void*)this, SerialCallback))
        {
            m_Serial.SetBaudRate(m_BaudRate);
            m_Serial.SetParity(m_Parity);
            m_Serial.SetDataBits(m_DataBits);
            m_Serial.SetStopBits(m_StopBits);
            m_Serial.SetHandshaking(m_HandShaking);
            Fire_OnConnected();
            return S_OK;
        }
        else
        {
            return E_FAIL;
        }
    }
    else if (FALSE == bVal && TRUE == m_Serial.IsOpen())
    {
        if (ERROR_SUCCESS == m_Serial.Close())
        {
            Fire_OnClose();
            return S_OK;
        }
        else
        {
            return E_FAIL;
        }
    }

    return E_INVALIDARG;
}

STDMETHODIMP CSerialCommunication::Initialize(BSTR bstrParameters)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())

    // the bstrParameters string should look something like the following

    // "COM#:Baud:DataBits:Parity:StopBits:FlowControl"

    CString strParameters(bstrParameters);
    CStringArray straParameters;
    Split(strParameters, _T(":"), straParameters);
    if (6 != straParameters.GetSize()) // too few or too many arguments

        return E_INVALIDARG;

    m_strPortName = straParameters.GetAt(0);
    m_BaudRate = (CSerial::BaudRate)atol(straParameters.GetAt(1));
    m_DataBits = (CSerial::DataBits)atol(straParameters.GetAt(2));

    if (0 == straParameters.GetAt(3).CollateNoCase("NONE"))
    {
        m_Parity = CSerial::EPARITY_NONE;
    }
    else if (0 == straParameters.GetAt(3).CollateNoCase("EVEN"))
    {
        m_Parity = CSerial::EPARITY_EVEN;
    }
    else if (0 == straParameters.GetAt(3).CollateNoCase("ODD"))
    {
        m_Parity = CSerial::EPARITY_ODD;
    }
    else if (0 == straParameters.GetAt(3).CollateNoCase("MARK"))
    {
        m_Parity = CSerial::EPARITY_MARK;
    }
    else if (0 == straParameters.GetAt(3).CollateNoCase("SPACE"))
    {
        m_Parity = CSerial::EPARITY_SPACE;
    }
    else
    {
        return E_INVALIDARG;
    }

    if (0 == straParameters.GetAt(4).CollateNoCase("1"))
    {
        m_StopBits = CSerial::ESTOPBITS_1;
    }
    else if (0 == straParameters.GetAt(4).CollateNoCase("1.5"))
    {
        m_StopBits = CSerial::ESTOPBITS_1_5;
    }
    else if (0 == straParameters.GetAt(4).CollateNoCase("2"))
    {
        m_StopBits = CSerial::ESTOPBITS_2;
    }
    else
    {
        return E_INVALIDARG;
    }

    if (0 == straParameters.GetAt(5).CollateNoCase("OFF"))
    {
        m_HandShaking = CSerial::EHANDSHAKE_OFF;
    }
    else if (0 == straParameters.GetAt(5).CollateNoCase("HARDWARE"))
    {
        m_HandShaking = CSerial::EHANDSHAKE_HARDWARE;
    }
    else if (0 == straParameters.GetAt(5).CollateNoCase("SOFTWARE"))
    {
        m_HandShaking = CSerial::EHANDSHAKE_SOFTWARE;
    }
    else
    {
        return E_INVALIDARG;
    }

    return S_OK;
}

STDMETHODIMP CSerialCommunication::Send(BSTR bstrData)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())

    if (TRUE == m_Serial.IsOpen())
    {
        CString strData(bstrData);
        m_Serial.Write(strData, strData.GetLength());
        Fire_OnSend();
        return S_OK;
    }

    return S_OK;
}

void CSerialCommunication::SerialCallback(WPARAM wParam1,
                             WPARAM wParam2, LPARAM lParam)
{
    CSerialCommunication* pSerialComm = (CSerialCommunication*)wParam1;
    CSerial::Event event = (CSerial::Event)wParam2;
    CSerial::Error error = (CSerial::Error)lParam;

    if (CSerial::EEVENT_RECV & event)
    {
        DWORD dwRead = 0;
        CString strBuffer = _T("");
        pSerialComm->m_Serial.Read(strBuffer.GetBuffer(DEFAULT_INQUEUE),
                                               DEFAULT_INQUEUE, 
&dwRead);
        strBuffer.ReleaseBuffer(dwRead);
        pSerialComm->Fire_OnReceive(strBuffer.AllocSysString());
    }
}

The Split.h file is included with each of the projects in the demo. It allows for quick parsing of CStrings by splitting them on delimiters and placing the resulting strings in a CStringArray. This alleviates the need to have complex Initialization functions, as it allows us to pass in a single delimited string. For example, there are 6 parameters to configure when using a serial port; however, there are only 2 when configuring a telnet session or a socket connection. This is not the best way to implement this kind of feature, but for the sake of simplicity, it will do for now.

Everything else in this step is fairly straight-forward.

We still have to implement our connection points. To do this, right click the implementation class in the class view. Select "Implement Connection Point" and select both _ISerialCommunicationEvents and _ICommunicationEvents in the dialog that appears. The wizard will create 2 proxy classes and add the following code to your implementation classes header file.

SerialCommunication.h

#include "resource.h"       // main 

symbols
#include <atlctl.h>

#include "Serial.h"

#include "CommunicationCategory.h"


/////////////////////////////////////////////////////////////////////////

// CSerialCommunication

#include "SerialCommunicationsCP.h"

class ATL_NO_VTABLE CSerialCommunication :
    public CComObjectRootEx<CComSingleThreadModel>,
    public IDispatchImpl<ISerialCommunication,
        &IID_ISerialCommunication, 
&LIBID_SERIALCOMMUNICATIONSLib>,
    public CComControl<CSerialCommunication>,
    public IPersistStreamInitImpl<CSerialCommunication>,
    public IOleControlImpl<CSerialCommunication>,
    public IOleObjectImpl<CSerialCommunication>,
    public IOleInPlaceActiveObjectImpl<CSerialCommunication>,
    public IViewObjectExImpl<CSerialCommunication>,
    public IOleInPlaceObjectWindowlessImpl<CSerialCommunication>,
    public IConnectionPointContainerImpl<CSerialCommunication>,
    public IPersistStorageImpl<CSerialCommunication>,
    public ISpecifyPropertyPagesImpl<CSerialCommunication>,
    public IQuickActivateImpl<CSerialCommunication>,
    public IDataObjectImpl<CSerialCommunication>,
    public IProvideClassInfo2Impl<&CLSID_SerialCommunication,
          &DIID__ISerialCommunicationEvents, 
&LIBID_SERIALCOMMUNICATIONSLib>,
    public IPropertyNotifySinkCP<CSerialCommunication>,
    public CComCoClass<CSerialCommunication, 
&CLSID_SerialCommunication>,
    public CProxy_ISerialCommunicationEvents< CSerialCommunication >,
    public CProxy_ICommunicationEvents< CSerialCommunication >,
    public IDispatchImpl<ICommunication, &IID_ICommunication,
                                  &LIBID_SERIALCOMMUNICATIONSLib>
{
public:
    CSerialCommunication();

DECLARE_REGISTRY_RESOURCEID(IDR_SERIALCOMMUNICATION)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CSerialCommunication)
    COM_INTERFACE_ENTRY(ISerialCommunication)
//  COM_INTERFACE_ENTRY(ICommunication)

//  COM_INTERFACE_ENTRY(IDispatch)

    COM_INTERFACE_ENTRY(IViewObjectEx)
    COM_INTERFACE_ENTRY(IViewObject2)
    COM_INTERFACE_ENTRY(IViewObject)
    COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
    COM_INTERFACE_ENTRY(IOleInPlaceObject)
    COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
    COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
    COM_INTERFACE_ENTRY(IOleControl)
    COM_INTERFACE_ENTRY(IOleObject)
    COM_INTERFACE_ENTRY(IPersistStreamInit)
    COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
    COM_INTERFACE_ENTRY(IConnectionPointContainer)
    COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
    COM_INTERFACE_ENTRY(IQuickActivate)
    COM_INTERFACE_ENTRY(IPersistStorage)
    COM_INTERFACE_ENTRY(IDataObject)
    COM_INTERFACE_ENTRY(IProvideClassInfo)
    COM_INTERFACE_ENTRY(IProvideClassInfo2)
    COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
    COM_INTERFACE_ENTRY2(IDispatch, ISerialCommunication)
    COM_INTERFACE_ENTRY2(ICommunication, ISerialCommunication)
END_COM_MAP()

BEGIN_CATEGORY_MAP(CSerialCommunication)
    IMPLEMENTED_CATEGORY(CATID_COMMUNICATION)
END_CATEGORY_MAP()

BEGIN_PROP_MAP(CSerialCommunication)
    PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
    PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
    // Example entries

    // PROP_ENTRY("Property Description", dispid, clsid)

    // PROP_PAGE(CLSID_StockColorPage)

END_PROP_MAP()

BEGIN_CONNECTION_POINT_MAP(CSerialCommunication)
    CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
    CONNECTION_POINT_ENTRY(DIID__ISerialCommunicationEvents)
    CONNECTION_POINT_ENTRY(DIID__ICommunicationEvents)
END_CONNECTION_POINT_MAP()

BEGIN_MSG_MAP(CSerialCommunication)
    CHAIN_MSG_MAP(CComControl<CSerialCommunication>)
    DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
// Handler prototypes:

//  LRESULT MessageHandler(UINT uMsg, WPARAM wParam,

//                    LPARAM lParam, BOOL& bHandled);

//  LRESULT CommandHandler(WORD wNotifyCode, WORD wID,

//                        HWND hWndCtl, BOOL& bHandled);

//  LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);




// IViewObjectEx

    DECLARE_VIEW_STATUS(0)

// ISerialCommunication

public:
    HRESULT OnDraw(ATL_DRAWINFO& di);

// ICommunication

public:
    STDMETHOD(Initialize)(BSTR bstrParameters);
    STDMETHOD(Send)(BSTR bstrData);
    STDMETHOD(get_Connected)(/*[out, retval]*/ BOOL *pVal);
    STDMETHOD(put_Connected)(/*[in]*/ BOOL newVal);

protected:
    CString m_strPortName;
    CSerial::BaudRate m_BaudRate;
    CSerial::DataBits m_DataBits;
    CSerial::StopBits m_StopBits;
    CSerial::Parity m_Parity;
    CSerial::HandShaking m_HandShaking;
    CSerial m_Serial;
public:
    static void CALLBACK SerialCallback(WPARAM wParam1,
                           WPARAM wParam2, LPARAM lParam);
};

Now our component is complete, so lets create a test application.

Using the code

The test application for this article is essentially a terminal emulation application. It functions similarly to Hyperterminal or Telnet (without all the bells and whistles those 2 applications have). The first thing you should note is in the CWinApp::InitInstance function.

CommunicationsTest.cpp

//////////////////////////////////////////////////////////////////////////

// The one and only CCommunicationsTestApp object


CCommunicationsTestApp theApp;
GUID guidLib = GUID_NULL;

//////////////////////////////////////////////////////////////////////////

// CCommunicationsTestApp initialization


BOOL CCommunicationsTestApp::InitInstance()
{
    if (!InitATL())
        return FALSE;

    AfxEnableControlContainer();

    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);

    if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
    {
        return TRUE;
    }

    CoInitialize(NULL);

    // Standard initialization

    // If you are not using these features and wish to reduce the size

    //  of your final executable, you should remove from the following

    //  the specific initialization routines you do not need.


#ifdef _AFXDLL
    Enable3dControls(); // Call this when using MFC in a shared DLL

#else
    Enable3dControlsStatic(); // Call this when linking to MFC statically

#endif

    CConfigureDialog dlg1;
    if (IDOK == dlg1.DoModal())
    {
        GUID* libid = new GUID;
        BSTR    szCLSID;
        CString strKey;
        StringFromCLSID(dlg1.m_CLSID, &szCLSID);
        CString strCLSID(szCLSID);
        CString strValue;
        long lRet;
        strKey.Format(_T("CLSID\\%s\\TypeLib"), strCLSID);
        HRESULT hr = RegQueryValue(HKEY_CLASSES_ROOT,
                    strKey, strValue.GetBuffer(80), &lRet);
        strValue.ReleaseBuffer(lRet);
        if (SUCCEEDED(hr))
        {
            CLSIDFromString(strValue.AllocSysString(), libid);
            guidLib = *libid;

            CCommunicationsTestDlg dlg;
            dlg.m_CLSID = dlg1.m_CLSID;
            dlg.m_strParams = dlg1.m_strParameters;
            m_pMainWnd = &dlg;
            int nResponse = dlg.DoModal();
            if (nResponse == IDOK)
            {
                // TODO: Place code here to handle when the dialog is

                //  dismissed with OK

            }
            else if (nResponse == IDCANCEL)
            {
                // TODO: Place code here to handle when the dialog is

                //  dismissed with Cancel

            }
        }
        delete libid;
    }

    // Since the dialog has been closed, return FALSE so that we exit the

    //  application, rather than start the application's message pump.

    return FALSE;
}

The CConfigureDialog class does the same thing that CConfigDlg class did in Part I. That is, it enumerates the components of a particular category and allows the user to select which one to use. Also, note that under the global theApp declaration, I have declared a GUID global. This variable will store the GUID for the type library that gives us the events we want to receive. Using a global variable for this is not the best way to accomplish this, but again, it is for the sake of simplicity.

Now to look at the CCommunicationsTestDlg class.

CommunicationsTestDlg.h

#include "Communications.h"


extern GUID guidLib;

/////////////////////////////////////////////////////////////////////////////

// CCommunicationsTestDlg dialog


class CCommunicationsTestDlg : public CDialog,
           public IDispEventImpl<1, CCommunicationsTestDlg,
                 &DIID__ICommunicationEvents, &guidLib, 1, 0>
{
// Construction

public:
    CCommunicationsTestDlg(CWnd* pParent = NULL);  // standard constructor

    virtual ~CCommunicationsTestDlg();

// Dialog Data

    //{{AFX_DATA(CCommunicationsTestDlg)

    enum { IDD = IDD_COMMUNICATIONSTEST_DIALOG };
    CEdit    m_wndLog;
    CEdit    m_wndData;
    //}}AFX_DATA

    CComPtr<ICommunication>    m_pComm;
    CString m_strParams;
    CLSID  m_CLSID;

    // ClassWizard generated virtual function overrides

    //{{AFX_VIRTUAL(CCommunicationsTestDlg)

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

    //}}AFX_VIRTUAL


// Implementation

protected:
    HICON m_hIcon;

    // Generated message map functions

    //{{AFX_MSG(CCommunicationsTestDlg)

    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    afx_msg void OnSend();
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //}}AFX_MSG

    afx_msg void __stdcall OnCommConnected();
    afx_msg void __stdcall OnCommSend();
    afx_msg void __stdcall OnCommReceive(/*LPCTSTR*/ BSTR bstrData);
    afx_msg void __stdcall OnCommClose();
    DECLARE_MESSAGE_MAP()

public:
    BEGIN_SINK_MAP(CCommunicationsTestDlg)
        SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
                       1 /* OnConnected */, OnCommConnected)
        SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
                       2 /* OnSend */, OnCommSend)
        SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
                       3 /* OnReceive */, OnCommReceive)
        SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
                       4 /* OnClose */, OnCommClose)
    END_SINK_MAP()
};

There are several things to note in this file. First, we included the header file for the Communications category interface. This gives the dialog, access to the v-table it needs to call methods in our components as well as receive events. The guidLib variable is a global that we want access to, so it is extern'ed. To give the dialog an event sink, we add IDispEventImpl as a base class. This will enable us to use ATL's SINK_MAP macros. Next we define our variables. One for the actual Communications pointer, one for storing its GUID, and the other for holding its initialization string. Next, we define callback functions to use as event handlers. Doing this is fairly straightforward, but currently there is no wizard support for this, so you must do it by hand. Finally, we use ATL's SINK_MAP macros to attach the events to the handlers we just defined. Now all we have left to do is implement these new functions and override the OnCreate function.

CommunicationsTestDlg.cpp

void CCommunicationsTestDlg::OnSend()
{
    CString strData, strLog, str;
    m_wndData.GetWindowText(strData);
    m_wndLog.GetWindowText(strLog);
    strData += _T("\r\n");
    str.Format("SEND> %s", strData);
    strLog += str;
    m_wndLog.SetWindowText(strLog);
    m_wndLog.LineScroll(m_wndLog.GetLineCount());

    m_pComm->Send(strData.AllocSysString());
}

int CCommunicationsTestDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CDialog::OnCreate(lpCreateStruct) == -1)
        return -1;

    HRESULT hr = CoCreateInstance(m_CLSID, NULL, CLSCTX_ALL,
                          IID_ICommunication, (void**)&m_pComm);
    if (SUCCEEDED(hr))
    {
        m_pComm->Initialize(m_strParams.AllocSysString());
        m_pComm->put_Connected(TRUE);
        DispEventAdvise(m_pComm);
    }
    else
    {
        TRACE("CoCreateInstance Failed!\n");
        return -1;
    }

    return 0;
}

void CCommunicationsTestDlg::OnCommConnected()
{
    TRACE("Connection Open\n");
}

void CCommunicationsTestDlg::OnCommSend()
{
    TRACE("Data Sent\n");
}

void CCommunicationsTestDlg::OnCommReceive(/*LPCTSTR*/BSTR bstrData)
{
    CString strData(bstrData);
    TRACE("Received %s\n", bstrData);
    CString strLog, str;
    strData.TrimRight(_T("\r\n"));
    m_wndLog.GetWindowText(strLog);
    strData += _T("\r\n");
    str.Format("RECV> %s", strData);
    strLog += str;
    m_wndLog.SetWindowText(strLog);
    m_wndLog.LineScroll(m_wndLog.GetLineCount());
}

void CCommunicationsTestDlg::OnCommClose()
{
    TRACE("Connect Closed\n");
}

Points of interest

This is a fairly advanced example of designing, implementing, and using component categories to create pluggable objects. There is a lot of information that goes into accomplishing this, and I'm sure I have not touched on it all, but I hope this is at least a starting point for anyone looking to design a component based application.

Also, just as in Part I, I only adjusted the configuration settings for the debug configuration. Thus, the Release builds will not work unless you change them to match the Debug configuration (specifically, the include files, MIDL includes, and the build options for the Communications.idl file).

Links

History

  • First revision: July 23rd, 2003.
  • Second revision: September 15th, 2003.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here