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 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ísticas como las listadas arriba.
Gecko
Aunque en algunos aspectos es similar a Microsoft 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.
Interfaces
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.
var el = env.locale; 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.
var el = env.locale; Inicializacion de AlgunaClase
class AlgunaClase { 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:
var el = env.locale; 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 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.
var el = env.locale; 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 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
, o 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:
var el = env.locale;
La Interfaz nsISupports
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 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.
var el = env.locale;
Implementación de la interfaz nsISupports
// 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:
var el = env.locale;
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 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.
var el = env.locale; 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 nsIID
s en el proceso de requerimiento de esta interfaz.
Cuando implementas la clase 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.
Identificadores XPCOM
Además del identificador de interfaz IID discutido en la sección anterior, XPCOM usa otros dos identificadores muy importantes para distinguir las clases y los componentes.
var el = env.locale; Clases Identificadoras de XPCOM
La clase nsIID
es es un tupo definido para la clase nsID
. Los otros tipos definidos de nsID
, CID e IID, se refieren a implementaciones específicas de una clase en concreto y a una iterfaz específica, respectivamente.
La clase nsID
proporciona métodos como Equals
para comparar identificadores en el código. Ve Identificadores en XPCOM para mayor información de la clase nsID
.
CID
Un CID es un número de 128 bits que identifica como únicos a una clase o un componente de manera muy parecida a la forma en que un IID identifica una interfaz. El CID para nsISupports
se ve como este:
00000000-0000-0000-c000-000000000046
El largo de un CID puede hacer incómodo manejarlo en el código, así que muy a menudo verás #defines para los CIDs y otros identificadores usados, como en el siguiente ejemplo:
#define CID_EJEMPLO \ { 0x777f7150, 0x4a2b, 0x4301, \ { 0xad, 0x10, 0x5e, 0xab, 0x25, 0xb3, 0x22, 0xaa}}
También verás que NS_DEFINE_CID
es muy usado. Esta simple macro declara una constante con el valor del CID:
static NS_DEFINE_CID(kWebShellCID, NS_WEB_SHELL_CID);
Un CID es algunas veces llamado identificador de clase. Si la clase a la que se refiere un CID implementa más de una interfaz, ese CID garantiza que la clase implementa todo ese conjunto de interfaces cuando se publica como congelado.
Contract ID
Un contract ID es una cadena leible humanamente usada para accesar un componente. Un CID o un contract ID puede ser usado para obtener un componente desde el gestor de componentes. Este es el contract ID para el componente de Operación LDAP:
"@mozilla.org/network/ldap-operation;1"
El formato del contract ID es el dominio del componente, el módulo, el nombre del componente y el número de versión separados por diagonales.
Como un CID, el contract ID se refiere a una implementación más que a una interfaz, como lo hace un IID. Pero un contract ID no está relacionado a ninguna implementación en específico, como el CID, por lo cual es más general. Más bien, un contract ID especifica un conjunto de interfaces dadas que requiere implementadas y cualquier número de CIDs diferentes pueden estar presentes y llenar ese requerimiento. Esta diferencia entre un contract ID y un CID es lo que hace posible sobreescribir componentes.
Factorías
Una vez que el código es dividido en componentes, el código cliente típicamente usa el operador new
para instanciar los objetos a usar:
SomeClass* component = new SomeClass();
Este patrón requiere que el cliente sepa algo acerca del componente, al menos qué tan grande es. El patrón de diseño factoría puede usarse para encapsular la construcción de objetos. El objetivo principal de una factoría es crear un objeto sin mostrar a los clientes la implementación e inicialización de este objeto. En el ejemplo SomeClass
la construcción e inicialización de SomeClass
que implementa la clase abstracta SomeInterface
, es contenida dentro de la función New_SomeInterface
que sigue el patrón de diseño factoría:
var el = env.locale; Encapsulación del Constructor
int New_SomeInterface(SomeInterface** ret) { // crea el objeto SomeClass* out = new SomeClass(); if (!out) return -1; // inicializa el objeto if (out->Init() == FALSE) { delete out; return -1; } // referencia de la interfaz *ret = static_cast<SomeInterface*>(out); return 0; }
La factoría es la clase que gestiona la creación de instancias separadas de un componente para su uso. En XPCOM, las factorías son implementaciones de la interfaz nsIFactory
y usan un patrón de diseño factoría como el ejemplo de arriba para abstraer y encapsular la construcción e inicialización del objeto.
El ejemplo en Encapsulación del Constructor es una versión simple sin estado de las factorías, pero programarlo en el mundo real usualmente no es tan simple y en general las factorías necesitan guardar un estado. La factoría necesita, por lo menos preservar información de qué objetos ha creado. Cuando una factoría gestiona instancias de una clase contenida en un biblioteca dinámica compartida, por ejemplo, necesita saber cuando puede descargar la biblioteca. Cuando la factoría preserva un estado, puedes preguntarle si hay referencias esperando y saber si la factoría creó objetos.
Otro estado que puede guardar una factoría es si un objeto es o no singleton. Por ejemplo, si una factoría crea un objeto que se supone debe ser singleton, entonces las llamadas subsecuentes a la factoría por el objeto deben regresar el mismo objeto. Aunque que hay herramientas y mejores formas de gestionar un singleton (lo que discutiremos cuando hablemos del nsIServiceManager
), un desarrollador tal vez quiera usar esta información para asegurarse de que sólo puede existir un objeto singleton sin importar lo que hagan los clientes.
Los requerimientos de una clase factoría pueden gestionarse de una manera estrictamente funcional con el estado guardado en variables globales, pero hay beneficios de usar clases para las factorías. Cuando usas una clase para implementar la funcionalidad de una factoría, por ejemplo, derivas de la interfaz nsISupports
, que te permite manejar el tiempo de vida de los objetos de la factoría por sí mismos. Esto es importante cuando quieres agrupar conjuntos de factorías y determinar si pueden ser descargados. Otro beneficio de usar la interfaz nsISupports
es que puedes soportar otras interfaces al momento en que sean introducidas. Como mostraremos al discutir nsIClassInfo
, algunas factorías permiten pedir información acerca de la implementación que tienen debajo, como el lenguaje en el está escrito el objeto, las interfaces que soporta, etc. Este tipo de "comprobación futura" es una ventaja clave que se obtiene al derivar de nsISupports
.
XPIDL y Bibliotecas de Tipos
Una manera fácil y potente de definir una interfaz es - en efecto, un requerimiento para definir interfaces en un ambiente multiplataforma, independiente del lenguaje- es usar un lenguaje de definición de interfaces (IDL). XPCOM usa su propia variante del Lenguaje de Definición de Interfaces (IDL) de CORBA OMG llamado XPIDL, que te permite especificar métodos, atributos y contantes de una interfaz dada y también definir herencia de interfaz.
Hay algunas desventajas de definir tu interfaz usando XPIDL. No hay soporte para herencia múltiple de una sola cosa; si defines una interfaz nueva, no puede derivar de más de una interfaz; otra limitante de las intertfaces en XPIDL es que los nombres de los métodos deben ser únicos, no puedes tener dos métodos con el mismo nombre aunque tomen distintos parámetros, es decir no se permite la sobrecarga de funciones, y el trabajo que implica tener múltiples nombres de funciones no es agradable:
void AlgoConInt(in int x); void AlgoConString(in string x); void AlgoConURI(in nsIURI x);
De cualquier modo, estos pequeños inconvenientes palidecen en comparación con la funcionalidad ganada usando XPIDL. XPIDL te permite generar bibliotecas de tipos, o typelibs, que son archivos con la extensión .xpt. La biblioteca de tipo es una representación binaria de una interfaz o interfaces, permite el control programático y acceso de la interfaz, lo que es crucial para las interfaces que no son usadas en el mundo de C++. Cuando los componentes son accesados desde otros lenguajes,como puede hacerse en XPCOM, usan la biblioteca binaria de tipo para accesar a la interfaz, ver qué métodos soporta y llamar esos métodos. Este aspecto de XPCOM se llama XPConnect. XPConnect es la capa de XPCOm que permite el acceso a los componentes de XPCOM desde lenguajes como JavaScript. Ve Conexión a Componentes desde la Interfaz para más información de XPConnect.
Cuando un componente es accesible desde un lenguaje distinto a C++, como JavaScript, se le ordena a su interfaz "reflejarse" en ese lenguaje. Cada interfaz reflejada debe tener una biblioteca de tipos correspondiente. Actualmente puedes escribir componentes en C, C++, Javascript (y algunas veces Python o Java, dependiendo del estado de las etiquetas respectivas) y hay esfuerzos tratando de construir etiquetas XPCOm para Ruby y Perl también.
var el = env.locale; Escribir Componentes en Otros Lenguajes
Tal vez no tengas acceso a algunas de las herramientas que XPCOM da para los desarrolladores en C++ (como macros, plantillas, punteros inteligentes y otros) cuando creas componentes en otros lenguajes, tal vez te tengas que conformar con el lenguaje en sí mismo prescindir de C++ y construir, por ejemplo, un componente XPCOm basado en Python que pueden ser usados desde JavaScript o vice versa.
Ve Resources Para más información de Python y otros lenguajes para los que se ha añadido soporte en XPCOM.
Todas las interfaces públicas en XPCOM sin definidas usando la sintaxis XPIDL. Las Bibliotecas de tipos y los archivos de cabecera de C++ son generados a partir de estos archivos IDL y la herramienta que genera esos archivos se llama compilador xpidl. La sección Definición de la Interfaz WebLock en XPIDL describe la sintaxis XPIDL a detalle.
Servicios de XPCOM
Cuando los clientes usan los componentes, normalmente instancían un nuevo objeto cada vez que necesitan la funcionalidad que da un componente. Este es el caso cuando, por ejemplo, los clientes lidian con archivos: cada archivo distinto es representado por un objeto diferente y muchos objetos de archivo pueden ser usados al mismo tiempo en cualquier momento.
Pero también hay un tipo de objeto conocido como servicio, del cual siempre hay sólo una copia (aunque puede haber varios servicios corriendo al mismo tiempo). Cada vez que un cliente quiere accesar la funcionalidad de un servicio, se comunican con la misma instancia de ese servicio. Cuando un usuario busca un número telefónico en la base de datos de una compañía , por ejemplo, probablemente esa base de datos está representada por un "objeto" que es el mismo para todos los trabajadores. Si no lo fuera, la aplicación necesitaría varias copias de una gran base de datos en memoria, para una misma cosa y tal vez habría inconsistencias entre los datos grabados porque las copias serían diferentes.
Dar este único punto de acceso a la funcionalidad es para lo que está el patrón de diseño singleton y es lo que los servicios hacen en una aplicación (y en un ambiente de desarrollo como XPCOM).
En XPCOM, además del soporte y gestión de componentes, hay un número de servicios que ayudan al desarrollador a escribir componentes multiplataforma. Estos servicios incluyen una abstracción de archivos multiplataforma que da un acceso a archivos uniforme y potente, los servicios de directorio que mantienen la locación de la aplicación y locaciones específicas del sistema, manejo de memoria para asegurar que todos usen el mismo localizador de memoria y el sistema de notificación de eventos que permite el paso de mensajes simples. El tutorial mostrará cada uno de estos componentes y servicios en uso, y la XPCOM API Reference tiene una lista completa de las interfaces en éstas áreas.
Tipos de XPCOM
Hay muchos tipos XPCOM declarados y macros simples que usaremos en los siguientes ejemplos, la mayoría de esos tipos son simples correlaciones. Los tipos más comunes son descritos en las siguientes secciones.
Tipos de Métodos
Los siguientes son un conjunto de tipos para asegurar la convención correcta de llamadas y tipos regresados de los métodos XPCOM.
NS_IMETHOD | Tipo regresado en la declaración del método. Las declaraciones de métodos XPCOM deben usar este como su tipo de regreso. |
NS_IMETHODIMP | Tipo de regreso de implementación del método. Las implementaciones de métodos XPCOM deben usar este como su tipo de regreso. |
NS_IMETHODIMP_(tipo) | Tipo de regreso de implementaciones de casos especiales. Algunos métodos como AddRef y Release no regresan el tipo por defecto. Esta excepción es regrettable, pero requerida para cumplir la compatibilidad con COM. |
NS_IMPORT | Forza el método a ser resuelto internamente por la biblioteca compartida. |
NS_EXPORT | Forza el método a ser exportado por la biblioteca compartida. |
Cuenta de Referencias
Estas Macros manejan la cuenta de referencias.
NS_ADDREF | Llama a AddRef en un objeto nsISupports . |
NS_IF_ADDREF | Lo mismo que el anterior pero valida null antes de llamar a AddRef . |
NS_RELEASE | Llama a Release en un objeto nsISupports . |
NS_IF_RELEASE | Lo mismo que el anterior pero valida null antes de llamar a Release . |
Códigos de Estatus
Estas macros prueban códigos de estatus.
NS_FAILED | Regresa verdadero si el código de estatus pasado fue fallo. |
NS_SUCCEEDED | Regresa verdadero si el código de estatus pasado fue éxito. |
Correlaciones Variables
nsrefcnt | Tipo de cuenta de referencias por defecto. Correlaciona un entero de 32-bits. |
nsresult | Tipo de error por defecto. Correlaciona un entero de 32-bits. |
nsnull | Valor nulo por defecto. |
Códigos de Error Comunes de XPCOM
NS_ERROR_NOT_INITIALIZED | Regresado cuando una instancia no está inicializada. |
NS_ERROR_ALREADY_INITIALIZED | Regresado cuando una instancia ya fue inicializada. |
NS_ERROR_NOT_IMPLEMENTED | Regresado por un método no implementado. |
NS_ERROR_NO_INTERFACE | Regresado cuando una interfaz dada no es soportada. |
NS_ERROR_NULL_POINTER | Regresado cuando un puntero válido es nsnull . |
NS_ERROR_FAILURE | Regresado cuando un método falla. Caso de error genérico. |
NS_ERROR_UNEXPECTED | Regresado cuando ocurre un error inesperado. |
NS_ERROR_OUT_OF_MEMORY | Regresado cuando una localización de memoria falla. |
NS_ERROR_FACTORY_NOT_REGISTERED | Regresado cuando una clase requerida no está registrada. |