mozilla

Revision 276181 of Un vistazo de XPCOM

  • Enlace amigable (slug) de la revisión: Creación_de_Componentes_XPCOM/Un_vistazo_de_XPCOM
  • Título de la revisión: Un vistazo de XPCOM
  • Id de la revisión: 276181
  • Creada:
  • Creador: Maharba
  • ¿Es la revisión actual? No
  • Comentario /* La Interfaz Base <code>nsISupports</code> */

Contenido de la revisión

Este es un libro acerca de XPCOM. Esta escrito en forma de un tutorial acerca de la creación de componentes XPCOM, pero cubre la mayoría de los aspectos, conceptos y terminología del modelo de componentes XPCOM en el camino.

Este capítulo empieza con un tour rápido de XPCOM - una introducción a los conceptos básicos y tecnologías en XPCOM y el desarrollo de componentes. Las secciones principales en este capítulo introducen los conceptos a un nivel muy superficial, así que podremos discutirlos y usarlos con más familiaridad en el tutorial que describe la creación del componente Mozilla llamado Weblock.

La Solución XPCOM

El Modelo Componente Objeto Multiplataforma (XPCOM) es una plataforma que permite a los desarrolladores romper proyectos de software monolíticos en piezas modulares más pequeñas. Estas piezas, conocidas como componentes son ensamblados juntos nuevamente en tiempo de ejecución.

El objetivo de XPCOM es permitir a diferentes piezas de software ser desarrolladas y construidas independientes unas de otras. Para permitir interoperabilidad entre componentes dentro de una aplicación, XPCOM separa la implementación de un componente de la interfaz, lo cual discutimos en {{template.Anch("Interfases")}}. Pero XPCOM también provee muchas herramientas y bibliotecas que habilitan la carga y manipulación de estos componentes, servicios que ayudan al desarrollador a escribir código modular multiplataforma, y soporte para versiones, así que los componentes pueden ser reemplazados o actualizados sin tener que romper o volver a crear la aplicación. Usando XPCOM, los desarrolladores crean componentes que pueden ser reutilizados en diferentes aplicaciones o pueden ser reemplazados para cambiar la funcionalidad de aplicaciones existentes.

XPCOM no solamente soporta el desarrollo de componetes de software, también provee gran parte de la funcionalidad de una plataforma de desarrollo, como:

  • gestión de componentes
  • abstracción de archivos
  • paso de mensajes objeto
  • manejo de memoria

Discutiremos los puntos de arriba a detalle en los siguientes capítulos, pero por ahora, puede ser útil pensar en XPCOM como una plataforma para desarrollo de componentes, en la que la que se incluyen caracterízticas como las listadas arriba.

Gecko

Aunque en algunos aspectos es similar a Micrisoft COM, XPCOM está diseñado para ser usado primordialmente a nivel de aplicación. El uso más importante de XPCOM es dentro de Gecko, un buscador web embebido de código abierto, que cumple con estándares y un conjunto de herramientas para crear buscadores web y otras aplicaciones.

XPCOM se encarga de accesar la funcionalidad de las bibliotecas de Gecko y embeber o extender Gecko. Este libro se enfoca en lo último - extender Gecko - pero las ideas fundamentales en el libro también serán importantes para los desarrolladores que embeban Gecko.

Gecko es usado en muchas aplicaciones de internet, la mayoría buscadores. La lista incluye dispositivos como el Gateway/AOL, el Instant AOL y la Nokia Media Terminal. Gecko también se usa en el último cliente Compuserve, AOL para Mac OS X, Netscape 7 y por supuesto el cliente de Mozilla. En este momento, Gecko es el web browser de código abierto predominante.

Componentes

XPCOM te permite construir un sistema en el que grandes proyectos de software pueden ser fragmentados en piezas más pequeñas. Estas piezas, conocidas como componentes, son normalmente diseñadas en pequeñas y reutilizables bibliotecas binarias(una DLL en Windows, por ejemplo, o una DSO en Unix), que pueden incluir uno o más componentes. Cuando hay dos o más componentes relacionados juntos en una biblioteca binaria, llamamos a la biblioteca módulo.

Fragmentar el software en distintos componentes puede ayudar a hacerlo menos difícil de desarrollar y mantener. Más allá de esto, la programación modular basada en componentes tiene ciertas ventajas bien conocidas:

Beneficio Descripción
Reutlizable El código modular puede ser reutilizado en otras aplicaciones y en otros contextos.
Actualizaciones Puedes actualizar componentes sin tener que recompilar toda la aplicación.
Rendimiento Cuando el código es modular, los módulos que no serán usados en seguida pueden ser "cargados durmiendo", o no ser cargados del todo, lo que puede mejorar el rendimiento de tu aplicación.
Mantenimiento Aún cuando no estés actualizando un componente, diseñar tu aplicación de forma modular puede hacerte más fácil encontrar e mantener las partes de la aplicación en que estás interesado.

Mozilla tiene más de cuatro millones de líneas de código, y ningún individuo por sí solo entiende el código fuente entero. La mejor forma de afrontar un proyecto de este tamaño es dividirlo en piezas más pequeñas y manejables, usar un modelo de programación basado en componentes y organizar ciertos grupos de componentes en módulos. La biblioteca de red, por ejemplo, consiste en componentes para cada uno de los protocolos, HTTP, FTP y otros, los cuales son armados juntos y enlazados en una sola biblioteca. Esta biblioteca es el módulo de trabajo en red, conocida también como "necko".

El componente HTTP en Gecko no expone las clases privadas que usa como componentes separados. El "stuff"

The HTTP component in Gecko doesn't expose private classes it uses as separate components. The "stuff" that's internal to the component stays internal, and isn't exposed to XPCOM. In the haste of early Mozilla development, components were created where they were inappropriate, but there's been an ongoing effort to remove XPCOM from places like this.

Pero no siempre es buena idea dividir las cosas. Hay algunas cosas en el mundo que sólo trabajan si están juntas y otras que deberían estar separadas. Por ejemplo, el hijo de un autor no se comerá un sandwich de crema de cacahuate si no tiene jamón, porque en este mundo, la crema de cachuate y el jamón forman una unión inseparable (guácala, en México como en muchos lugares no opinamos lo mismo creo que fue un mal ejemplo, pero en fin esto es parte de la traducción y espero se entienda la idea). Con el software es similar. En áreas de código que están estrechamente acopladas en clases que son usadas sólo internamente, por ejemplo, el duro trabajo de dividir las cosas tal vez no sea un esfuerzo vano.

El componente HTTP en Gecko no expone las clases privadas que usa como componentes separados. El "material" que es interno del componente permanece interno y no es visible para XPCOM. Por la prisa al inicio del desarrollo de Mozilla, fueron creados componentes donde era inadecuado, pero se ha estado haciendo un grán esfuerzo para quitar XPCOM de estos lugares.

Interfases

Generalmente es buena idea dividir el software en componentes, pero ¿Cómo hacer esto exactamente? La idea básica es identificar piezas de funcionalidad que esten relacionadas entre sí y entender cómo se comunican entre ellas. Cuando son definidos los canales de comunicación entre los distintos delimitadores de forma que se encuentran entre componentes y dichos delimitadores son formalizados se llaman interfaces.

Las interfaces no son una idea nueva en programación. Todos hemos usado interfaces desde nuestro primer programa "Hola Mundo", donde la interface estaba entre el código que escribimos-el código de la aplicación-y el código de impresión. El código de aplicación usó una interfaz de una biblioteca, stdio para pintar la cadena "Hola Mundo" en la pantalla. La diferencia aquí es que una aplicación "Hola Mundo" en XPCOM encuentra esta pantalla pintando funcionalidad en tiempo de ejecución y nunca tiene que saber acerca de stdio cuando es compilado.

Las interfaces permiten a los desarrolladores encapsular la implementación y la lógica interna de su programa y permitir a los clientes ignorar cómo se hacen las cosas y sólo usar el software.

{{wiki.template('Block-title', [ "Interfaces y Programación por Contrato" ])}}

Una interfaz forma un acuerdo contractual entre componentes y clientes. No hay código que obligue estos acuerdos, pero ignorarlos puede ser fatal. En la programación basada en componentes, un componente garantiza que las interfaces que provee serán inmutables, es decir, proveerán el mismo acceso a los mismos métodos en diferentes versiones del componente, estableciendo un contrato con los clientes que usan el software. A este respecto, la programación basada en interfaces también es llamada programación por contrato.

Interfaces y Encapsulación

Entre delimitadores de componentes, la abstracción es crucial para el mantenimiento y la reutilización del software. Considera, por ejemplo, una clase que no está bien encapsulada; usar un método público de inicialización disponible libremente, como sugiere el ejemplo de abajo puede causar problemas.

{{wiki.template('Block-title', [ "AlgunaClase Class Inicializacion" ])}}

class SomeClass
{
  public:
    // Constructor
    AlgunaClase();

    // Virtual Destructor
    virtual ~AlgunaClase();

    // init method
    void Init();

    void HazAlgoUtil();
};

Para que este sistema funcione correctamente, el programador del cliente debe prestar mucha atención a todas las reglas que el programador del componente estableció. Este es el acuerdo contractual de esta clase clase no encapsulada: un conjunto de reglas que definen cuando cada método puede ser llamado y cuando se espera que se haga. Una regla puede especificar que HazAlgoUtil puede ser llamado sólo después de una llamada a Init(). El método HazAlgoUtil puede hacer algún tipo de validación para asegurar que la condición de que Init() ha sido llamado, ha sido cumplida.

Además de escribir código bien comentado que le diga al desarrollador del cliente las reglas acerca de Init(), el desarrollador puede seguir un par de pasos para hacer este contrato más claro. Primero, la construcción de un objeto puede ser encapsulada y proveer una clase virtual que defina el método HazAlgoUtil. De esta forma, construcción e inicialización pueden ser completamente ocultos de los clientes de la clase. En esta situación "semiencapsulada", la única parte de la clase que se ve esuna bien definida lista de métodos llamables (la interfaz). Una vez que la clase es encapsulada, la única interfaz que verá el cliente es esta:

{{wiki.template('Block-title', [ "Encapsulación de AlgunaInterfaz" ])}}

class AlgunaInterfaz
{
  public:
    virtual void HazAlgoUtil() = 0;
};

La implementación puede entonces derivar de esta clase e implementar el método virtual. Los clientes de este código pueden usar después un patrón de diseño factoría para crear el objeto (ve {{template.Anch("Factorías")}}) y después encapsular la implementación. En XPCOM, los clientes se escudan de la lógica interna de los componentes de esta forma y confiar en la interfaz para proveer acceso a la funcionalidad requerida.


La Interfaz Base nsISupports

Dos aspectos fundamentales en la programación basada en componentes e interfaces son: la Vida del componente, también llamada pertenencia del objeto y las llamadas de interfaz, o poder identificar que interfaces soporta un componente al momento de ejecución

Two fundamental issues in component and interface-based programming are component lifetime, also called object ownership, and interface querying, or being able to identify which interfaces a component supports at runtime. This section introduces the base interface-the mother of all interfaces in XPCOM - nsISupports, which provides solutions to both of these issues for XPCOM developers.

Object Ownership

In XPCOM, since components may implement any number of different interfaces, interfaces must be reference counted. Components must keep track of how many references to it clients are maintaining and delete themselves when that number reaches zero.

When a component gets created, an integer inside the component tracks this reference count. The reference count is incremented automatically when the client instantiates the component; over the course of the component's life, the reference count goes up and down, always staying above zero. At some point, all clients lose interest in the component, the reference count hits zero, and the component deletes itself.

When clients use interfaces responsibly, this can be a very straightforward process. XPCOM has tools to make it even easier, as we describe later. It can raise some real housekeeping problems when, for example, a client uses an interface and forgets to decrement the reference count. When this happens, interfaces may never be released and will leak memory. The system of reference counting is, like many things in XPCOM, a contract between clients and implementations. It works when people agree to it, but when they don't, things can go wrong. It is the responsibility of the function that creates the interface pointer to add the initial reference, or owning reference, to the count.

{{wiki.template('Block-title', [ "Pointers in XPCOM" ])}}

In XPCOM, pointers refer to interface pointers. The difference is a subtle one, since interface pointers and regular pointers are both just address in memory. But an interface pointer is known to implement the nsISupports base interface, and so can be used to call methods such as AddRef, Release, or QueryInterface.

nsISupports, shown below, supplies the basic functionality for dealing with interface discovery and reference counting. The members of this interface, QueryInterface, AddRef, and Release, provide the basic means for getting the right interface from an object, incrementing the reference count, and releasing objects once they are not being used, respectively. The nsISupports interface is shown below:

{{wiki.template('Block-title', [ "The <code>nsISupports</code> Interface" ])}}

class Sample: public nsISupports
{
  private:
    nsrefcnt mRefCnt;
  public:
    Sample();
    virtual ~Sample();

    NS_IMETHOD QueryInterface(const nsIID &aIID, void **aResult);
    NS_IMETHOD_(nsrefcnt) AddRef(void);
    NS_IMETHOD_(nsrefcnt) Release(void);
};

The various types used in the interface are described in the {{template.Anch("XPCOM Types")}} section below. A complete (if spare) implementation of the nsISupports interface is shown below. See A Reference Implementation of QueryInterface for detailed information.

{{wiki.template('Block-title', [ "Implementation of <code>nsISupports</code> Interface" ])}}

// initialize the reference count to 0
Sample::Sample() : mRefCnt(0)
{ 
}
Sample::~Sample()
{
}

// typical, generic implementation of QI
NS_IMETHODIMP Sample::QueryInterface(const nsIID &aIID,
                                  void **aResult)
{
  if (!aResult) {
    return NS_ERROR_NULL_POINTER;
  }
  *aResult = NULL;
  if (aIID.Equals(kISupportsIID)) {
    *aResult = (void *) this;
  }
  if (!*aResult) {
    return NS_ERROR_NO_INTERFACE;
  }
  // add a reference
  AddRef();
  return NS_OK;
}

NS_IMETHODIMP_(nsrefcnt) Sample::AddRef()  
{
  return ++mRefCnt;
}

NS_IMETHODIMP_(nsrefcnt) Sample::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0;
  }
  // optional: return the reference count
  return mRefCnt;
}
Object Interface Discovery

Inheritance is another very important topic in object oriented programming. Inheritance is the means through which one class is derived from another. When a class inherits from another class, the inheriting class may override the default behaviors of the base class without having to copy all of that class's code, in effect creating a more specific class, as in the following example:

{{wiki.template('Block-title', [ "Simple Class Inheritance" ])}}

class Shape
{
  private:
    int m_x;
    int m_y;

  public:
    virtual void Draw() = 0;
    Shape();
    virtual ~Shape();
};
 
class Circle : public Shape
{
  private:
    int m_radius;
  public:
    virtual Draw();
    Circle(int x, int y, int radius);
    virtual ~Circle();
};

Circle is a derived class of Shape. A Circle is a Shape, in other words, but a Shape is not necessarily a Circle. In this case, Shape is the base class and Circle is a subclass of Shape.

In XPCOM, all classes derive from the nsISupports interface, so all objects are nsISupports but they are also other, more specific classes, which you need to be able to find out about at runtime. In {{template.Anch("Simple Class Inheritance")}}, for example, you'd like to be able ask the Shape if it's a Circle and to be able to use it like a circle if it is. In XPCOM, this is what the QueryInterface feature of the nsISupports interface is for: it allows clients to find and access different interfaces based on their needs.

In C++, you can use a fairly advanced feature known as a dynamic_cast<>, which throws an exception if the Shape object is not able to be cast to a Circle. But enabling exceptions and RTTI may not be an option because of performance overhead and compatibility on many platforms, so XPCOM does things differently.

{{wiki.template('Block-title', [ "Exceptions in XPCOM" ])}}

C++ exceptions are not supported directly by XPCOM. Instead all exceptions must be handled within a given component, before crossing interface boundaries. In XPCOM, all interface methods should return an nsresult error value (see the XPCOM API Reference for a listing of these error codes). These error code results become the "exceptions" that XPCOM handles.

Instead of leveraging C++ RTTI, XPCOM uses the special QueryInterface method that casts the object to the right interface if that interface is supported.

Every interface is assigned an identifier that gets generated from a tool commonly named "uuidgen". This universally unique identifier (UUID) is a unique, 128-bit number. Used in the context of an interface (as opposed to a component, which is what the contract ID is for), this number is called an IID.

When a client wants to discover if an object supports a given interface, the client passes the IID assigned to that interface into the QueryInterface method of that object. If the object supports the requested interface, it adds a reference to itself and passes back a pointer to that interface. If the object does not support the interface an error is returned.

class nsISupports { 
  public:
    long QueryInterface(const nsIID & uuid,
                        void **result) = 0;
    long AddRef(void) = 0;
    long Release(void) = 0;
};

The first parameter of QueryInterface is a reference to a class named nsIID, which is a basic encapsulation of the IID. Of the three methods on the nsIID class, Equals, Parse, and ToString, Equals is by far the most important, because it is used to compare two nsIIDs in this interface querying process.

When you implement the {{template.Interface("nsISupports")}} class (and you'll see in the chapter Using XPCOM Utilities to Make Things Easier how macros can make this process much easier), you must make sure the class methods return a valid result when the client calls QueryInterface with the nsISupports IID. QueryInterface should support all interfaces that the component supports.

In implementations of QueryInterface, the IID argument is checked against the nsIID class. If there is a match, the object's this pointer is cast to void, the reference count is incremented, and the interface returned to the caller. If there isn't a match, the class returns an error and sets the out value to null.

In the example above, it's easy enough to use a C-style cast. But casting can become more involved where you must first cast void then to the requested type, because you must return the interface pointer in the vtable corresponding to the requested interface. Casting can become a problem when there is an ambiguous inheritance hierarchy.

Fuente de la revisión

<p>Este es un libro acerca de XPCOM. Esta escrito en forma de un tutorial acerca de la creación de componentes XPCOM, pero cubre la mayoría de los aspectos, conceptos y terminología del modelo de componentes XPCOM en el camino. 
</p><p>Este capítulo empieza con un tour rápido de XPCOM - una introducción a los conceptos básicos y tecnologías en XPCOM y el desarrollo de componentes. Las secciones principales en este capítulo introducen los conceptos a un nivel muy superficial, así que podremos discutirlos y usarlos con más familiaridad en el tutorial que describe la creación del componente Mozilla llamado <b>Weblock</b>.
</p>
<h3 name="La_Soluci.C3.B3n_XPCOM"> La Solución XPCOM </h3>
<p>El Modelo Componente Objeto Multiplataforma (XPCOM) es una plataforma que permite a los desarrolladores romper proyectos de software monolíticos en piezas modulares más pequeñas. Estas piezas, conocidas como <i>componentes</i> son ensamblados juntos nuevamente en tiempo de ejecución.
</p><p>El objetivo de XPCOM es permitir a diferentes piezas de software ser desarrolladas y construidas independientes unas de otras. Para permitir interoperabilidad entre componentes dentro de una aplicación, XPCOM separa la <i>implementación</i> de un componente de la <i>interfaz</i>, lo cual discutimos en {{template.Anch("Interfases")}}. Pero XPCOM también provee muchas herramientas y bibliotecas que habilitan la carga y manipulación de estos componentes, servicios que ayudan al desarrollador a escribir código modular multiplataforma, y soporte para versiones, así que los componentes pueden ser reemplazados o actualizados sin tener que romper o volver a crear la aplicación. Usando XPCOM, los desarrolladores crean componentes que pueden ser reutilizados en diferentes aplicaciones o pueden ser reemplazados para cambiar la funcionalidad de aplicaciones existentes.
</p><p>XPCOM no solamente soporta el desarrollo de componetes de software, también provee gran parte de la funcionalidad de una plataforma de desarrollo, como:
</p>
<ul><li> gestión de componentes
</li><li> abstracción de archivos
</li><li> paso de mensajes objeto
</li><li> manejo de memoria
</li></ul>
<p>Discutiremos los puntos de arriba a detalle en los siguientes capítulos, pero por ahora, puede ser útil pensar en XPCOM como una <i>plataforma para desarrollo de componentes</i>, en la que la que se incluyen caracterízticas como las listadas arriba.
</p>
<h3 name="Gecko"> Gecko </h3>
<p>Aunque en algunos aspectos es similar a Micrisoft COM, XPCOM está diseñado para ser usado primordialmente a nivel de aplicación. El uso más importante de XPCOM es dentro de <i>Gecko</i>, un buscador web embebido de código abierto, que cumple con estándares y un conjunto de herramientas para crear buscadores web y otras aplicaciones. 
</p><p>XPCOM se encarga de accesar la funcionalidad de las bibliotecas de <i>Gecko</i> y embeber o extender Gecko. Este libro se enfoca en lo último - extender Gecko - pero las ideas fundamentales en el libro también serán importantes para los desarrolladores que embeban Gecko.
</p><p>Gecko es usado en muchas aplicaciones de internet, la mayoría buscadores. La lista incluye dispositivos como el Gateway/AOL, el Instant AOL y la Nokia Media Terminal. Gecko también se usa en el último cliente Compuserve, AOL para Mac OS X, Netscape 7 y por supuesto el cliente de Mozilla. En este momento, Gecko es el web browser de código abierto predominante.
</p>
<h3 name="Componentes"> Componentes </h3>
<p>XPCOM te permite construir un sistema en el que grandes proyectos de software pueden ser fragmentados en piezas más pequeñas. Estas piezas, conocidas como componentes, son normalmente diseñadas en pequeñas y reutilizables bibliotecas binarias(una <abbr title="Dynamic Link Library">DLL</abbr> en Windows, por ejemplo, o una <abbr title="Distributed Shared Object">DSO</abbr> en Unix), que pueden incluir uno o más componentes. Cuando hay dos o más componentes relacionados juntos en una biblioteca binaria, llamamos a la biblioteca <i>módulo</i>.
</p><p>Fragmentar el software en distintos componentes puede ayudar a hacerlo menos difícil de desarrollar y mantener. Más allá de esto, la programación modular basada en componentes tiene ciertas ventajas bien conocidas:
</p>
<table class="standard-table">
  <tbody><tr>
    <td class="header">Beneficio</td>
    <td class="header">Descripción</td>
  </tr>
  <tr>
    <td>Reutlizable</td>
    <td>El código modular puede ser reutilizado en otras aplicaciones y en otros contextos.</td>
  </tr>
  <tr>
    <td>Actualizaciones</td>
    <td>Puedes actualizar componentes sin tener que recompilar toda la aplicación.</td>
  </tr>
  <tr>
    <td>Rendimiento</td>
    <td>Cuando el código es modular, los módulos que no serán usados en seguida pueden ser "cargados durmiendo", o no ser cargados del todo, lo que puede mejorar el rendimiento de tu aplicación.</td>
  </tr>
  <tr>
    <td>Mantenimiento</td>
    <td>Aún cuando no estés actualizando un componente, diseñar tu aplicación de forma modular puede hacerte más fácil encontrar e mantener las partes de la aplicación en que estás interesado.</td>
  </tr>
</tbody></table>
<p>Mozilla tiene más de cuatro millones de líneas de código, y ningún individuo por sí solo entiende el código fuente entero. La mejor forma de afrontar un proyecto de este tamaño es dividirlo en piezas más pequeñas y manejables, usar un modelo de programación basado en componentes y organizar ciertos grupos de componentes en módulos. La biblioteca de red, por ejemplo, consiste en componentes para cada uno de los protocolos, HTTP, FTP y otros, los cuales son armados juntos y enlazados en una sola biblioteca. Esta biblioteca es el módulo de trabajo en red, conocida también como "necko".
</p><p>El componente <abbr title="Hypertext Transfer Protocol">HTTP</abbr> en Gecko no expone las clases privadas que usa como componentes separados. El "stuff" 
</p><p>The <abbr title="Hypertext Transfer Protocol">HTTP</abbr> component in Gecko doesn't expose private classes it uses as separate components. The "stuff" that's internal to the component stays internal, and isn't exposed to XPCOM. In the haste of early Mozilla development, components were created where they were inappropriate, but there's been an ongoing effort to remove XPCOM from places like this.
</p><p>Pero no siempre es buena idea dividir las cosas. Hay algunas cosas en el mundo que sólo trabajan si están juntas y otras que deberían estar separadas. Por ejemplo, el hijo de un autor no se comerá un sandwich de crema de cacahuate si no tiene jamón, porque en este mundo, la crema de cachuate y el jamón forman una unión inseparable (guácala, en México como en muchos lugares no opinamos lo mismo creo que fue un mal ejemplo, pero en fin esto es parte de la traducción y espero se entienda la idea). Con el software es similar. En áreas de código que están estrechamente acopladas en clases que son usadas sólo internamente, por ejemplo, el duro trabajo de dividir las cosas tal vez no sea un esfuerzo vano.
</p><p>El componente <abbr title="Hypertext Transfer Protocol">HTTP</abbr> en Gecko no expone las clases privadas que usa como componentes separados. El "material" que es interno del componente permanece interno y no es visible para XPCOM. Por la prisa al inicio del desarrollo de Mozilla, fueron creados componentes donde era inadecuado, pero se ha estado haciendo un grán esfuerzo para quitar XPCOM de estos lugares.
</p>
<h3 name="Interfases"> Interfases </h3>
<p>Generalmente es buena idea dividir el software en componentes, pero ¿Cómo hacer esto exactamente? La idea básica es identificar piezas de funcionalidad que esten relacionadas entre sí y entender cómo se comunican entre ellas. Cuando son definidos los canales de comunicación entre los distintos delimitadores de forma que se encuentran entre componentes y dichos delimitadores son formalizados se llaman <i>interfaces</i>.
</p><p>Las interfaces no son una idea nueva en programación. Todos hemos usado interfaces desde nuestro primer programa "Hola Mundo", donde la interface estaba entre el código que escribimos-el código de la aplicación-y el código de impresión. El código de aplicación usó una interfaz de una biblioteca, <code>stdio</code> para pintar la cadena "Hola Mundo" en la pantalla. La diferencia aquí es que una aplicación "Hola Mundo" en XPCOM encuentra esta pantalla pintando funcionalidad en tiempo de ejecución y nunca tiene que saber acerca de <code>stdio</code> cuando es compilado.
</p><p>Las interfaces permiten a los desarrolladores <i>encapsular</i> la implementación y la lógica interna de su programa y permitir a los clientes ignorar cómo se hacen las cosas y sólo usar el software.  
</p>
<div class="side-note">
<p>{{wiki.template('Block-title', [ "Interfaces y Programación por Contrato" ])}}
</p><p>Una interfaz forma un acuerdo contractual entre componentes y clientes. No hay código que obligue estos acuerdos, pero ignorarlos puede ser fatal. En la programación basada en componentes, un componente garantiza que las interfaces que provee serán <i>inmutables</i>, es decir, proveerán el mismo acceso a los mismos métodos en diferentes versiones del componente, estableciendo un contrato con los clientes que usan el software. A este respecto, la programación basada en interfaces también es llamada <i>programación por contrato</i>.
</p>
</div>
<h4 name="Interfaces_y_Encapsulaci.C3.B3n"> Interfaces y Encapsulación</h4>
<p>Entre delimitadores de componentes, la abstracción es crucial para el mantenimiento y la reutilización del software. Considera, por ejemplo, una clase que <i>no está</i> bien encapsulada; usar un método público de inicialización disponible libremente, como sugiere el ejemplo de abajo puede causar problemas.
</p><p>{{wiki.template('Block-title', [ "AlgunaClase Class Inicializacion" ])}}
</p>
<pre>class SomeClass
{
  public:
    // Constructor
    AlgunaClase();

    // Virtual Destructor
    virtual ~AlgunaClase();

    // init method
    void Init();

    void HazAlgoUtil();
};
</pre>
<p>Para que este sistema funcione correctamente, el programador del cliente debe prestar mucha atención a todas las reglas que el programador del componente estableció. Este es el acuerdo contractual de esta clase clase no encapsulada: un conjunto de reglas que definen cuando cada método puede ser llamado y cuando se espera que se haga. Una regla puede especificar que <code>HazAlgoUtil</code> puede ser llamado sólo después de una llamada a <code>Init()</code>. El método <code>HazAlgoUtil</code> puede hacer algún tipo de validación para asegurar que la condición de que <code>Init()</code> ha sido llamado, ha sido cumplida.
</p><p>Además de escribir código bien comentado que le diga al desarrollador del cliente las reglas acerca de <code>Init()</code>, el desarrollador puede seguir un par de pasos para hacer este contrato más claro. Primero, la construcción de un objeto puede ser encapsulada y proveer una <i>clase virtual</i> que defina el método <code>HazAlgoUtil</code>. De esta forma, construcción e inicialización pueden ser completamente ocultos de los clientes de la clase. En esta situación "semiencapsulada", la única parte de la clase que se ve esuna bien definida lista de métodos llamables (la interfaz). Una vez que la clase es encapsulada, la única interfaz que verá el cliente es esta:
</p><p>{{wiki.template('Block-title', [ "Encapsulación de AlgunaInterfaz" ])}}
</p>
<pre>class AlgunaInterfaz
{
  public:
    virtual void HazAlgoUtil() = 0;
};
</pre>
<p>La implementación puede entonces derivar de esta clase e implementar el método virtual. Los clientes de este código pueden usar después un patrón de diseño factoría para crear el objeto (ve {{template.Anch("Factorías")}}) y después encapsular la implementación. En XPCOM, los clientes se escudan de la lógica interna de los componentes de esta forma y confiar en la interfaz para proveer acceso a la funcionalidad requerida.
</p><p><br>
</p>
<h4 name="La_Interfaz_Base_nsISupports"> La Interfaz Base <code>nsISupports</code> </h4>
<p>Dos aspectos fundamentales en la programación basada en componentes e interfaces son: la <i>Vida del componente</i>, también llamada <i>pertenencia del objeto</i> y las <i>llamadas de interfaz</i>, o poder identificar que interfaces soporta un componente al momento de ejecución
</p><p>Two fundamental issues in component and interface-based programming are <i>component lifetime</i>, also called <i>object ownership</i>, and <i>interface querying</i>, or being able to identify which interfaces a component supports at runtime. This section introduces the base interface-the mother of all interfaces in XPCOM - <code>nsISupports</code>, which provides solutions to both of these issues for XPCOM developers.
</p>
<h5 name="Object_Ownership"> Object Ownership </h5>
<p>In XPCOM, since components may implement any number of different interfaces, interfaces must be <i>reference counted</i>. Components must keep track of how many references to it clients are maintaining and delete themselves when that number reaches zero.
</p><p>When a component gets created, an integer inside the component tracks this reference count. The reference count is incremented automatically when the client instantiates the component; over the course of the component's life, the reference count goes up and down, always staying above zero. At some point, all clients lose interest in the component, the reference count hits zero, and the component deletes itself.
</p><p>When clients use interfaces responsibly, this can be a very straightforward process. XPCOM has tools to make it even easier, as we describe later. It can raise some real housekeeping problems when, for example, a client uses an interface and forgets to decrement the reference count. When this happens, interfaces may never be released and will leak memory. The system of reference counting is, like many things in XPCOM, a contract between clients and implementations. It works when people agree to it, but when they don't, things can go wrong. It is the responsibility of the function that creates the interface pointer to add the initial reference, or <i>owning reference</i>, to the count.
</p>
<div class="side-note">
<p>{{wiki.template('Block-title', [ "Pointers in XPCOM" ])}}
</p><p>In XPCOM, <i>pointers</i> refer to interface pointers. The difference is a subtle one, since interface pointers and regular pointers are both just address in memory. But an interface pointer is known to implement the nsISupports base interface, and so can be used to call methods such as <code>AddRef</code>, <code>Release</code>, or <code>QueryInterface</code>.
</p>
</div>
<p><code>nsISupports</code>, shown below, supplies the basic functionality for dealing with interface discovery and reference counting. The members of this interface, <code>QueryInterface</code>, <code>AddRef</code>, and <code>Release</code>, provide the basic means for getting the right interface from an object, incrementing the reference count, and releasing objects once they are not being used, respectively. The <code>nsISupports</code> interface is shown below:
</p><p>{{wiki.template('Block-title', [ "The &lt;code&gt;nsISupports&lt;/code&gt; Interface" ])}}
</p>
<pre>class Sample: public nsISupports
{
  private:
    nsrefcnt mRefCnt;
  public:
    Sample();
    virtual ~Sample();

    NS_IMETHOD QueryInterface(const nsIID &amp;aIID, void **aResult);
    NS_IMETHOD_(nsrefcnt) AddRef(void);
    NS_IMETHOD_(nsrefcnt) Release(void);
};
</pre>
<p>The various types used in the interface are described in the {{template.Anch("XPCOM Types")}} section below. A complete (if spare) implementation of the <code>nsISupports</code> interface is shown below. See <a class="external" href="http://www.mozilla.org/projects/xpcom/QI.html">A Reference Implementation of QueryInterface</a> for detailed information.
</p><p>{{wiki.template('Block-title', [ "Implementation of &lt;code&gt;nsISupports&lt;/code&gt; Interface" ])}}
</p>
<pre>// initialize the reference count to 0
Sample::Sample() : mRefCnt(0)
{ 
}
Sample::~Sample()
{
}

// typical, generic implementation of QI
NS_IMETHODIMP Sample::QueryInterface(const nsIID &amp;aIID,
                                  void **aResult)
{
  if (!aResult) {
    return NS_ERROR_NULL_POINTER;
  }
  *aResult = NULL;
  if (aIID.Equals(kISupportsIID)) {
    *aResult = (void *) this;
  }
  if (!*aResult) {
    return NS_ERROR_NO_INTERFACE;
  }
  // add a reference
  AddRef();
  return NS_OK;
}

NS_IMETHODIMP_(nsrefcnt) Sample::AddRef()  
{
  return ++mRefCnt;
}

NS_IMETHODIMP_(nsrefcnt) Sample::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0;
  }
  // optional: return the reference count
  return mRefCnt;
}
</pre>
<h5 name="Object_Interface_Discovery"> Object Interface Discovery </h5>
<p><i>Inheritance</i> is another very important topic in object oriented programming. Inheritance is the means through which one class is derived from another. When a class inherits from another class, the inheriting class may <i>override</i> the default behaviors of the base class without having to copy all of that class's code, in effect creating a more specific class, as in the following example:
</p><p>{{wiki.template('Block-title', [ "Simple Class Inheritance" ])}}
</p>
<pre>class Shape
{
  private:
    int m_x;
    int m_y;

  public:
    virtual void Draw() = 0;
    Shape();
    virtual ~Shape();
};
 
class Circle : public Shape
{
  private:
    int m_radius;
  public:
    virtual Draw();
    Circle(int x, int y, int radius);
    virtual ~Circle();
};
</pre>
<p><code>Circle</code> is a derived class of <code>Shape</code>. A <code>Circle</code> is a <code>Shape</code>, in other words, but a <code>Shape</code> is not necessarily a <code>Circle</code>. In this case, <code>Shape</code> is the <i>base class</i> and <code>Circle</code> is a <i>subclass</i> of <code>Shape</code>.
</p><p>In XPCOM, all classes derive from the <code>nsISupports</code> interface, so all objects are <code>nsISupports</code> but they are also other, more specific classes, which you need to be able to find out about at runtime. In {{template.Anch("Simple Class Inheritance")}}, for example, you'd like to be able ask the <code>Shape</code> if it's a <code>Circle</code> and to be able to use it like a circle if it is. In XPCOM, this is what the <code>QueryInterface</code> feature of the <code>nsISupports</code> interface is for: it allows clients to find and access different interfaces based on their needs.
</p><p>In C++, you can use a fairly advanced feature known as a <code>dynamic_cast&lt;&gt;</code>, which throws an exception if the <code>Shape</code> object is not able to be cast to a <code>Circle</code>. But enabling exceptions and <abbr title="Runtime Type Information">RTTI</abbr> may not be an option because of performance overhead and compatibility on many platforms, so XPCOM does things differently.
</p>
<div class="side-note">
<p>{{wiki.template('Block-title', [ "Exceptions in XPCOM" ])}}
</p><p>C++ exceptions are not supported directly by XPCOM. Instead all exceptions must be handled within a given component, before crossing interface boundaries. In XPCOM, all interface methods should return an <code>nsresult</code> error value (see the <a href="es/XPCOM_API_Reference">XPCOM API Reference</a> for a listing of these error codes). These error code results become the "exceptions" that XPCOM handles.
</p>
</div>
<p>Instead of leveraging C++ RTTI, XPCOM uses the special <code>QueryInterface</code> method that casts the object to the right interface if that interface is supported.
</p><p>Every interface is assigned an identifier that gets generated from a tool commonly named "uuidgen". This universally unique identifier (UUID) is a unique, 128-bit number. Used in the context of an interface (as opposed to a component, which is what the contract ID is for), this number is called an <i>IID</i>.
</p><p>When a client wants to discover if an object supports a given interface, the client passes the IID assigned to that interface into the <code>QueryInterface</code> method of that object. If the object supports the requested interface, it adds a reference to itself and passes back a pointer to that interface. If the object does not support the interface an error is returned.
</p>
<pre>class nsISupports { 
  public:
    long QueryInterface(const nsIID &amp; uuid,
                        void **result) = 0;
    long AddRef(void) = 0;
    long Release(void) = 0;
};
</pre>
<p>The first parameter of <code>QueryInterface</code> is a reference to a class named <code>nsIID</code>, which is a basic encapsulation of the IID. Of the three methods on the <code>nsIID</code> class, <code>Equals</code>, <code>Parse</code>, and <code>ToString</code>, <code>Equals</code> is by far the most important, because it is used to compare two <code>nsIID</code>s in this interface querying process.
</p><p>When you implement the {{template.Interface("nsISupports")}} class (and you'll see in the chapter <a href="es/Creating_XPCOM_Components/Using_XPCOM_Utilities_to_Make_Things_Easier">Using XPCOM Utilities to Make Things Easier</a> how macros can make this process much easier), you must make sure the class methods return a valid result when the client calls <code>QueryInterface</code> with the <code>nsISupports</code> IID. <code>QueryInterface</code> should support all interfaces that the component supports.
</p><p>In implementations of <code>QueryInterface</code>, the IID argument is checked against the <code>nsIID</code> class. If there is a match, the object's <code>this</code> pointer is cast to <code>void</code>, the reference count is incremented, and the interface returned to the caller. If there isn't a match, the class returns an error and sets the out value to <code>null</code>.
</p><p>In the example above, it's easy enough to use a C-style cast. But casting can become more involved where you must first cast <code>void</code> then to the requested type, because you must return the interface pointer in the <abbr title="virtual table">vtable</abbr> corresponding to the requested interface. Casting can become a problem when there is an ambiguous inheritance hierarchy.
</p>
Revertir a esta revisión