Создание клиентского приложения для обоих COM объектов
Мы собираемся создать клиентское приложение, которое будет поддерживать два COM объекта GasTankLevelGetter и FishTankLevelGetter. Используя AppWizard, создайте MFC диалог приложения, который бы поддерживал и управляющие элементы Automation, и ActiveX одновременно (укажите это в соответствующих check box во время работы с AppWizard).
Как только вы создали приложение, отредактируйте ваш основной диалог в редакторе ресурсов, так чтобы он имел сходство с следующим:
Следующий шаг состоит в добавлении указателей сообщений для двух кнопок Gas Tank Level и Fish Tank Level. В примере эти методы называются OnGas и OnFish соответственно
Если вы создали класс диалога и добавили указатели сообщений для кнопок, вам необходимо открыть этот класс и добавить несколько членов класса и методов класса. Первое, что мы сделаем, - это опишем далее интерфейс ILevelGetter так, чтобы мы могли добавлять члены класса (class member) для этого типа интерфейса. Во-вторых, добавим два дополнительных метода класса (class methods) ClearMembers и SetNewData и два члена класса m_pILevelGetter и m_sLastCalled. Затем, используя Class Wizard, добавим методы OnDestroy и OnTimer. Как только это сделано, ваше описание класса должно быть таким, как показано ниже.
Далее изменим файл описания реализации (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(); }
//-------------------------------------------------------------------- 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 компоненты, или позволить таймеру переключать их автоматически каждые четыре секунды. И только теперь Ричи Рич сможет спокойно лететь на своем вертолете и следить за уровнем воды в своем аквариуме.