|
Ahora que sabemos cómo funciona una interfase y como se implementa en Object Pascal, veamos cómo funcionan las interfases COM (y DCOM) en específico.
COM es un transporte para interfases. La conveniencia de COM es que viene con todas las copias de Windows en el planeta, así que no hay que preocuparse de instalar librerías con su sistema. La parte inconveniente es que si usted utiliza COM, su programa solo puede hablar con máquinas que corran Windows (hay un esfuerzo para implementar la librería COM en Unix, pero no ha tenido éxito técnico o político).
ActiveX es simplemente un conjunto de interfases COM y utilerías relacionadas que, cuando usted implementa, permiten que su producto sea utilizado en lenguajes de programación como componente visual. Todos los componentes de Visual Basic son ActiveX, y cualquier componente de Delphi puede implementar un ActiveX también. Esto quiere decir que cualquier componente visual que usted haga en Delphi puede ser utilizado en programas de Visual Basic. A su vez, Delphi puede utilizar cualquier componente ActiveX.
La diferencia entre un Componente de ActiveX y un componente de Delphi normal (.pas) es que ActiveX es independiente del lenguaje, pero los componentes de Delphi son mucho más compactos y no le requiere redistribuir binarios ajenos a su programa.
La implementación de una interfase COM en Delphi puede vivir en cualquier archivo de 32 bits que Windows pueda ejecutar. Estos archivos pueden ser ejecutables, Librerías DLL o Librerías OCX. Hay ciertos detalles en cuanto a lo que es soportado en cada uno de estos tipos de implementación, como hilos de ejecución o capacidad de funcionar como un control de ActiveX. Un solo ejecutable puede implementar todas sus interfases, o puede usted granularizar en diferentes servicios como más convenga a su caso en particular.
Para poder instanciar una interfase, la computadora necesita saber en qué ejecutable vive esta interfase, y además necesita saber algunos otros detalles acerca de su ejecución, como puntero de entrada a cada una de las interfases (dentro del ejecutable), permiso de usuario con los cuales el ejecutable va a funcionar, etcétera. Toda esta información es guardada por el registro de Windows.
Lo primero que necesitamos para una interfase es no confundirla con otra. Para esto se utilizan los llamados GUIDs, o Globally Unique IDentifiers. Estos identificadores son una especie de número de serie aleatorio de 128 bits generado utilizando una función llamada CoCreateGUID. La función está escrita de manera tal que se le garantiza que éste numero nunca será repetido. Esto es importante porque el GUID es el identificador que la computadora utilizará para nuestra interfase en todas las computadoras de todo el mundo, y no queremos que nuestras interfases se confundan con las de otra persona. Por ejemplo, en todo el planeta, el siguiente GUID identifica al Active Desktop de Internet Explorer:
{FBF23B40-E3F0-101B-8488-00AA003E56F8}
Las interfases en Windows vienen en "paquetes de interfaces" llamados CoClasses. Un CoClass es una clase que implementa una o más interfaces. En COM, todas las funciones que manejan interfases de manera abstracta comienzan con el preidentificador "Co". De esta manera sabemos que esta función es una función que puede ser aplicada a cualquier CoClass, no obstante los particulares de la misma.
Tómese unos cuantos minutos para examinar el registro de Windows con el programa REGEDIT. Usted podrá observar que uno de los folders de "raíz" se llama HKEY_CLASSES_ROOT. Este folder contiene toda la información de todas las interfases. Su nombre, su identificador único, sus permisos, etcétera. Windows utiliza COM durante su operación diaria, así que usted encontrará los GUIDs de interfases de Windows, Borland, y todas sus aplicaciones junto con sus propias interfases.
Utilizar regedit para ver sus interfases puede resultar complicado. Microsoft tiene funciones para averiguar información acerca de las interfases sin tener que accesar el registro, y se reservan el derecho de cambiar el formato del registro. Así que dentro de lo posible, procure que su programa utilice las funciones del Windows API para averiguar información acerca de las interfases en vez de usar el registro.
Hay un programa muy útil llamado OleView en el sitio de Microsoft que nos proporciona toda la información pertinente a las interfases COM instaladas en la computadora.
TODO: Find link to OleView.exeLos programas en Windows pueden ser muy diferentes. ¿Cómo sabe Windows cómo instanciar una interfase? Cuando el programa inicializa (antes de comenzar a ejecutar), Delphi crea por nosotros una "Fabrica de Objetos" (Class Factory). Una fábrica de objetos es un objeto concreto que genera objetos de la clase cada vez que Windows decide que un programa cliente ha solicitado un puntero de interfase. Si usted utiliza las capacidades de RAD de Delphi, rara vez se tendrá que preocupar acerca de esto, pero es importante que tenga en mente como funciona para resolver algunos problemas que no son obvios. Por ejemplo, si por algún motivo su objeto nunca inicializa y usted nota que el procedimiento create nunca ejecuta (y el objeto está registrado), el problema puede ser que la fábrica de objetos nunca está siendo creada. Si no hay fábrica de Objetos, Windows no puede crear una CoClass.
En Object Pascal, una fábrica de objetos se instancía en la sección initialization de la unidad donde vive el objeto. Por ejemplo, un objeto recién creado utilizando los Wizards de Delphi tiene la siguiente sección de implementación en la unidad donde usted implementará el objeto:
implementation uses ComServ; initialization TAutoObjectFactory.Create(ComServer, TClientes, Class_Clientes, ciMultiInstance, tmApartment); end.
TAutoObjectFactory es una fábrica de clases que genera objetos COM. El Create crea una nueva fábrica de clases y la añade al ComServer (un objeto global dentro de la unidad ComServ que mantiene una lista de todas las fábricas de clases en este ejecutable). También registra el hecho de que va a instanciar objetos de Delphi TClientes cada vez que Windows necesite una CoClass llamada Class_Clientes (esta CoClass fué generada por la biblioteca de tipos del proyecto automáticamente, y tiene asociado un GUID para ponerlo en el registro de Windows. Vea el capítulo práctico para entender como funciona con un ejemplo).
También notará que el Create le notifica a la fábrica de clases que este objeto es de instancia múltiple y su modelo de hilos de ejecución es "Apartamento".
Por ejemplo, las siguientes gráficas ilustran cómo hace COM para instanciar un objeto a partir de una fábrica de Clases. Cuando un cliente pide la interfase IClientes (1), COM busca en el registro de Windows el GUID que corresponde a IClientes (a menos que usted haya especificado el GUID en vez del nombre), y después busca el ejecutable que corresponde al GUID. Si el archivo no está en memoria, lo ejecuta (2). Una vez hecho esto, COM busca en la lista de la librería de clases (que Delphi guarda en el objeto global ComServer) la fábrica de clases (en rojo) que puede crear IClientes (representado por Class_Clientes en el código fuente arriba) (3).
Después, COM llama al método de la fábrica de clases para crear un objeto, y la fábrica de clases crea un objeto TClientes (4) (usando el método TComObject.CreateFromFactory) y asigna un puntero de interfase, el cual es devuelto a COM (5).
COM a su vez crea un puntero de interfase interno para devolver al cliente (6).
¿Porqué el paso 6? Recuerde que en un ambiente de memoria protegida como es Win32, usted no puede accesar memoria que no le pertenece a su aplicación, así que COM necesita crear un puntero para usted. Esto quiere decir que usted no puede comparar los numeros de puntero en el lado del servidor y del cliente y esperar que sean el mismo. Esto también es util cuando instanciamos un objeto usando DCOM en otra computadora (donde la posición de memoria no tiene sentido en nuestra máquina).
Usted notará que lo complicado en COM ocurre en el servidor, no en el cliente. Lo único que el cliente tiene que hacer es llamar CreateComObject para obtener un puntero de interfase y usarlo. Toda la traducción y lo "Feo" se encuentra en el lado del servidor.
El modelo de creación de una interfase se compone de dos secciónes. El modelo de instanciación (instancing model), y el modelo de hilos de ejecución (threading model)
Los modelos de instanciación son como sigue:
En este modelo, la primera vez que un cliente pide un puntero de interfase, la fábrica de objetos crea nuestro objeto. Pero la segunda vez, la fábrica de objetos simplemente encuentra la dirección de nuestro primer objeto y da esta dirección al cliente en vez de crear un nuevo objeto. De esta manera usted tiene la garantía de que sólo un TCliente estará en memoria dando servicio a todos los programas clientes.
Bajo este modelo, cada vez que un cliente pide un nuevo puntero de interfase, la fábrica de objetos crea un nuevo objeto. Bajo este modelo usted debe tener cuidado con los nombres de sus objetos para no confundirse, porque usted literalmente tendrá varios objetos TCliente rondando por ahí (uno por cada cliente).
Los modelos de hilos de ejecución (threading) son los siguientes:
Bajo este modelo, hay solo un hilo de ejecución. Todos los programas esperan a que la llamada X a Objeto uno termine antes de pedir la llamada Y a objeto dos. Esto es muy lento, pero no requiere ninguna programación en especial en el servidor, porque nos garantiza que solo una llamada va a llegar desde COM a la vez.
Bajo este modelo, COM crea un hilo de ejecución por cada objeto, y solo ejecuta una llamada a la vez por objeto (por ejemplo, si dos hilos de ejecución tratan de llamar métodos del mismo puntero de interfase a la vez, COM los llama uno por uno). Obviamente esto sólo tiene sentido en Multiple Instance. Como hay un hilo por cada objeto, debemos tener cuidado cuando utilizamos variables globales (Printers, Screen.DataModules, Screen.Forms), para evitar accesar memoria que otro de los hilos ha liberado y cosas así.
Bajo este modelo, los clientes pueden llamar a cualquier método de un objeto desde cualquier hilo de ejecución a cualquier momento. Los servidores con modelo de Free Threaded son los más difíciles de desarrollar porque deben de proteger no solo las variables globales al ejecutable, sino también las variables locales al objeto (porque otro hilo podría llamar a otra función del objeto que cambia la misma variable). Esto permite mucha facilidad a los clientes, porque los clientes pueden compartir un sólo puntero de interfase para todas sus operaciones y no preocuparse de nada.
Este modelo es un modelo que permite a clientes utilizar Apartment y Free a la vez.
Recuerde que cuando usted registra un modelo de ejecución con TAutoObjectFactory, está efectivamente entrando en un "contrato" con Windows, donde usted se compromete a escribir sus servidores poniendo ciudado a la manera en que COM le enviará las peticiones para métodos. El que usted haya dicho "Free Threaded" no quiere decir que el programa automáticamente va a tomarse cargo de la protección de memoria y diferentes consideraciones de ejecución.
El mundo de COM es muy grande, y apenas hemos visto suficiente para poder comenzar a hacer un ejemplo y entenderlo. Si este tema le interesa, le recomiendo que compre un buen libro acerca de COM/DCOM y se asome al código fuente de los objetos de Delphi en comobj.pas, ComServ.pas y ActiveX.pas. Espero que esta explicación le haya dado suficientes bases para entender conceptos más avanzados.