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

         

Часть 1: Дублирование интерфейсов


В вышеприведенном случае с Ричи Ричем мы видели, что DLL для аквариума и DLL для топливного бака не могли находиться на одном и том же компьютере, потому что ни клиентское приложение, ни две DLL не были COM компонентами. Какая бы DLL ни была скопирована на компьютер, только скопированная последней будет использоваться клиентским приложением. Как мы уже видели, использование некорректной DLL может привести к катастрофическим результатам: вертолет разбился. Мы предположили, что если бы разработчик программы использовал технологию COM, то он имел бы обе DLL на машине. Поскольку две DLL были бы различимы по их CLSID, они могли бы использоваться в пределах одного приложения. По технологии COM обе DLL должны задействовать идентичные методы через заменяемые интерфейсы.

Чтобы это доказать, мы собираемся создать единственное GUI-приложение, которое использует и показывает информацию, получаемую от двух серверов COM: GasTankLevelGetter DLL и FishTankLevelGetter DLL. Мы также создадим одно приложение, которое будет получать информацию от каждой COM DLL и отображать их. Опрос каждой DLL будет происходить попеременно по четырехсекундному таймеру. Чтобы подчеркнуть неизменность интерфейсов и что COM является двоичным стандартом, мы собираемся написать GUI-приложение FishTankLevelGetter COM DLL исключительно на основе информации о GasTankLevelGetter COM DLL. Однако мы не собираемся предоставлять вам исходный код GasTankLevelGetter COM DLL. Если вы переписали пример, вы найдете GasTankLevelGetter COM DLL в папке Binaries. Мы вам даже не скажем на чем написана GasTankLevelGetter: на Delphi, Visual C++, Java(tm), Cobol, Turbo Pascal или Visual Basic. Вам, однако, придется зарегистрировать GasTankLevelGetter DLL с помощью RegSvr32.

Как только вы зарегистрировали GasTankLevelGetter DLL с помощью RegSvr32, вы готовы начать, вооружившись OLE/COM Object Viewer. Если вы используете Visual C++ 5.0, OLE/COM Object Viewer находится в программной группе Visual C++ 5.0 при навигации через Start | Programs в Explorer.
Если у вас нет OLE/COM Object Viewer, спишите его из http://www.microsoft.com/oledev/ и запустите приложение.

Запустив OLE/COM Object Viewer, выберите режим View | Expert для просмотра Type Libraries. Пролистайте список и откройте папку под названием Type Libraries. Пролистайте папку пока не найдете GasTankLevelGetter 1.0 TypeLibrary (Ver 1.0). Выделите этот элемент списка и вы увидите на правой панели ID библиотеки типа и ее полный путь, как показано на рисунке.



Двойной щелчок на GasTankLevelGetter откроет окно, отображающее всю библиотеку типа. Эта информация берется их данных регистров, которые создаются при регистрации DLL. Данные по TypeLib хранятся в HKEY_CLASSES_ROOT \ TypeLib.



Раздел coclass содержит список поддерживаемых интерфейсов для компонентного объекта. Объект может иметь любое количество интерфейсов, перечисляемых в его теле и полностью описывающих тот набор интерфейсов, которые этот объект реализует, как входящих, так и исходящих. Ниже приведены CLSID и интерфейс, содержащиеся в coclass для данного COM объекта:

CLSID: 8A544DC6-F531-11D0-A980-0020182A7050
Interface Name: ILevelGetter

[ uuid(8A544DC6-F531-11D0-A980-0020182A7050), helpstring("LevelGetter Class") ] coclass LevelGetter { [default] interface ILevelGetter; }; Раскрывая далее информацию по интерфейсу наподобие coclass, мы можем определить:

  • ID интерфейса 8A544DC5-F531-11D0-A9 80-0020182A7050.


  • Интерфейс наследуется от IUnknown.
  • Интерфейс поддерживает методы. Первые три метода возвращают значения типа long, а четвертый - указатель на BSTR.
[ odl, uuid(8A544DC5-F531-11D0-A980-0020182A7050), helpstring("ILevelGetter Interface") ] interface ILevelGetter : IUnknown { HRESULT _stdcall GetLowestPossibleSafeLevel([out, retval] long* plLowestSafeLevel); HRESULT _stdcall GetHighestPossibleSafeLevel([out, retval] long* plHighestSafeLevel); HRESULT _stdcall GetCurrentLevel([out, retval] long* plCurrentLevel); HRESULT _stdcall GetTextMessage([out, retval] BSTR* ppbstrMessage); }; Более детальный взгляд на структуру type library открывает нам методы и ID интерфейсов.







Теперь, поскольку мы знаем, как построить интерфейс ILevelGetter, давайте создадим наш собственный компонент COM на основе этой информации. Если вы решили работать с существующим примером, все источники находятся в папке LevelViewer. Запустите Visual C++ 5.0 и создайте новый проект. Определите тип ATLComAppWizard как проект и "FishTankLevelGetter" как имя проекта. Мы полагаем, что вы создали новую папку проекта. Окно New Project Dialog должно выглядеть как это показано ниже.



В AppWizard для Server Type укажите Dynamic Link Library (DLL). Отметьте обе опции Allow merging of proxy/stub code и Support MFC.



Когда вы создали новый проект FishTankLevelGetter, выберите в меню Insert | New Class... для создания нового ATL класса. Вы можете выбрать любое имя класса, но убедитесь, что интерфейс называется IlevelGetter, а его тип - Custom, что указывает на наследование ILevelGetter от IUnknown. Если бы ILevelGetter в GasTankLevelGetter COM DLL наследовалась от IDispatch, нам пришлось бы выбрать тип интерфейса Dual, который указывал бы на то, что новый интерфейс будет производным от IDispatch. Если диалог New Class выглядит как показано ниже, нажмите OK, чтобы создать новый класс.



Следующий шаг заключается в редактировании FishTankLevelGetter.IDL. В IDL файле вам нужно иметь новый интерфейс ILevelGetter, наследуемый из IUnknown. Если вы работаете с примерами, вы увидите следующий код, который содержит четыре одинаковых неизменяемых метода интерфейса IlevelGetter, которые мы видели в интерфейсе ILevelGetter GasTankLevelGetter.

  • [ object, uuid(7F0DFAA2-F56D-11D0-A980-0020182A7050), helpstring("ILevelGetter Interface"), pointer_default(unique) ] interface ILevelGetter : IUnknown { HRESULT GetLowestPossibleSafeLevel([out, retval] long* plLowestSafeLevel); HRESULT GetHighestPossibleSafeLevel([out, retval] long* plHighestSafeLevel); HRESULT GetCurrentLevel([out, retval] long* plCurrentLevel); HRESULT GetTextMessage([out, retval] BSTR* ppbstrMessage); };
  • Если вы пишите код, как и мы, самостоятельно, вы захотите добавить вышеуказанный код так, что ваш интерфейс соответствовал четырем идентичным неизменным методам.


    Наиболее просто добавить код с помощью " copy and paste" непосредственно из окна ITypeLib Viewer. Ваш код должен выглядеть точно также, как в примере, за исключением ID интерфейса.

    Откройте LevelGetter.H и объявите методы в классе. В вашем классе объявление методов должно выглядеть как это показано ниже:

  • class LevelGetter : public ILevelGetter, public CComObjectRoot, public CComCoClass<LevelGetter,&CLSID_LevelGetter> { public: LevelGetter(){} BEGIN_COM_MAP(LevelGetter) COM_INTERFACE_ENTRY(ILevelGetter) END_COM_MAP() //DECLARE_NOT_AGGREGATABLE(LevelGetter) // Remove the comment from the line above if you don't want your object to // support aggregation. DECLARE_REGISTRY_RESOURCEID(IDR_LevelGetter) // ILevelGetter public: //THE FOUR NEW METHODS STDMETHOD (GetLowestPossibleSafeLevel) (long* plLowestSafeLevel); STDMETHOD (GetHighestPossibleSafeLevel) (long* plHighestSafeLevel); STDMETHOD (GetCurrentLevel) (long* plCurrentLevel); STDMETHOD (GetTextMessage) (BSTR* ppbstrMessage); };
  • Вам теперь нужно сделать четыре метода. Для демонстрационных целей, давайте оставим методы простыми. Реализуйте их по вашему усмотрению или скопируете следующий код из образцов.

  • //--------------------------------------------------------------------------- STDMETHODIMP LevelGetter::GetLowestPossibleSafeLevel(long* plLowestSafeLevel) { *plLowestSafeLevel = 70; return S_OK; } //--------------------------------------------------------------------------- STDMETHODIMP LevelGetter::GetHighestPossibleSafeLevel(long* plHighestSafeLevel) { *plHighestSafeLevel = 98; return S_OK; } //--------------------------------------------------------------------------- STDMETHODIMP LevelGetter::GetCurrentLevel(long* plCurrentLevel) { *plCurrentLevel = 94; return S_OK; } //--------------------------------------------------------------------------- STDMETHODIMP LevelGetter::GetTextMessage(BSTR* ppbstrMessage) { *ppbstrMessage = ::SysAllocString(L"All clear, water level is fine"); return S_OK; }
  • Поскольку у вас уже есть методы, скомпилируйте и слинкуйте вашу COM DLL.Затем мы начнем создавать клиентское приложение.


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