Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / ATL
Article

Pluggable Components using Component Categories - Part II

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
18 Sep 20037 min read 82.2K   1.1K   46   12
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


Written By
Web Developer
United States United States
I started programming at 15 with a TI-82 calclator in Z80 assembly (oh, those were the days . . .) I am pretty much a self taught programmer. I've taught myself Visual Basic, C/C++, Java, and am currently working on C#. I also like to experiment with system administration and security issues, and occassionally I work on web design. For the last 4 years, I have worked for Leitch, Inc. as a Software Engineer and graduated from Old Dominion University with bachelor's degrees in Computer Science, Mathematics, and Business Management in December of 2004.

Comments and Discussions

 
QuestionAbout ATL Pin
showny24-Jul-07 21:52
showny24-Jul-07 21:52 
GeneralIs it possible... Pin
MarcellaElq12-Dec-05 2:08
MarcellaElq12-Dec-05 2:08 
GeneralRe: Is it possible... Pin
Zac Howland12-Dec-05 3:55
Zac Howland12-Dec-05 3:55 
GeneralRe: Is it possible... Pin
MarcellaElq12-Dec-05 4:15
MarcellaElq12-Dec-05 4:15 
GeneralRe: Is it possible... Pin
Zac Howland12-Dec-05 4:29
Zac Howland12-Dec-05 4:29 
What the ATL/COM stuff does is actually create an abstract base class (which is what all COM components use). If you wanted to strip out the COM stuff, you could fairly easily create an abstract base class.

For example, the ICommunications interface (which is the interface the category expects to be exposed) is the base class for the ISerialCommunications, ISocketCommunications, and ITelnetCommunications interfaces. It declares 3 pure-virtual methods: Initialize, Get/Set Connected, and Send (note that you will probably want more methods than that, but that is all that I required for the article). Declaring a C++ abstract base class that has the same methods would be akin to the COM version used here. Since no class is directly inheriting from ICommunication, you won't find an implementation for it (directly). Instead, you see CSerialCommunication (for example) that is derived from ISerialCommunication which handles the implementation of the methods exposed in the ICommunication interface.

NOTE: There are also events exposed in these interfaces (OnConnected, OnSend, OnReceive, OnClose). To convert to pure C++, you will either have to convert those to Callback functions, or use actual Windows-events (or *Nix-signals).

If you decide to become a software engineer, you are signing up to have a 1/2" piece of silicon tell you exactly how stupid you really are for 8 hours a day, 5 days a week

Zac
GeneralRe: Is it possible... Pin
MarcellaElq12-Dec-05 4:50
MarcellaElq12-Dec-05 4:50 
GeneralRe: Is it possible... Pin
Zac Howland12-Dec-05 5:47
Zac Howland12-Dec-05 5:47 
GeneralRe: Is it possible... Pin
MarcellaElq12-Dec-05 5:51
MarcellaElq12-Dec-05 5:51 
GeneralChoice : COM Pluggable vs DLL Pluggable Pin
Garth J Lancaster17-Nov-03 13:57
professionalGarth J Lancaster17-Nov-03 13:57 
GeneralRe: Choice : COM Pluggable vs DLL Pluggable Pin
Zac Howland7-Sep-05 10:29
Zac Howland7-Sep-05 10:29 
GeneralRe: Choice : COM Pluggable vs DLL Pluggable Pin
Garth J Lancaster7-Sep-05 11:47
professionalGarth J Lancaster7-Sep-05 11:47 
Generalis voice apllication possible Pin
imgemini1-Sep-03 7:02
imgemini1-Sep-03 7:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.