XPCOM часть четвертая и пятая: разработка компонентов

image

Вольный перевод четвертой части и пятой части статьи про технологию XPCOM от Rick Parrish.

XPCOM — кросс-платформенная объектная модель компонентов. Предназначение XPCOM заключается в предоставлении механизма создания программных модулей (компонентов) на множестве различных платформ. В предыдущих статьях рассматривалось как инициализировать XPCOM и как использовать существующие компоненты. В этой статье рассматривается как создать компонент.

Создание компонента

Создание компонента начинается с написания кода, который состоит из пяти шагов. Для каждого XPCOM компонента потребуется:

  1. Генерировать идентификатор класса UUID
  2. Генерировать идентификатор интерфейса UUID для каждого интерфейса
  3. Назначить идентификатор контракта  связанный с идентификатором класса
  4. Описать интерфейсы в IDL
  5. Скомпилировать IDL и используя сгенерированные заглушки, реализовать компонент

Рассмотрим эти пять шагов более детально. Для генерации идентификатора класса можно использовать GUIDGEN или любую другую GUID/UUID генерирующую утилиту. GUIDGEN удобно использовать, потому что он помещает результат в буфер обмена так что можно просто вставить значения прямиком в IDL файл. На втором шаге сгенерируйте один или более UUID по одному на каждый интерфейс, который будет реализовываться компонентом. На третьем этапе составляют значимый (или запоминающийся) идентификатор контракта. Напомним, что идентификатор контракта — текстовое имя компонента. Далее в текстовом редакторе все это собирается вместе и создается описание интерфейса. На последнем шаге IDL файл компилируется с помощью XPIDL.

Компилятор создаст XPT файл со всей информацией библиотеки типов, и C++ заглушку. Если даже ваш IDL файл компилируется XPIDL без предупреждений, все равно проверьте, соответствуют ли заглушки C++ тому, что вы хотели сказать в IDL файле. После создания C++ заглушек можно приступать к кодированию.

В качестве примера будем создавать вымышленный компонент, который мог бы служить интерфейсом к глобальной системе позиционирования для XPCOM приложения. Приемник GPS мы можем подключить к компьютеру для получения следующей информации: текущее время, наше положение на поверхности земли, иногда высоту.

Для придания большой значимости компоненту, предположим, что у нас есть кросс-платформенный инструмент способный масштабировать и вращать двумерную карту на экране. Карта представлена в SVG формате, а программная логика и экраны отображения реализованы с использованием XUL. Наша задача заставить инструмент двигать карту согласно получаемой в реальном времени информации о положении с GPS приемника. Усложним задачу — необходимо поддерживать три различных типа GPS приемников, три различных протокола (в приложении приведены реальные примеры таких протоколов).

XUL — способ описания пользовательского интерфейса  на основе XML. Вместо написания специфичного GUI кода конкретной операционной системы создающего панели, кнопки, метки, и пр. создается один или несколько текстовых файлов заполненных XML тегами указывающих размещения различных GUI элементов. Если вы планируете писать приложения базирующиеся на движке Mozilla GUI, то вам придется использовать XUL как часть своего дизайна.

Компонент должен предоставлять общий интерфейс, и содержать различные реализации для каждого и множества GPS приемников. Это позволит приложению работать с общим интерфейсом не заботясь о деталях реализации конкретного устройства.

Рекомендуется создать директорию для проекта рядом с  директорией компонента nsSample (mozilla/xpcom/sample, см. часть 3). Для нашего примера была создана директория /mozilla/xpcom/gps. Все три реализации компонента будут в одном модуле, поэтому только одна директория потребуется. В листинге 1 приведен интерфейс GPS приемника.

Листинг 1. Интерфейс GPS приемника (имя файла nsIGPS.idl).

#include "nsISupports.idl"

[scriptable, uuid(1BDC2EE0-E92A-11d4-BCC0-0060089296CB)]
interface mozIGPS : nsISupports
{
    boolean Open(in string strDevice);
    boolean Close();
    string Reason(in boolean bClear);
    readonly attribute double latitude;
    readonly attribute double longitude;
    readonly attribute double elevation;
    readonly attribute double gpstime;
};

Идентификатор интерфейса IGPS помещен непосредственно в файл IDL. XPIDL позволяет вставлять текст напрямую в генерируемый C++ код через использование специального тэга ‘%’, можно размещать идентификатор класса и контракта непосредственно в IDL.

Компилятор XPIDL преобразует IDL из Листинга 1 в XPT файл и заголовочный файл C++, а так же заглушку С++ класса (закомментированную с помощью #if 0 … #endif). На данном этапе необходимо сделать следующее:

  • Скопировать заглушки в соответствующие .cpp файл реализаций.
  • Создать заголовочный файл C++ с пустой декларацией класса унаследованного от интерфейса определенного в сгенерированном заголовочном файле C++.
  • Не забудьте добавить #include сгенерированного заголовочного файла. Реализации же должны подключать созданный (не сгенерированный) заголовочный файл.

Не редактируйте сгенерированный заголовочный файл. В случае изменения IDL файла заголовочный файл будет стерт. Поэтому мы создали свой заголовочный файл, который можем безопасно редактировать. На данном этапе мы имеем только сгенерированный компилятором код. Теперь необходимо заполнить заглушки реальным кодом. В листинге 2 приведен сгенерированный код.

Листинг 2. Сгенерированный код

/*
 * DO NOT EDIT.  THIS FILE IS GENERATED FROM nsIGPS.idl
 */

#ifndef __gen_nsIGPS_h__
#define __gen_nsIGPS_h__

#ifndef __gen_nsISupports_h__
#include "nsISupports.h"
#endif

/* For IDL files that don't want to include root IDL files. */
#ifndef NS_NO_VTABLE
#define NS_NO_VTABLE
#endif

/* starting interface:    mozIGPS */
#define MOZIGPS_IID_STR "1bdc2ee0-e92a-11d4-bcc0-0060089296cb"

#define MOZIGPS_IID 
  {0x1bdc2ee0, 0xe92a, 0x11d4, 
    { 0xbc, 0xc0, 0x00, 0x60, 0x08, 0x92, 0x96, 0xcb }}

class NS_NO_VTABLE mozIGPS : public nsISupports {
 public:

  NS_DEFINE_STATIC_IID_ACCESSOR(MOZIGPS_IID)

  /* boolean Open (in string strDevice); */
  NS_IMETHOD Open(const char *strDevice, PRBool *_retval) = 0;

  /* boolean Close (); */
  NS_IMETHOD Close(PRBool *_retval) = 0;

  /* readonly attribute double latitude; */
  NS_IMETHOD GetLatitude(double *aLatitude) = 0;

  /* readonly attribute double longitude; */
  NS_IMETHOD GetLongitude(double *aLongitude) = 0;

  /* readonly attribute double elevation; */
  NS_IMETHOD GetElevation(double *aElevation) = 0;

  /* readonly attribute double gpstime; */
  NS_IMETHOD GetGpstime(double *aGpstime) = 0;

  /* string Reason (in boolean bClear); */
  NS_IMETHOD Reason(PRBool bClear, char **_retval) = 0;

};

/* Use this macro when declaring classes that implement this interface. */
#define NS_DECL_MOZIGPS 
  NS_IMETHOD Open(const char *strDevice, PRBool *_retval); 
  NS_IMETHOD Close(PRBool *_retval); 
  NS_IMETHOD GetLatitude(double *aLatitude); 
  NS_IMETHOD GetLongitude(double *aLongitude); 
  NS_IMETHOD GetElevation(double *aElevation); 
  NS_IMETHOD GetGpstime(double *aGpstime); 
  NS_IMETHOD Reason(PRBool bClear, char **_retval);

/* Use this macro to declare functions that forward the behavior of this interface to another object. */
#define NS_FORWARD_MOZIGPS(_to) 
  NS_IMETHOD Open(const char *strDevice, PRBool *_retval) { return _to ## Open(strDevice, _retval); } 
  NS_IMETHOD Close(PRBool *_retval) { return _to ## Close(_retval); } 
  NS_IMETHOD GetLatitude(double *aLatitude) { return _to ## GetLatitude(aLatitude); } 
  NS_IMETHOD GetLongitude(double *aLongitude) { return _to ## GetLongitude(aLongitude); } 
  NS_IMETHOD GetElevation(double *aElevation) { return _to ## GetElevation(aElevation); } 
  NS_IMETHOD GetGpstime(double *aGpstime) { return _to ## GetGpstime(aGpstime); } 
  NS_IMETHOD Reason(PRBool bClear, char **_retval) { return _to ## Reason(bClear, _retval); }

#if 0
/* Use the code below as a template for the implementation class for this interface. */

/* Header file */
class _MYCLASS_ : public mozIGPS
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_MOZIGPS

  _MYCLASS_();
  virtual ~_MYCLASS_();
  /* additional members */
};

/* Implementation file */
NS_IMPL_ISUPPORTS1(_MYCLASS_, mozIGPS)

_MYCLASS_::_MYCLASS_()
{
  NS_INIT_ISUPPORTS();
  /* member initializers and constructor code */
}

_MYCLASS_::~_MYCLASS_()
{
  /* destructor code */
}

/* boolean Open (in string strDevice); */
NS_IMETHODIMP _MYCLASS_::Open(const char *strDevice, PRBool *_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* boolean Close (); */
NS_IMETHODIMP _MYCLASS_::Close(PRBool *_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute double latitude; */
NS_IMETHODIMP _MYCLASS_::GetLatitude(double *aLatitude)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute double longitude; */
NS_IMETHODIMP _MYCLASS_::GetLongitude(double *aLongitude)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute double elevation; */
NS_IMETHODIMP _MYCLASS_::GetElevation(double *aElevation)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute double gpstime; */
NS_IMETHODIMP _MYCLASS_::GetGpstime(double *aGpstime)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* string Reason (in boolean bClear); */
NS_IMETHODIMP _MYCLASS_::Reason(PRBool bClear, char **_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* End of implementation class template. */
#endif

#endif /* __gen_nsIGPS_h__ */

Посмотрите на этот файл, макрос NS_IMPL_ISUPPORTS реализует QueryInterface, AddRed, и Release. Компилятор IDL так же создал определения для методов Open и Close, а так же для атрибутов Lattitude и Longitude появились методы доступа GetLattitude и GetLongitude. Заглушки возвращают NS_ERROR_NOT_IMPLEMENTED.

Скопируя строчки кода между #if 0 и #endif в отдельный файл получим заготовку реализации. Если компонент не предполагает предоставление внутренней реализации связанным компонентам, то декларацию класса можно оставить в файле реализации. Другими словами, реализация включает сгенерированный заголовочный файл и описывает C++ класс унаследованный от одного из заголовочного файла. Если другим C++ классам требуется знать о внутренней реализации вашего класса, то необходимо создать отдельный заголовочный файл для этого класса. В любом случае, потребуется определить несколько #define символов в реализации для идентификатора контракта, идентификатора класса и имени класса (если необходимо).

Будет создано три различных реализации одного и того же интерфейса, каждая из них нуждается в собственном идентификаторе класса и контракта, как в листинге 3.

Листинг 3. Пример описания идентификаторов интерфейса.

#define MOZ_GPS_NMEA_CONTRACTID "@ficticious.org/GPS/NMEA;1"
#define MOZ_GPS_NMEA_CLASSNAME "GPS Support for NMEA"
#define MOZ_GPS_NMEA_CID  {0x1bdc2ee1,0xe92a,0x11d4,{0xbc,0xc0,0x0,0x60,0x8,0x92,0x96,0xcb}}

#define MOZ_GPS_TAIP_CONTRACTID "@ficticious.org/GPS/TAIP;1"
#define MOZ_GPS_TAIP_CLASSNAME "GPS Support for TAIP"
#define MOZ_GPS_TAIP_CID  {0x1bdc2ee2,0xe92a,0x11d4,{0xbc,0xc0,0x0,0x60,0x8,0x92,0x96,0xcb}}

#define MOZ_GPS_TSIP_CONTRACTID "@ficticious.org/GPS/TSIP;1"
#define MOZ_GPS_TSIP_CLASSNAME "GPS Support for TSIP"
#define MOZ_GPS_TSIP_CID  {0x1bdc2ee3,0xe92a,0x11d4,{0xbc,0xc0,0x0,0x60,0x8,0x92,0x96,0xcb}}

В листинге 4 приведен заголовочный содержащий описание классов реализаций.

Листинг 4. Заголовочный файл реализации.

#ifndef _MOZGPS_H
#define _MOZGPS_H

#include "nsIGPS.h"

#define MOZ_GPS_NMEA_CONTRACTID "@ficticious.org/GPS/NMEA;1"
#define MOZ_GPS_NMEA_CLASSNAME "GPS Support for NMEA"
#define MOZ_GPS_NMEA_CID  {0x1bdc2ee1,0xe92a,0x11d4,{0xbc,0xc0,0x0,0x60,0x8,0x92,0x96,0xcb}}

#define MOZ_GPS_TAIP_CONTRACTID "@ficticious.org/GPS/TAIP;1"
#define MOZ_GPS_TAIP_CLASSNAME "GPS Support for TAIP"
#define MOZ_GPS_TAIP_CID  {0x1bdc2ee2,0xe92a,0x11d4,{0xbc,0xc0,0x0,0x60,0x8,0x92,0x96,0xcb}}

#define MOZ_GPS_TSIP_CONTRACTID "@ficticious.org/GPS/TSIP;1"
#define MOZ_GPS_TSIP_CLASSNAME "GPS Support for TSIP"
#define MOZ_GPS_TSIP_CID  {0x1bdc2ee3,0xe92a,0x11d4,{0xbc,0xc0,0x0,0x60,0x8,0x92,0x96,0xcb}}

/* NMEA */
class mozGPSNMEAImpl : public mozIGPS
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_MOZIGPS

  mozGPSNMEAImpl();
  virtual ~mozGPSNMEAImpl();
  /* additional members */
};

/* TSIP */
class mozGPSTSIPImpl : public mozIGPS
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_MOZIGPS

  mozGPSTSIPImpl();
  virtual ~mozGPSTSIPImpl();
  /* additional members */
};

/* TAIP */
class mozGPSTAIPImpl : public mozIGPS
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_MOZIGPS

  mozGPSTAIPImpl();
  virtual ~mozGPSTAIPImpl();
  /* additional members */
};

#endif

Каждый реализующий класс определяет конструктор, виртуальный деструктор и пару макросов. Макросы NS_DECL_ISUPPORTS и NS_DECL_MOZIGPS определяют что каждый класс будет реализовать интерфейс nsISupports и mozIGPS.

На данный момент ни одной конкретной реализации не написано, каждый реализующий класс использует сгенерированную заглушку реализации, как показано в листинге 5.

Листинг 5. Использование заглушки реализации.

#include "mozGPS.h"
/* NMEA Implementation */
NS_IMPL_ISUPPORTS1(mozGPSNMEAImpl, mozIGPS)
mozGPSNMEAImpl::mozGPSNMEAImpl()
{
  NS_INIT_ISUPPORTS();
  /* member initializers and constructor code */
}
mozGPSNMEAImpl::~mozGPSNMEAImpl()
{
  /* destructor code */
}
/* boolean Open (in string strDevice); */
NS_IMETHODIMP mozGPSNMEAImpl::Open(const char *strDevice, PRBool *_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* boolean Close (); */
NS_IMETHODIMP mozGPSNMEAImpl::Close(PRBool *_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double latitude; */
NS_IMETHODIMP mozGPSNMEAImpl::GetLatitude(double *aLatitude)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double longitude; */
NS_IMETHODIMP mozGPSNMEAImpl::GetLongitude(double *aLongitude)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double elevation; */
NS_IMETHODIMP mozGPSNMEAImpl::GetElevation(double *aElevation)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double gpstime; */
NS_IMETHODIMP mozGPSNMEAImpl::GetGpstime(double *aGpstime)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* string Reason (in boolean bClear); */
NS_IMETHODIMP mozGPSNMEAImpl::Reason(PRBool bClear, char **_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

Таким образом, мы имеем XPCOM модуль реализующий все три компонента. Осталось внести действительную реализацию заменив NS_ERROR_NOT_IMPLEMENTED реальным кодом.

Реализация

Большинство XPCOM компонентов имеют атрибуты, которые можно назвать «прозрачными». Такие атрибуты имеют get и set методы. Для «прозрачного» атрибута подразумевается, что вызов метола get после метода set всегда возвращает одно и то же значение. Такое поведение не меняется вплоть до уничтожения компонента.

Предположим мы объявили атрибут «attribute string name;» в IDL файле. Код, реализующий данный атрибут как «прозрачный», мог бы выглядеть как в листинге 6.

Листинг 6. Прозрачный атрибут.

NS_IMETHODIMP mozSomeImpl::GetName(char** aName)
{
    if (!aName)
       return NS_ERROR_NULL_POINTER;

    *aName = nsnull;
    if (mName)
    {
       *aName = PL_strdup(mName);
       if (!*aName)
          return NS_ERROR_OUT_OF_MEMORY;
    }

    return NS_OK;
}

NS_IMETHODIMP mozSomeImpl::SetName(const char* aName)
{
    if (!aName)
       return NS_ERROR_NULL_POINTER;
    if (mName)
       PL_strfree(mName);
    mName = PL_strdup(aName);
    if (!mName)
       return NS_ERROR_OUT_OF_MEMORY;
    return NS_OK;
}

В листинге 6 предполагается, что класс mozSomeImpl имеет внутренний член типа char* названный mName. Предполагается, что mName инициализируется значением NULL в конструкторе. Рекомендуется инициализировать внутренние переменные каким-либо известным значением. Пример использует строковый тип, который требует выделения некоторого количества памяти для создания строки. Предполагается, что NULL указатель сигнализирует об ошибке. Однако, в некоторых ситуация желательно что бы NULL указатель был эквивалентом пустой строки. Исходный код для реализации свойств простых типов (bool, int и пр.) будет проще.

Некоторые ситуации требуют обращения к атрибутам, чей тип является другим XPCOM компонентом. Член класса предназначенный для хранения такого атрибута рекомендуется объявлять умным указателем, вместо простого указателя на интерфейс. Кода XPCOM компонент будет разрушен, вызовется деструктор умного указателя, и ссылка на другой компонент будет освобождена.

Если говорить о деструкторах, класс сгенерированный  компилятором XPIDL определяет виртуальный деструктор. Убедитесь, что ваш XPCOM компонент так же обладает виртуальным деструктором.

Регистрация модуля

XPCOM компонент необходимо преобразовать в библиотечный файл, который менеджер компонентов называет модулем. Такой модуль может содержать, как только один XPCOM компонент, так и множество компонентов. Когда менеджер компонентов изучает новый модуль, он запрашивает у модуля какие XPCOM компоненты модуль предоставляет. Имя библиотечного файла вместе  со всеми идентификаторами контрактов и классов помещается в небольшой реестр компонентов для быстрого поиска. XPCOM требует соблюдения простого API для каждого модуля, что бы менеджер компонентов мог выполнять свою работу.

Предоставлять такое API очень просто, оно включает — две статических C функции, один статический массив, и пару макросов. В листинге 7 приведен исходный код модуля.

Листинг 7. Исходный код модуля.

#include "nsIGenericFactory.h"
#include "mozGPS.h"

// macro expansion defines our factory constructor method
// used by the components[] array below.
NS_GENERIC_FACTORY_CONSTRUCTOR(mozGPSImpl)

// pair of static register/unregister functions.
static NS_METHOD mozGPSRegistrationProc(nsIComponentManager *aCompMgr,
    nsIFile *aPath, const char *registryLocation, const char *componentType)
{
    // Registration specific activity goes here
    return NS_OK;
}

static NS_METHOD mozGPSUnregistrationProc(
    nsIComponentManager *aCompMgr, nsIFile *aPath,
    const char *registryLocation)
{
    // Un-registration specific activity goes here
    return NS_OK;
}

// Here's the structure array I was talking about.
static nsModuleComponentInfo components[] =
{
    {
       "GPS Component", NS_GPS_CID,
       NS_GPS_CONTRACTID,
       mozGPSImplConstructor,
       mozGPSRegistrationProc, /* NULL if not needed. */
       mozGPSUnregistrationProc /* NULL if not needed. */
    }
};
// One last macro expansion to export NSGetModule() so
// that the component manager can find us.
NS_IMPL_NSGETMODULE("mozGPSModule", components)

Код из листинга 7 использует преимущества nsIGenericFactory, что бы описать модуль для менеджера компонентов. В данном примере, mozGPSImpl имя C++ класса который реализует nsIGPS интерфейс. Это класс вместе с идентификатором класса и контракта описан в mozGPS.h. Массив components[] состоит из одного элемента. Пример модуля предоставляющий несколько компонентов приведен в листинге 8. Достаточно очевидно как добавить третий и четвертый компонент.

Листинг 8. Модуль с несколькими компонентами

#include "nsIGenericFactory.h"
#include "mozFirst.h"
#include "mozSecond.h"

NS_GENERIC_FACTORY_CONSTRUCTOR(mozFirstImpl)
NS_GENERIC_FACTORY_CONSTRUCTOR(mozSecondImpl)

// NOTE: mozFirst[Un]RegistrationProc &
mozSecond[Un]RegistrationProc are completely optional.
static nsModuleComponentInfo components[] =
{
    {
       "First Component",
       NS_FIRST_CID, NS_FIRST_CONTRACTID,
       mozFirstImplConstructor,
       mozFirstRegistrationProc, /* NULL if not needed. */
       mozFirstUnregistrationProc /* NULL if not needed. */
    },
    {
       "Second Component",
       NS_SECOND_CID, NS_SECOND_CONTRACTID,
       mozSecondImplConstructor,
       mozSecondRegistrationProc, /* NULL if not needed. */
       mozSecondUnregistrationProc /* NULL if not needed. */
    }
};
NS_IMPL_NSGETMODULE("mozSomeModule", components)

Make файл

Большинство программистов знакомо с использованием утилиты make для управления компиляцией и связыванием приложений. Большинство IDE представляют собой комбинацию текстового редактора и редактора make файлов с привлекательным внешним видом. Большинство программистов ограничиваются написанием простых make файлов, которые могут быть использованы любой утилитой make для обеспечения кросс-платформенности. В листинге 9 приведен make файл данного проекта, который осуществляют компиляцию IDL и C++ для получения бинарной библиотеки и XPT файла.

Примечание: приведен оригинальный файл из статьи, его корректность не проверялась. Компиляции и сборке компонентов будет просвещенна отдельная статья.

Листинг 9. Make файл проекта

DEPTH=....
MODULE=GPS
MAKE_OBJ_TYPE=DLL
DLLNAME=$(MODULE)
DLL=.$(OBJDIR)$(DLLNAME).dll
XPIDLSRCS=.nsIGPS.idl 
           $(NULL)
CPP_OBJS=
.$(OBJDIR)mozGPS.obj 
.$(OBJDIR)mozGPSModule.obj 
$(NULL)

LLIBS=
$(DIST)libxpcom.lib 
$(LIBPLC) 
$(NULL)
LINCS=
-I$(PUBLIC)GPS 
-I$(PUBLIC)raptor 
-I$(PUBLIC)xpcom 
$(NULL)

include <$(DEPTH)configrules.mak>
install:: $(DLL)
    $(MAKE_INSTALL) $(DLL) $(DIST)bincomponents

На Windows системах используйте nmake, на большинстве других операционных систем — make.

На данном этапе все должно компилироваться. Лучше всего на этом этапе проверить, что все работает, что бы избежать непредвиденных сюрпризов в сгенерированном коде. Такой подход позволит избежать больших затрат при обнаружении не верно спроектированного интерфейса в будущем.

Установка

Установка XPCOM компонента очень проста. Достаточно скопировать скомпилированную библиотеку и XPT файл в директорию компонентов и запустить утилиту regxpcom. Если Вы используете сборку Mozilla для разработки и тестирования (debug build), можно пропустить этот шаг. Mozilla вызывает метод AutoRegister менеджера компонентов для поиска и регистрации новых компонентов. В некоторых случаях механизм регистрации может быть запутанным, особенно когда необходимо заменить существующий компонент. Простейший способ принудить менеджер компонентов перестроить реестр компонентов — удалить два файла, которые он создает в директории компонентов.

Тестирование с применением XPCShell

XPT файл содержит информацию необходимую для JavaScript. Нет необходимости загружать Mozilla GUI, что бы протестировать компонент, если только компонент не связан с chrome. Необходимо написать код JavaScript для загрузки компонента и тестирование его методов и свойств. XPCShell небольшая утилита командной строки, которая предоставляет консоль JavaScript. Для запуска тестового скрипта достаточно написать:

load('test.js');

Использовать JavaScript для тестирования очень удобно. Вы можете тестировать компоненты очень быстро, гораздо быстрее, чем написать и скомпилировать отдельные программы на C или С++ выполняющие туже работу. В листинге 10  приведен тестовый скрипт, который может быть скопирован в файл (например, test.js)

Листинг 10. Тестовый скрипт

const kClass = "@fictitious.org/GPS;1";
var obj = components.classes[kClass].createInstance();
obj = obj.QueryInterface(Components.interfaces.nsIGPS);
dump("obj = " + obj + "n");

Если запустить этот код до написания реального кода реализации компонента, то произойдет ошибка, поскольку каждое свойство и метод являются заглушками возвращающими ошибку «not impleneted».

Разработка

Перечислим некоторые особенности и ограничения XPCOM как фреймворка. Во-первых, XPCOM компоненты могут быть не реентерабельными. Во-вторых, большинство клиентских приложений не используют многопоточность. В-третьих, поток, создающий компонент, обычно его и разрушает. Эти требования не являются обязательными, но позволяют избежать некоторых проблем.

Существует возможность создать XPCOM компонент, который будет привязан к потоку, возможно совместно с другими компонентами. Любой другой поток желающий получить доступ к этому компоненту обязан делать это через прокси механизм. В таком случае, вызов методов или сеттеров свойств (когда не возвращается какое-либо значение) может быть выполнен асинхронно — отправкой сообщений потоку-владельцу компонента (который обрабатывает прокси код). Однако, когда необходимо вернуть значение, — вызывающий поток может блокироваться до тех пор, пока поток-владелец не выполнит запрашиваемые операции и не вернет результат.

Если Вы программируете под Windows и любите вызывать MessageBox в коде, постарайтесь избегать этого, если нет уверенности что компонент будет использован в среде имеющей очередь сообщений. XPCShell не имеет очереди сообщений для обработки сообщений Windows. Как следствие любой диалог будет показываться и зависать. Вместо этого лучше использовать fprintf() в stdout или stderr. В общем случае избегайте вызовов к Win API, если вы не разрабатывает платформо-зависимый компонент. Netscape Portable Runtime (NSPR) — С библиотека обрабатывающая большинство базовых запросов к ресурсам, таки как выделение памяти, управление файлами, синхронизация. XPCOM реализован с использованием NSPR, благодаря этому XPCOM хорошо портируем.

XPConnect в сочетании с XPIDL делает интерфейсы видимыми в JavaScript. Однако, существует несколько типов данных, которые не так легко поддерживать, некоторый JavaScript синтаксис требует прямых вызовов движка JavaScript. Например, свойство которое выступает в роли контейнера. Если контейнер содержит только простые типы или другой объект, можно объявить интерфейс «только для чтения» (read-only) который принимает некоторый тип данных в качестве индекса. Тем не менее, если необходимо иметь доступ более чем к одному типу данных во входном или выходном параметре, придется использовать JavaScript магию.

Движок JavaScript поддерживает вариативный тип данных называемый jsval, но его нельзя описывать в XPIDL (ситуация могла измениться в новых версиях XPCOM). Возможность использовать jsval в XPIDL была бы большим плюсом, многие методы могут принимать и возвращать вариативные значения. Хороший пример — обзор базы данных, когда типы данных не известны до тех пор, пока не будет выполнено подсоединение к базе.

Возвращаясь к примеру с контейнером. Если необходимо использовать операторы массива ([ и ]) для задания индекса, то придется изучить,  как работает движок JavaScript. Короче говоря, проще избегать таких ситуаций.

Для тех, кто увлекается программированием на VBScript и JScript  (возможно в ASP или VBA окружении) будет приятно узнать, что XPConnect делает метод QueryInterface() видимым. Это позволяет переключаться между интерфейсами одного компонента. Хотя, в JScript и VBScript есть способы обойти это ограничение, в обычных условиях вы вынуждены работать с интерфейсом, объявленным по умолчанию для скриптового компонента.

Кроме того, сейчас создается «плоская» модель интерфейса для скриптовых компонентов. Она позволит JScript-программистам при вызове какого-либо метода не вызывать каждый раз QueryInterface() в поисках интерфейса, которому этот метод принадлежит. Такой подход в будущем может сделать QueryInterface() вообще ненужным.

В некоторый момент может потребоваться изменить интерфейс компонента, добавить метод или свойство, изменить тип данных. Небольшое изменение не сможет разрушить всю вашу работу. Достаточно исправить IDL файл, скомпилировать его XPIDL компилятором, сгенерировать новый заголовочный файл; вырезать и вставить те части, которые изменились в файл реализации и наконец, скомпилировать проект, что бы убедиться что все в порядке.

Потоки и прокси

При разработке компонента необходимо разработать стратегию реентерабельности. Пусть некоторые многопоточные приложения намерены взаимодействовать с компонентом. Есть два варианта: разработать полностью реентерабельный компонент, или не реентерабельный компонент. Многопоточное приложение или компонент должны выполнять дополнительную работу, что бы взаимодействовать с однопоточным компонентом.

Если вы игнорирует потоковую модель компонента, то вскоре у вас начнутся многочисленный проблемы с выделением и высвобождением памяти в разных потоках.

Реализация полностью реентерабельного компонента достигается за счет использования семафоров и мьютексов библиотеки NSPR (см. Приложение). API предоставляемое NSPR хорошо документировано. Рекомендуется посмотреть в сторону потоко-безопасных версий макросов nsISupports.

Большинство компонентов написаны с расчетом на то, что только один поток будет их использовать. Клиентский код в данном случае должен работать с не реентерабельным компонентом. XPCOM включает прокси менеджер способный создать прокси интерфейс по описанию типа, который будет взаимодействовать с компонентом в потоко-безопасном режиме.

Прокси менеджер предоставляет два метода: GetProxy для создания проксируемого компонента (он создает компонент и прокси) и GetProxyForObject для создания прокси к уже существующему компоненту. Оба метода принимают указатель на очередь событий, которую можно получить через сервис.

Механизм прокси работает только для компонентов, которые были созданы с использованием xpidl компилятора. Они должны иметь информацию о типе (type library), что бы прокси менеджер мог создавать интерфейсы на лету. Прокси менеджер создает прокси только для работы внутри одного приложения, но не разных приложений или сети.

Заключение

На этом закончим нашу серию из пяти статей. Теперь вы должны понимать базовые механизмы и способы написания компонентов. Вот несколько статей рекомендованных к дальнейшему прочтению:

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s