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: 276188
  • Creada:
  • Creador: Maharba
  • ¿Es la revisión actual? No
  • Comentario /* Descubrimiento de Objetos de Interfaz */

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', [ "Inicializacion de AlgunaClase" ])}}

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 posesión del objeto y las llamadas de interfaz, o poder identificar que interfaces soporta un componente al momento de ejecución. Esta sección introduce la interfaz base, que es la madre de todas las interfaces en XPCOM, nsISupports, la cual proporciona soluciones a estos dos aspectos para los desarrolladores de XPCOM.

Posesión de Objetos

Como los componentes en XPCOM pueden implementar cualquier número de interfaces, dichas interfaces deben ser "contadas por referencia". Los componentes deben tener control de cuántas referencias a él tienen activas los clientes y borrarse ellos mismos cuando ese número llega a cero.

Cuando un componente se crea, un entero dentro del componente almacena esta cuenta de referencias. La cuenta de referencias se incrementa automáticamente cuando el cliente hace una instancia del componente; durante el transcurso de vida del componente. En algún punto, todos los clientes pierden interés en el componente, en ese momento la cuenta llega a cero y el componente se borra a sí mismo.

Cuando los clientes usan interfaces responsablemente, esto puede ser un proceso muy serio. XPCOM tiene herramientas para hacer esto más sencillo, como describiremos después. Podemos tener serios problemas cuando por ejemplo, un cliente usa una interfaz y olvida decrementar la cuenta de referencia. Cuando esto pasa, las interfaces tal vez nunca puedan ser liberadas y se desbordará la memoria. El sistema de cuenta de referencias es, como muchas otras cosas en XPCOM, un contrato entre clientes e implementaciones. Trabaja cuando la gente se pone de acuerdo con él, pero si no, las cosas pueden ir mal. Es responsabilidad de la funcion que crea el puntero a la interfaz añadir la referencia inicial o posesión de referencia a la cuenta.

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

En XPCOM, "punteros" se refiere a los punteros de interfaz. La diferencia es muy sutil ya que los punteros de interfaz y los punteros comunes son ambos sólo direcciones en memoria. Pero un puntero de interfaz debe poder implementar la interfaz base nsISupports, que también puede ser usada para llamar métodos como AddRef, Release, or QueryInterface.

nsISupports, mostrado abajo, proporciona la funcionalidad básica para lidiar con el descubrimiento de la interfaz y la cuenta de referencias. Los miembros de esta interfaz, QueryInterface, AddRef, and Release, proporcionan los medios básicos para conseguir el interfaz correcto de un objeto, incrementando la cuenta de referencias y liberando objetos una vez que dejan de ser usados respectivamente. La interfaz nsISupports se muestra a continuación:

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

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);
};

Los distintos tipos usados en la interfaz son descritos en la sección {{template.Anch("Tipos XPCOM")}} más adelante. Una implementación completa de la interfaz nsISupports se muestra abajo. Vea A Reference Implementation of QueryInterface para información más detallada.

{{wiki.template('Block-title', [ "Implementación de la interfaz <code>nsISupports</code>" ])}}

// inicializa la cuenta de referencias a 0
Sample::Sample() : mRefCnt(0)
{ 
}
Sample::~Sample()
{
}

// típica implementación genérica de 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;
  }
  // añade una referencia
  AddRef();
  return NS_OK;
}

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

NS_IMETHODIMP_(nsrefcnt) Sample::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0;
  }
  // opcional: regresa la cuenta de referencias
  return mRefCnt;
}
Descubrimiento de Objetos de Interfaz

Herencia es otro tópico muy importante en la programación orientada a objetos. Herencia es el medio por el que una clase es derivada de otra. Cuando una clase hereda de otra clase, le clase hija puede sobreescribir los comportamientos originales de la clase base sin tener que copiar todo el código de esa clase, en efecto creando una clase más específica, como en el ejemplo siguiente:


{{wiki.template('Block-title', [ "Herencia de la Clase Simple" ])}}

class Figura
{
  private:
    int m_x;
    int m_y;

  public:
    virtual void Pintar() = 0;
    Shape();
    virtual ~Shape();
};
 
class Circulo : public Figura
{
  private:
    int m_radio;
  public:
    virtual Pintar();
    Circulo(int x, int y, int radio);
    virtual ~Circulo();
};

Circulo es una clase derivada de Figura. En otras palabras un Circulo es una Figura, pero una Figura no es necesariamente un Circulo. En este caso, Figura es la clase base y Circulo es una subclase de Figura.

En XPCOM, todas las clases derivan de la interfaz nsISupports, así que todos los objetos son nsISupports pero también son otras clases más específicas que necesitas para poder encontrarlas en tiempo de ejecución. En {{template.Anch("Herencia de la Clase Simple")}}, por ejemplo, ¿te gustaría poder preguntarle a la Figura si es un Circulo y poder usarlo como circulo si lo es? En XPCOM, esto es para lo que está la caracteríztica QueryInterface de la interfaz nsISupports: permite a los clientes encontrar y accesar diferentes interfaces de acuerdo a sus necesidades.

En C++, puedes usar un aspecto verdaderamente avanzado conocido como refernecia_dinámica<>, que da una excepción si el objeto Figura no puede hacer referencia a Circulo. Pero habilitar las excepciones y RTTI puede no ser una opción por la mejora del rendimiento y la compatibilidad en varias plataformas, así que XPCOM hace las cosas diferente.

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

Las excepciones de C++ no son soportadas directamente en XPCOM. Todas las excepciones deben ser gestionadas dentro de un componente dado, antes de cruzar los límites de las interfaces. En XPCOM, todos métodos de interfaz deben regresar un valor de error nsresult (Vea la Referencia del API de XPCOM para ver la lista de códigos de error). Esos resultados de códigos de error se vuelven "excepciones" que gestiona XPCOM.

En vez de utilizar el RTTI de C++, XPCOM usa el método especial QueryInterface que referencía el objeto a la interfaz correcta si esa interfaz es soportada.

A cada interfaz se le asigna un identificador que se genera de una herramienta comunmente llamada "uuidgen". Este identificador universal único es un número único de 128 bits. Usado en el contexto de una interfaz (similar a un componente, donde el contract ID(ID de contrato) hace esta función), a este número se le conoce como IID.

Cuando un cliente quiere saber si un objeto soporta una interfaz dada, el cliente pasa el IID asignado a esa interfaz al método QueryInterface de ese objeto. Si el objeto soporta la interfaz requerida, añade una referencia a sí mismo y regresa un puntero a esa interfaz. Si el objeto no soporta la interfaz, regresa un error.

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

El primer parámetro de QueryInterface es una referencia a la clase llamada nsIID, que es una encapsulación básica del IID. De los tres métodos en la clase nsIID, Equals, Parse, and ToString, Equals es por mucho el más importante, porque se usa para comparar dos nsIIDs en el proceso de requerimiento de esta interfaz.

Cuando implementas la clase {{template.Interface("nsISupports")}} (y verás en el capítulo Uso de Utilidades XPCOM para hacer las cosas más fáciles como las macros pueden hacer este proceso mucho más sencillo), debes asegurarte que los métodos de la clase regresan un resultado válido cuando el cliente llama QueryInterface con el IID de nsISupports. QueryInterface debe soportar todas las interfaces que el componente soporta.

En las implementaciones de QueryInterface, el parámetro IID es comparado con el nsIID de la clase. Si coinciden, el puntero this del objeto es referenciado a void, la cuenta de referencias se incrementa y la interfaz es devuelta al llamador. Si no coinciden, la clase regeresa un error y pone el valor de salida a null.

En el ejemplo de arriba, es muy fácil usar referencias al estilo de C. Pero referenciar puede volverse más complicado donde debes primero referenciar a void y luego al tipo requerido porque debes regresar el puntero a la interfaz en el vtable correspondiente a la interfaz requerida. Referenciar puede volverse un problema cuando hay un orden ambiguo de herencia.

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', [ "Inicializacion de AlgunaClase" ])}}
</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>
<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>posesión del objeto</i> y las <i>llamadas de interfaz</i>, o poder identificar que interfaces soporta un componente al momento de ejecución. Esta sección introduce la interfaz base, que es la madre de todas las interfaces en XPCOM, <code>nsISupports</code>, la cual proporciona soluciones a estos dos aspectos para los desarrolladores de XPCOM.
</p>
<h5 name="Posesi.C3.B3n_de_Objetos"> Posesión de Objetos </h5>
<p>Como los componentes en XPCOM pueden implementar cualquier número de interfaces, dichas interfaces deben ser "contadas por referencia". Los componentes deben tener control de cuántas referencias a él tienen activas los clientes y borrarse ellos mismos cuando ese número llega a cero.
</p><p>Cuando un componente se crea, un entero dentro del componente almacena esta <i>cuenta de referencias</i>. La cuenta de referencias se incrementa automáticamente cuando el cliente hace una instancia del componente; durante el transcurso de vida del componente. En algún punto, todos los clientes pierden interés en el componente, en ese momento la cuenta llega a cero y el componente se borra a sí mismo.
</p><p>Cuando los clientes usan interfaces responsablemente, esto puede ser un proceso muy serio. XPCOM tiene herramientas para hacer esto más sencillo, como describiremos después. Podemos tener serios problemas cuando por ejemplo, un cliente usa una interfaz y olvida decrementar la cuenta de referencia. Cuando esto pasa, las interfaces tal vez nunca puedan ser liberadas y se desbordará la memoria. El sistema de cuenta de referencias es, como muchas otras cosas en XPCOM, un contrato entre clientes e implementaciones. Trabaja cuando la gente se pone de acuerdo con él, pero si no, las cosas pueden ir mal. Es responsabilidad de la funcion que crea el puntero a la interfaz añadir la referencia inicial o <i>posesión de referencia</i> a la cuenta.
</p>
<div class="side-note">
<p>{{wiki.template('Block-title', [ "Punteros en XPCOM" ])}}
</p><p>En XPCOM, "punteros" se refiere a los punteros de interfaz. La diferencia es muy sutil ya que los punteros de interfaz y los punteros comunes son ambos sólo direcciones en memoria. Pero un puntero de interfaz debe poder implementar la interfaz base nsISupports, que también puede ser usada para llamar métodos como <code>AddRef</code>, <code>Release</code>, or <code>QueryInterface</code>.
</p>
</div>
<p><code>nsISupports</code>, mostrado abajo, proporciona la funcionalidad básica para lidiar con el descubrimiento de la interfaz y la cuenta de referencias. Los miembros de esta interfaz, <code>QueryInterface</code>, <code>AddRef</code>, and <code>Release</code>, proporcionan los medios básicos para conseguir el interfaz correcto de un objeto, incrementando la cuenta de referencias y liberando objetos una vez que dejan de ser usados respectivamente. La interfaz <code>nsISupports</code> se muestra a continuación:
</p><p>{{wiki.template('Block-title', [ "La &lt;code&gt;nsISupports&lt;/code&gt; Interfaz" ])}}
</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>Los distintos tipos usados en la interfaz son descritos en la sección {{template.Anch("Tipos XPCOM")}} más adelante. Una implementación completa de la interfaz <code>nsISupports</code> se muestra abajo. Vea <a class="external" href="http://www.mozilla.org/projects/xpcom/QI.html">A Reference Implementation of QueryInterface</a> para información más detallada.
</p><p>{{wiki.template('Block-title', [ "Implementación de la interfaz &lt;code&gt;nsISupports&lt;/code&gt;" ])}}
</p>
<pre>// inicializa la cuenta de referencias a 0
Sample::Sample() : mRefCnt(0)
{ 
}
Sample::~Sample()
{
}

// típica implementación genérica de 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;
  }
  // añade una referencia
  AddRef();
  return NS_OK;
}

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

NS_IMETHODIMP_(nsrefcnt) Sample::Release()
{
  if (--mRefCnt == 0) {
    delete this;
    return 0;
  }
  // opcional: regresa la cuenta de referencias
  return mRefCnt;
}
</pre>
<h5 name="Descubrimiento_de_Objetos_de_Interfaz"> Descubrimiento de Objetos de Interfaz</h5>
<p><i>Herencia</i> es otro tópico muy importante en la programación orientada a objetos. Herencia es el medio por el que una clase es derivada de otra. Cuando una clase hereda de otra clase, le clase hija puede <i>sobreescribir</i> los comportamientos originales de la clase base sin tener que copiar todo el código de esa clase, en efecto creando una clase más específica, como en el ejemplo siguiente:
</p><p><br>
{{wiki.template('Block-title', [ "Herencia de la Clase Simple" ])}}
</p>
<pre>class Figura
{
  private:
    int m_x;
    int m_y;

  public:
    virtual void Pintar() = 0;
    Shape();
    virtual ~Shape();
};
 
class Circulo : public Figura
{
  private:
    int m_radio;
  public:
    virtual Pintar();
    Circulo(int x, int y, int radio);
    virtual ~Circulo();
};
</pre>
<p><code>Circulo</code> es una clase derivada de <code>Figura</code>. En otras palabras un <code>Circulo</code> es una <code>Figura</code>,  pero una <code>Figura</code> no es necesariamente un <code>Circulo</code>. En este caso, <code>Figura</code> es la <i>clase base</i> y <code>Circulo</code> es una <i>subclase</i> de <code>Figura</code>.
</p><p>En XPCOM, todas las clases derivan de la interfaz <code>nsISupports</code>, así que todos los objetos son <code>nsISupports</code> pero también son otras clases más específicas que necesitas para poder encontrarlas en tiempo de ejecución. En {{template.Anch("Herencia de la Clase Simple")}}, por ejemplo, ¿te gustaría poder preguntarle a la <code>Figura</code> si es un <code>Circulo</code> y poder usarlo como circulo si lo es? En XPCOM, esto es para lo que está la caracteríztica <code>QueryInterface</code> de la interfaz <code>nsISupports</code>: permite a los clientes encontrar y accesar diferentes interfaces de acuerdo a sus necesidades.
</p><p>En C++, puedes usar un aspecto verdaderamente avanzado conocido como <code>refernecia_dinámica&lt;&gt;</code>, que da una excepción si el objeto <code>Figura</code> no puede hacer referencia a <code>Circulo</code>. Pero habilitar las excepciones y <abbr title="Runtime Type Information">RTTI</abbr> puede no ser una opción por la mejora del rendimiento y la compatibilidad en varias plataformas, así que XPCOM hace las cosas diferente.
</p>
<div class="side-note">
<p>{{wiki.template('Block-title', [ "Excepciones en XPCOM" ])}}
</p><p>Las excepciones de C++ no son soportadas directamente en XPCOM. Todas las excepciones deben ser gestionadas dentro de un componente dado, antes de cruzar los límites de las interfaces. En XPCOM, todos métodos de interfaz deben regresar un valor de error <code>nsresult</code> (Vea la <a href="es/Referencia_del_API_de_XPCOM">Referencia del API de XPCOM</a> para ver la lista de códigos de error). Esos resultados de códigos de error se vuelven "excepciones" que gestiona XPCOM.
</p>
</div>
<p>En vez de utilizar el RTTI de C++, XPCOM usa el método especial <code>QueryInterface</code> que referencía el objeto a la interfaz correcta si esa interfaz es soportada.
</p><p>A cada interfaz se le asigna un identificador que se genera de una herramienta comunmente llamada "uuidgen". Este identificador universal único es un número único de 128 bits. Usado en el contexto de una interfaz (similar a un componente, donde el contract ID(ID de contrato) hace esta función), a este número se le conoce como <i>IID</i>.
</p><p>Cuando un cliente quiere saber si un objeto soporta una interfaz dada, el cliente pasa el IID asignado a esa interfaz al método <code>QueryInterface</code> de ese objeto. Si el objeto soporta la interfaz requerida, añade una referencia a sí mismo y regresa un puntero a esa interfaz. Si el objeto no soporta la interfaz, regresa un error.
</p>
<pre>class nsISupports { 
  public:
    long QueryInterface(const nsIID &amp; uuid,
                        void **result) = 0;
    long AddRef(void) = 0;
    long Release(void) = 0;
};
</pre>
<p>El primer parámetro de <code>QueryInterface</code> es una referencia a la clase llamada <code>nsIID</code>, que es una encapsulación básica del IID. De los tres métodos en la clase <code>nsIID</code>, <code>Equals</code>, <code>Parse</code>, and <code>ToString</code>, <code>Equals</code> es por mucho el más importante, porque se usa para comparar dos <code>nsIID</code>s en el proceso de requerimiento de esta interfaz. 
</p><p>Cuando implementas la clase {{template.Interface("nsISupports")}} (y verás en el capítulo <a href="es/Creaci%c3%b3n_de_Componentes_XPCOM/Uso_de_Utilidades_XPCOM_para_hacer_las_cosas_m%c3%a1s_f%c3%a1ciles">Uso de Utilidades XPCOM para hacer las cosas más fáciles</a> como las macros pueden hacer este proceso mucho más sencillo), debes asegurarte que los métodos de la clase regresan un resultado válido cuando el cliente llama <code>QueryInterface</code> con el IID de <code>nsISupports</code>. <code>QueryInterface</code> debe soportar todas las interfaces que el componente soporta.
</p><p>En las implementaciones de <code>QueryInterface</code>, el parámetro IID es comparado con el <code>nsIID</code> de la clase. Si coinciden, el puntero <code>this</code> del objeto es referenciado a <code>void</code>, la cuenta de referencias se incrementa y la interfaz es devuelta al llamador. Si no coinciden, la clase regeresa un error y pone el valor de salida a <code>null</code>.
</p><p>En el ejemplo de arriba, es muy fácil usar referencias al estilo de C. Pero referenciar puede volverse más complicado donde debes primero referenciar a <code>void</code> y luego al tipo requerido porque debes regresar el puntero a la interfaz en el <abbr title="virtual table">vtable</abbr> correspondiente a la interfaz requerida. Referenciar puede volverse un problema cuando hay un orden ambiguo de herencia.
</p>
Revertir a esta revisión