Средства разработки приложений

         

Создание клиентского приложения для обоих COM объектов


Мы собираемся создать клиентское приложение, которое будет поддерживать два COM объекта GasTankLevelGetter и FishTankLevelGetter. Используя AppWizard, создайте MFC диалог приложения, который бы поддерживал и управляющие элементы Automation, и ActiveX одновременно (укажите это в соответствующих check box во время работы с AppWizard).

Как только вы создали приложение, отредактируйте ваш основной диалог в редакторе ресурсов, так чтобы он имел сходство с следующим:

  • Замечание: Возможно вы захотите просмотреть ID элементов управления в примере, поскольку мы собираемся изменить эти значения.
  • Следующий шаг состоит в добавлении указателей сообщений для двух кнопок Gas Tank Level и Fish Tank Level. В примере эти методы называются OnGas и OnFish соответственно

    Если вы создали класс диалога и добавили указатели сообщений для кнопок, вам необходимо открыть этот класс и добавить несколько членов класса и методов класса. Первое, что мы сделаем, - это опишем далее интерфейс ILevelGetter так, чтобы мы могли добавлять члены класса (class member) для этого типа интерфейса. Во-вторых, добавим два дополнительных метода класса (class methods) ClearMembers и SetNewData и два члена класса m_pILevelGetter и m_sLastCalled. Затем, используя Class Wizard, добавим методы OnDestroy и OnTimer. Как только это сделано, ваше описание класса должно быть таким, как показано ниже.

  • //forward declaration so for our class member interface ILevelGetter; class CLevelViewerDlg : public CDialog { DECLARE_DYNAMIC(CLevelViewerDlg); friend class CLevelViewerDlgAutoProxy; public: CLevelViewerDlg(CWnd* pParent = NULL); // standard constructor virtual ~CLevelViewerDlg(); //{{AFX_DATA(CLevelViewerDlg) enum { IDD = IDD_LEVELVIEWER_DIALOG }; //}}AFX_DATA //{{AFX_VIRTUAL(CLevelViewerDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: CLevelViewerDlgAutoProxy* m_pAutoProxy; HICON m_hIcon; BOOL CanExit(); //added by manually typing these into the class void ClearMembers(); void SetNewData(const CLSID& clsid, const IID& iid); ILevelGetter* m_pILevelGetter; CString m_sLastCalled; // Generated message map functions //{{AFX_MSG(CLevelViewerDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnClose(); virtual void OnOK(); virtual void OnCancel(); //added by the Class Wizard afx_msg void OnFish(); afx_msg void OnGas(); afx_msg void OnDestroy(); afx_msg void OnTimer(UINT nIDEvent); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
  • Далее изменим файл описания реализации (implementation file).
    В конструкторе класса проинициализируйте переменные членов класса как это показано ниже:

    //-------------------------------------------------------------- CLevelViewerDlg::CLevelViewerDlg(CWnd* pParent /*=NULL*/) : CDialog(CLevelViewerDlg::IDD, pParent) { //{{AFX_DATA_INIT(CLevelViewerDlg) //}}AFX_DATA_INIT m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_pAutoProxy = NULL; m_pILevelGetter = NULL; m_sLastCalled = _T("CheckedGas"); } Реализация метода ClearMembers приводится далее. Эта функция очищает элементы управления диалога (dialog controls). (Отметим, что мы использовали бы Dialog Data exchange для членов класса.)

    //-------------------------------------------------------------------- void CLevelViewerDlg::ClearMembers() { CWnd* pWnd = GetDlgItem(IDC_TANK_TYPE); if(pWnd != NULL) pWnd->SetWindowText(""); pWnd = GetDlgItem(IDC_LOWEST_SAFE); if(pWnd != NULL) pWnd->SetWindowText(""); pWnd = GetDlgItem(IDC_HIGHEST_SAFE); if(pWnd != NULL) pWnd->SetWindowText(""); pWnd = GetDlgItem(IDC_CURRENT); if(pWnd != NULL) pWnd->SetWindowText(""); pWnd = GetDlgItem(IDC_MESSAGE); if(pWnd != NULL) pWnd->SetWindowText(""); } OnDestroy, показанный ниже, используется для очистки при закрытии диалога.

    //-------------------------------------------------------------------- void CLevelViewerDlg::OnDestroy() { CDialog::OnDestroy(); KillTimer(1); } Данный класс использует OnTimer для вызова методов кнопок OnFish и OnGas так, что пользователю не требуется нажимать кнопки для обновления данных.

    //-------------------------------------------------------------------- void CLevelViewerDlg::OnTimer(UINT nIDEvent) { if(m_sLastCalled == _T("CheckedFish")) OnGas(); else OnFish(); }

  • Замечание: В реальной жизни предпочтительнее использовать технологию с нажатием кнопок и интерфейс IConnectionPoint. Законченный пример такой реализации вы найдете на http://www.microsoft.com/workshop/prog/com/overview-f.htm.
  • Виртуальная функция OnInitDialog используется в основном для запуска таймера, хотя она также возвращает данные из GasTankLevelGetter COM DLL.



    //-------------------------------------------------------------------- BOOL CLevelViewerDlg::OnInitDialog() { CDialog::OnInitDialog(); SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon OnGas(); //obtain data SetTimer(1, 4000, NULL); //set timer for 4 seconds return TRUE; // return TRUE unless you set the focus to a control } Теперь мы готовы описать реализацию наших методов кнопок OnFish и OnGas, которые вызываются попеременно каждые 4 секунды. Обе эти функции идентичны на процедурном уровне; они передают CLSID и IID в SetNewData. Единственная разница состоит в том, что CLSID и IID, передаваемые методом OnGas, используются в GasTankLevelGetter, а CLSID и IID передаваемые методом OnFish, - в FishTankLevelGetter.

    OnGas возвращает CLSID, взятый из строки GUID, которая имеется в данных coclass TypeLib. Таким же образом возвращается IID и, кроме того, он отображается в OLE/COM Object Viewer. Как только получены GUID, вызывается SetNewData.

    //-------------------------------------------------------------------- void CLevelViewerDlg::OnGas() { m_sLastCalled = _T("CheckedGas"); CLSID clsid; IID iid; HRESULT hRes; hRes = AfxGetClassIDFromString( "{8A544DC6-F531-11D0-A980-0020182A7050}", &clsid); if(SUCCEEDED(hRes)) { hRes = AfxGetClassIDFromString( "{8A544DC5-F531-11D0-A980-0020182A7050}", &iid); if(SUCCEEDED(hRes)) SetNewData(clsid, iid); } } Метод SetNewData, показанный ниже, создает instance в GasTankLevelGetter COM объекте или FishTankLevelGetter COM объекте в зависимости от CLSID. После этого SetNewData вызывает методы интерфейса ILevelGetter для получения данных.

    //-------------------------------------------------------------------- void CLevelViewerDlg::SetNewData(const CLSID& clsid, const IID& iid) { ClearMembers(); ASSERT(m_pILevelGetter == NULL); HRESULT hRes = CoCreateInstance(clsid, NULL, CLSCTX_ALL, iid, (void**)&m_pILevelGetter); if(!SUCCEEDED(hRes)) { m_pILevelGetter = NULL; return; } long lLowestSafeLevel, lHighestSafeLevel, lCurrentLevel; BSTR bstrMessage = NULL; m_pILevelGetter->GetLowestPossibleSafeLevel(&lLowestSafeLevel); m_pILevelGetter->GetHighestPossibleSafeLevel(&lHighestSafeLevel); m_pILevelGetter->GetCurrentLevel(&lCurrentLevel); m_pILevelGetter->GetTextMessage(&bstrMessage); m_pILevelGetter->Release(); m_pILevelGetter = NULL; CString sLowest, sHighest, sCurrent, sMessage; sLowest.Format("%d",lLowestSafeLevel); sHighest.Format("%d",lHighestSafeLevel); sCurrent.Format("%d",lCurrentLevel); sMessage = bstrMessage; ::SysFreeString(bstrMessage); CString sItem; if(m_sLastCalled == _T("CheckedFish")) { //we are checking the fish tank now sItem = _T("Fish Tank"); } else //m_sLastCalled == _T("CheckedGas") { //we are checking the fish tank now sItem = _T("Gas Tank"); } CWnd* pWnd = GetDlgItem(IDC_TANK_TYPE); if(pWnd != NULL) pWnd->SetWindowText(sItem); pWnd = GetDlgItem(IDC_LOWEST_SAFE); if(pWnd != NULL) pWnd->SetWindowText(sLowest); pWnd = GetDlgItem(IDC_HIGHEST_SAFE); if(pWnd != NULL) pWnd->SetWindowText(sHighest); pWnd = GetDlgItem(IDC_CURRENT); if(pWnd != NULL) pWnd->SetWindowText(sCurrent); pWnd = GetDlgItem(IDC_MESSAGE); if(pWnd != NULL) pWnd->SetWindowText(sMessage); } Поскольку интерфейсы одинаковы, мы уверены, что методы будут работать с обоими COM объектами.


    Последние два шага должны реализовать OnFish и включить определение интерфейса.

    //-------------------------------------------------------------------- void CLevelViewerDlg::OnFish() { m_sLastCalled = _T("CheckedFish"); CLSID clsid; IID iid; HRESULT hRes = AfxGetClassIDFromString( "{7F0DFAA3-F56D-11D0-A980-0020182A7050}", &clsid); if(SUCCEEDED(hRes)) hRes = AfxGetClassIDFromString( "{7F0DFAA2-F56D-11D0-A980-0020182A7050}", &iid); if(SUCCEEDED(hRes)) SetNewData(clsid, iid); } Определение интерфейса, созданное чисто виртуальными членами класса, включается в верхнюю часть файла описания реализации ( хотя его можно поместить в описание класса или отдельный .h файл), так что член класса m_pILevelGetter типа ILevelGetter* "знает" свои методы. Определение интерфейса представлено ниже:

    //------------------------------------------------------------------ interface ILevelGetter : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetLowestPossibleSafeLevel(long* plLowestSafeLevel) = 0; virtual HRESULT STDMETHODCALLTYPE GetHighestPossibleSafeLevel(long* plLowestSafeLevel) = 0; virtual HRESULT STDMETHODCALLTYPE GetCurrentLevel(long* plLowestSafeLevel) = 0; virtual HRESULT STDMETHODCALLTYPE GetTextMessage(BSTR* pbstrMessage) = 0; }; Теперь мы готовы откомпилировать, слинковать и запустить приложение. Если вы запустили приложение, вы можете щелкнуть по какой-либо кнопке, чтобы переключить COM компоненты, или позволить таймеру переключать их автоматически каждые четыре секунды. И только теперь Ричи Рич сможет спокойно лететь на своем вертолете и следить за уровнем воды в своем аквариуме.


    Содержание раздела