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
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"
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(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)
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()
DECLARE_VIEW_STATUS(0)
public:
HRESULT OnDraw(ATL_DRAWINFO& di);
public:
STDMETHOD(Initialize)(BSTR bstrParameters);
STDMETHOD(Send)(BSTR bstrData);
STDMETHOD(get_Connected)( BOOL *pVal);
STDMETHOD(put_Connected)( 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()
{
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;
(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())
CString strParameters(bstrParameters);
CStringArray straParameters;
Split(strParameters, _T(":"), straParameters);
if (6 != straParameters.GetSize())
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"
#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(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)
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()
DECLARE_VIEW_STATUS(0)
public:
HRESULT OnDraw(ATL_DRAWINFO& di);
public:
STDMETHOD(Initialize)(BSTR bstrParameters);
STDMETHOD(Send)(BSTR bstrData);
STDMETHOD(get_Connected)( BOOL *pVal);
STDMETHOD(put_Connected)( 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
CCommunicationsTestApp theApp;
GUID guidLib = GUID_NULL;
BOOL CCommunicationsTestApp::InitInstance()
{
if (!InitATL())
return FALSE;
AfxEnableControlContainer();
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
{
return TRUE;
}
CoInitialize(NULL);
#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();
#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)
{
}
else if (nResponse == IDCANCEL)
{
}
}
delete libid;
}
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;
class CCommunicationsTestDlg : public CDialog,
public IDispEventImpl<1, CCommunicationsTestDlg,
&DIID__ICommunicationEvents, &guidLib, 1, 0>
{
public:
CCommunicationsTestDlg(CWnd* pParent = NULL);
virtual ~CCommunicationsTestDlg();
enum { IDD = IDD_COMMUNICATIONSTEST_DIALOG };
CEdit m_wndLog;
CEdit m_wndData;
CComPtr<ICommunication> m_pComm;
CString m_strParams;
CLSID m_CLSID;
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
HICON m_hIcon;
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 void __stdcall OnCommConnected();
afx_msg void __stdcall OnCommSend();
afx_msg void __stdcall OnCommReceive( BSTR bstrData);
afx_msg void __stdcall OnCommClose();
DECLARE_MESSAGE_MAP()
public:
BEGIN_SINK_MAP(CCommunicationsTestDlg)
SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
1 , OnCommConnected)
SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
2 , OnCommSend)
SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
3 , OnCommReceive)
SINK_ENTRY_EX(1, DIID__ICommunicationEvents,
4 , 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(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.