65.9K
CodeProject 正在变化。 阅读更多。
Home

通过组件类别实现可插入组件 - 第 II 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2003年7月28日

7分钟阅读

viewsIcon

85061

downloadIcon

1140

一篇关于使用组件类别创建可插拔组件的文章。

Initialization

Initialization

Application

引言

在本系列的第1部分中,我讨论了如何从一个简单的类别创建一个非常基础的组件。该类别规定组件实现一个单一的方法 (Draw)。在本文中,我将更进一步。请准备进入连接点和类型库的领域。

背景(可选)

再次声明,为了节省时间和篇幅,我将假定您对 COM 和 ATL 有基本的了解。此外,我不会讨论这些控件所包含的通信类别的实现。这些是我多年来创建或修改过的类,并保存在我的有用对象集合中。由于我已经不记得其中某些代码的来源,我将感谢所有直接帮助过我或我曾借鉴过代码的人,在我开发这些类的时候。

如果您尝试使用第1部分中讨论的模型来开发组件,很快就会遇到需要更多功能的组件。例如,假设您想创建一个控件,允许您通过串行端口发送和接收文本数据。发送数据是一个调用方法的问题;但是,接收数据会带来一个问题。您想不断地轮询控件以查看是否有数据吗?可能不想。一个更简洁的方法是让控件在收到数据时通知您。要实现这一点,我们需要使用连接点。请注意,连接点等同于 ActiveX 事件。那么,我们如何设置连接点,使其与类别中的每个组件一致呢?毕竟,这不就是类别的目的吗?幸运的是,设置类别接口并不比以前更困难。

代码

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;
    };
};

在这里,我们创建了一个 IDL,它描述了一个派生自 IDispatch 的接口 ICommunication,它有两个方法和一个属性。此外,它还有一个具有 4 个方法的事件接口。这与任何其他双重接口声明没有区别。现在不要将此文件添加到您的项目中。Visual C++ 不喜欢在看到已存在类型库时添加新的类型库。我们还需要创建一个类别 ID。这就像以前一样,使用 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 } };

现在是时候使用这个接口了。在本文的其余部分,我将使用 SerialCommunications 对象作为示例;但是,套接字和 telnet 版本也是以类似的方式创建的。首先,创建一个完整的控件。使用 ATL COM AppWizard 来完成此操作很容易,但如果您愿意,也可以手动完成。以下是 SerialCommunications 组件的 IDL。

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;
    };
};

此文件中有些地方需要注意。首先,我们导入了 Communications.idl 文件。这将使我们能够从 ICommunication 接口派生我们的接口。它还将导致 MIDL 创建 Communication_i.cCommunication.h 文件,其中包含接口定义和 GUID 的 C/C++ 版本。接下来,我们从 ICommunication 继承了我们的接口 ISerialCommunication。最后,在类型库定义中,我们修改了 coclass 定义。将 _ICommunicationEvents 接口声明为事件的默认源接口,使我们能够始终获得这些事件。如果我们愿意,它还允许我们向 _ISerialCommunicationEvents 接口添加专门的事件。

目前,我们的代码无法编译。ISerialCommunication 接口的实现类缺少一些函数声明。以下是我们需要的更改。

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);
};

我们需要包含带有 CATID 定义的头文件。这使我们能够使用 CATEGORY_MAP 宏将我们的组件关联到类别。我们还需要在继承列表中添加 ICommunication 接口的派发实现。这使我们能够访问实现类中的方法和属性。此外,请注意,IDispatchICommunication 的接口条目已修改为使用 COM_INTERFACE_ENTRY2 宏。由于多重继承,我们不得不这样做。如果我们尝试使用 COM_INTERFACE_ENTRY 宏,会有多个继承路径指向 ICommunicationIDispatch 接口。为了避免这种情况,我们专门告诉编译器如何访问这些接口。最后,我们需要添加方法的声明。类声明底部的变量和静态回调函数用于 CSerial 类。

我们还需要为这些函数添加实现。

#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());
    }
}

Split.h 文件包含在演示项目的每个项目中。它允许通过在分隔符上拆分 CStrings 并将结果字符串放入 CStringArray 中来快速解析。这消除了对复杂初始化函数的需求,因为它允许我们传入单个分隔字符串。例如,使用串行端口有 6 个配置参数;然而,配置 telnet 会话或套接字连接只有 2 个。这不是实现这种功能最好的方法,但为了简单起见,目前就可以了。

此步骤中的其余部分都很直接。

我们仍然需要实现我们的连接点。要做到这一点,在类视图中右键单击实现类。选择“实现连接点”,然后在出现的对话框中选择 _ISerialCommunicationEvents_ICommunicationEvents。向导将创建 2 个代理类,并将以下代码添加到您的实现类头文件中。

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);
};

现在我们的组件已经完成,让我们创建一个测试应用程序。

使用代码

本文的测试应用程序本质上是一个终端仿真应用程序。它的功能类似于 Hyperterminal 或 Telnet(没有这两个应用程序的所有花哨功能)。您应该注意的第一件事是 CWinApp::InitInstance 函数。

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;
}

CConfigureDialog 类与第 I 部分中的 CConfigDlg 类做了相同的事情。也就是说,它枚举特定类别的组件并允许用户选择要使用的组件。另外,请注意,在全局 theApp 声明下,我声明了一个全局 GUID。这个变量将存储类型库的 GUID,该类型库为我们提供了我们想要接收的事件。使用全局变量来实现这一点不是最好的方法,但同样,这是为了简单起见。

现在来看 CCommunicationsTestDlg 类。

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()
};

此文件中需要注意几点。首先,我们包含了通信类别接口的头文件。这使得对话框能够访问 v-table,以调用我们组件中的方法以及接收事件。guidLib 变量是一个我们想要访问的全局变量,因此被声明为 extern。为了给对话框一个事件接收器,我们将 IDispEventImpl 添加为基类。这将使我们能够使用 ATL 的 SINK_MAP 宏。接下来我们定义我们的变量。一个用于实际的 Communications 指针,一个用于存储其 GUID,另一个用于保存其初始化字符串。接下来,我们定义回调函数作为事件处理程序。这样做相当简单,但目前没有向导支持,所以您必须手动完成。最后,我们使用 ATL 的 SINK_MAP 宏将事件附加到我们刚刚定义的处理程序。现在我们剩下要做的就是实现这些新函数并重写 OnCreate 函数。

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");
}

关注点

这是一个相当高级的示例,展示了如何设计、实现和使用组件类别来创建可插入对象。要实现这一点需要大量信息,我敢肯定我并没有涵盖所有方面,但我希望这至少是任何希望设计基于组件的应用程序的人的一个起点。

同样,就像在第 I 部分一样,我只调整了调试配置的设置。因此,如果未更改 Release 生成的设置以匹配 Debug 配置(特别是,包含文件、MIDL 包含以及 Communications.idl 文件的生成选项),则 Release 生成将无法工作。

链接

历史

  • 首次修订:2003 年 7 月 23 日。
  • 第二次修订:2003 年 9 月 15 日。
© . All rights reserved.