156 Capítulo 4. Automatización Automatizando una aplicación existente En esta sección vamos a tomar una aplicación existente y añadirle capacidades de automatización. La aplicación es un programa muy sencillo, que presenta un editor multilíneas y dos botones sobre la ventana principal. Un botón cambia el tipo de letra del editor, y el otro su color. Nada excepcional, pero servirá para ilustrar los puntos que deseamos presentar. Existen muchos casos en los que podríamos des ear automatizar una aplicación existente. Por ejemplo, suponga que hemos escrito una aplicación de contabilidad (tipo ContaWin) que ofrece la posibilidad de buscar precios de productos online. Si se automatiza la funcionalidad de búsqueda, Usted (u otro programador) podrá posteriormente crear una aplicación cliente que ordene a la aplicación de contabilidad descargar los últimos precios a intervalos de tiempo regulares. En este ejemplo aprenderemos no sólo cómo añadir capacidades de automatización a una aplicación existente, sino también cómo pasar listas de cadenas, colores y fuentes como parámetros a un método de automatización. Nota En la actualidad, muchos de los servidores de automatización externos al proceso, a diferencia del ejemplo que mostramos aquí, son programas completos en sí mismos. Por ejemplo, Microsoft Word es un servidor de automatización. Word es un programa muy completo, y proporciona la mayoría de su funcionalidad a otras aplicaciones a través de automatización. El Listado 4.2 muestra el código fuente para el formulario principal de la aplicación MemoDemo. Listado 4.2 : Aplicación MemoDemo - MainForm.pas unit MainForm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class (TForm) Memo1: TMemo; btnFont: TButton; btnColor: TButton; FontDialog1: TFontDialog; ColorDialog1: TColorDialog; procedure btnFontClick(Sender: TObject); procedure btnColorClick(Sender: TObject); private Servidores de automatización externos al proceso 157 { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.btnFontClick(Sender: TObject); begin if FontDialog1.Execute then Memo1.Font.Assign(FontDialog1.Font); end; procedure TForm1.btnColorClick(Sender: TObject); begin if ColorDialog1.Execute then Memo1.Color := ColorDialog1.Color; end; end. Como podemos ver, el código fuente de la aplicación es casi insignificante. La Figura 4.8 muestra el formulario principal de MemoDemo en tiempo de ejecución. Figura 4.8: MemoDemo no hace mucho, pero es un buen punto de partida. Escriba algún texto dentro del editor. Pulse el botón ‘ Set Font‘ para cambiar el tipo de letra que utiliza el editor, o el botón ‘SetColor ‘ para cambiar su color de fondo. Añadiendo un objeto de automatización Ahora que hemos visto MemoDemo – que, mucho me temo, no ganará ningún premio de la industria, veamos qué es necesario para añadir capacidades de automatización a éste. Una de las cosas más importantes que puede hacer cuando esté añadiendo capacidades de automatización a una aplicación existente es hacer uso del código que ya forma parte de la aplicación. Idealmente, los cuerpos de nuestros métodos de automatización deben contener una sola línea de código, que contiene una llamada a una función ya existente en la aplicación principal. 158 Capítulo 4. Automatización Si estamos desarrollando una aplicación teniendo en mente la automatización, no es difícil tener esto en cuenta desde el principio. Sin embargo, supongamos que la automatización era algo que estaba muy lejos de nuestras mentes cuando escribimos MemoDemo. La forma más rápida y sencilla de preparar una aplicación para la automatización es añadirle las funciones de soporte necesarias. Por ejemplo, en el caso de MemoDemo, deseamos ser capaces de leer y controlar el tipo de letra, el color y el texto del editor desde otra aplicación. Como debe ser obvio, debemos añadir los métodos GetColor, SetColor , GetFont, SetFont, GetText y SetText al formulario principal. El Listado 4.3 muestra el resultado de esta modularización. Listado 4.3 : Aplicación MemoSrv - MainForm.pas unit MainForm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class (TForm) Memo1: TMemo; btnFont: TButton; btnColor: TButton; FontDialog1: TFontDialog; ColorDialog1: TColorDialog; procedure btnFontClick(Sender: TObject); procedure btnColorClick(Sender: TObject); private { Private declarations } public { Public declarations } function GetColor: TColor; procedure SetColor(AColor: TColor); function GetFont: TFont; procedure SetFont(AFont: TFont); function GetText: TStrings; procedure SetText(AText: TStrings); end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.btnFontClick(Sender: TObject); begin if FontDialog1.Execute then Memo1.Font.Assign(FontDialog1.Font); end; procedure TForm1.btnColorClick(Sender: TObject); begin Servidores de automatización externos al proceso 159 if ColorDialog1.Execute then Memo1.Color := ColorDialog1.Color; end; procedure TForm1.SetColor(AColor: TColor); begin Memo1.Color := AColor; end; procedure TForm1.SetFont(AFont: TFont); begin Memo1.Font.Assign(AFont); end; procedure TForm1.SetText(AText: TStrings); begin Memo1.Lines.Assign(AText); end; function TForm1.GetColor: TColor; begin Result := Memo1.Color; end; function begin Result end; function begin Result end; end. TForm1.GetFont: TFont; := Memo1.Font; TForm1.GetText: TStrings; := Memo1.Lines; Ahora que el formulario principal soporta las funciones de acceso necesarias, es hora de añadir el objeto de automatización a MemoSrv. Seleccione FileNew en el menú principal de Delphi. Pulse sobre la página ActiveX del Depósito de Objetos, y seleccione Automation Object. Llame al objeto MemoIntf, y pulse OK. Delphi crea un nuevo fichero fuente para MemoIntf y presenta el Editor de Bibliotecas de Tipos. Vamos a añadir tres propiedades a la biblioteca de tipos: Color, Font y Text. 1. Pulse el botón New Property. Si se despliega un menú contextual, seleccione Read/Write. Llame a la propiedad Color, y asígnele el tipo OLE_COLOR . 2. Añada una nueva propiedad de lectura y escritura de nombre Font , y asígnele el tipo IFontDisp. No encontrará IFontDisp en la lista de tipos, pero el Editor de Bibliotecas de tipos aceptará la declaración si Ud. la escribe. 3. Añada una nueva propiedad de lectura y escritura, llámela Text, y asígnele el tipo IStrings. 4. Pulse el botón Refresh Implementation y cierre el Editor de Bibliotecas de Tipos. 5. Guarde el fichero fuente como MemoIntf.pas. 160 Capítulo 4. Automatización 6. Añada ActiveX, StdVCL y AxCtrls a la cláusula uses de la sección interface de la unidad, ya que en éstas están definidas IFont e IStrings, así como los procedimientos GetOLExx. 7. Añada MainForm a la cláusula uses de la sección implementation. 8. Reprograme los métodos de MemoIntf de forma tal que llamen a sus métodos correspondientes en TForm1 (ver Listado 4.4). Ahora ya puede compilar el programa. Ejecútelo para registrar el servidor de automatización en el registro de Windows, y salga de la aplicación. Nota Recuerde que los servidores COM externos al proceso se registran en el Registro de Windows cada vez que son ejecutados. Para registrar el servidor externo al proceso sin ejecutar la aplicación, puede ejecutar MemoSrv.exe /regserver. Para desregistrarlo, se debe ejecutar MemoSrv.exe /unregserver. El código fuente para MemoIntf se muestra en el Listado 4.4. Listado 4.4 : Aplicación MemoSrv – MemoIntf.pas unit MemoIntf; interface uses ComObj, ActiveX, AXCtrls, StdVCL, MemoSrv_TLB; type TMemoIntf = class(TAutoObject, IMemoIntf) protected function Get_Color: OLE_COLOR; safecall; procedure Set_Color(Value: OLE_COLOR); safecall ; function Get_Font: IFontDisp; safecall; function Get_Text: IStrings; safecall; procedure Set_Font(const Value: IFontDisp); safecall; procedure Set_Text(const Value: IStrings); safecall; { Protected declarations } end; implementation uses ComServ, MainForm; function TMemoIntf.Get_Color: OLE_COLOR; begin Result := Form1.GetColor; end; procedure TMemoIntf.Set_Color(Value: OLE_COLOR); begin Form1.SetColor(Value); end; function TMemoIntf.Get_Font: IFontDisp; begin GetOleFont(Form1.GetFont, Result); Servidores de automatización externos al proceso 161 end; function TMemoIntf.Get_Text: IStrings; begin GetOleStrings(Form1.GetText, Result); end; procedure TMemoIntf.Set_Font(const Value: IFontDisp); begin SetOleFont(Form1.GetFont, Value); end; procedure TMemoIntf.Set_Text(const Value: IStrings); begin SetOleStrings(Form1.GetText, Value); end; initialization TAutoObjectFactory.Create(ComServer, TMemoIntf, Class_MemoIntf, ciMultiInstance, tmApartment); end. Creando un cliente de automatización Ahora que tenemos una aplicación a automatizar, vamos a concentrarnos en desarrollar una aplicación cliente que la automatice. En la mayoría de los casos en que se desea utilizar un servidor de automatización, no se dispone del código fuente de las interfaces que proporciona el servidor. En este libro hemos desarrollado tanto el servidor como los clientes, así que el código fuente estaba disponible. Pero, ¿qué pasaría si MemoSrv hubiera sido creado por otro programador? Vamos a mostrar cómo podemos importar bibliotecas de tipos en Delphi y hacer que éste reconstruya automáticamente el código fuente necesario. Cree una aplicación nueva en Delphi, y seguidamente seleccione Project Import Type Library desde el menú principal de Delphi. Aparecerá la pantalla que se muestra en la Figura 4.9. Desplácese a través de la lista hasta encontrar MemoSrv (Version 1.0) . Si no aparece en la lista, pulse el botón Add... , y seleccione el fichero MemoSrv.exe en el directorio donde éste se encuentre. Seleccione MemoSrv (Version 1.0) y pulse OK. Delphi generará una biblioteca de importación llamada MemoSrv_TLB.pas para el servidor de automatización MemoSrv. Por defecto, el fichero será colocado en el directorio Imports del disco duro. Si ha instalado Delphi con la configuración por defecto, este directorio será C:\Archivos de programa\Borland\DelphiX\Imports, donde DelphiX representa la versión de Delphi que esté utilizando. El Listado 4.5 muestra la biblioteca de tipos importada para MemoSrv. 162 Capítulo 4. Automatización Figura 4.9: Importando una biblioteca de tipos. Listado 4.5 : Aplicación MemoCli - MemoSrv_TLB.pas unit MemoSrv_TLB; // ******************************************************************** // WARNING // ———// The types declared in this file were generated from data read from // a Type Library. If this type library is explicitly or indirectly // (via another type library referring to this type library) // re-imported, or the ‘Refresh’ command of the Type Library Editor // activated while editing the Type Library, the contents of this file // will be regenerated and all manual modifications will be lost. // ******************************************************************** // PASTLWTR : $Revision: 1.11.1.75 $ // File generated on 5/15/01 10:46:43 AM from Type Library described below. // ******************************************************************** // Type Lib: C:\Book\samples\Chap04\MemoSrv\MemoSrv.tlb // IID\LCID: {A1E420C7-F75F-11D2-B3B9-0040F67455FE}\0 // Helpfile: // HelpString: MemoSrv Library // Version: 1.0 // ******************************************************************** interface uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL; // ******************************************************************** // GUIDS declared in the TypeLibrary. Following prefixes are used: // // // // // // // // // // // // // // Servidores de automatización externos al proceso 163 // Type Libraries : LIBID_xxxx // CoClasses : CLASS_xxxx // DISPInterfaces : DIID_xxxx // Non-DISP interfaces: IID_xxxx // ******************************************************************** const LIBID_MemoSrv: TGUID = ‘{A1E420C7-F75F-11D2-B3B9-0040F67455FE}’; IID_IMemoIntf: TGUID = ‘{A1E420C8-F75F-11D2-B3B9-0040F67455FE}’; CLASS_MemoIntf: TGUID = ‘{A1E420CA-F75F-11D2-B3B9-0040F67455FE}’; type // ******************************************************************** // Forward declaration of interfaces defined in Type Library // ******************************************************************** IMemoIntf = interface; IMemoIntfDisp = dispinterface ; // ******************************************************************** // Declaration of CoClasses defined in Type Library // (NOTE: Here we map each CoClass to its Default Interface) // // // // // // // // // // // // ******************************************************************** // MemoIntf = IMemoIntf; // ******************************************************************** // // Interface: IMemoIntf // Flags: (4416) Dual OleAutomation Dispatchable // GUID: {A1E420C8-F75F-11D2-B3B9-0040F67455FE} // ******************************************************************** // IMemoIntf = interface (IDispatch) [‘{A1E420C8-F75F-11D2-B3B9-0040F67455FE}’] function Get_Color: OLE_COLOR; safecall; procedure Set_Color(Value: OLE_COLOR); safecall; function Get_Font: IFontDisp; safecall; procedure Set_Font( const Value: IFontDisp); safecall; function Get_Text: IStrings; safecall ; procedure Set_Text( const Value: IStrings); safecall; property Color: OLE_COLOR read Get_Color write Set_Color; property Font: IFontDisp read Get_Font write Set_Font; property Text: IStrings read Get_Text write Set_Text; end; // ******************************************************************** // // DispIntf: IMemoIntfDisp // Flags: (4416) Dual OleAutomation Dispatchable // GUID: {A1E420C8-F75F-11D2-B3B9-0040F67455FE} // ******************************************************************** // IMemoIntfDisp = dispinterface [‘{A1E420C8-F75F-11D2-B3B9-0040F67455FE}’] property Color: OLE_COLOR dispid 2; property Font: IFontDisp dispid 1; property Text: IStrings dispid 3; end; CoMemoIntf = class class function Create: IMemoIntf; class function CreateRemote(const MachineName: string): IMemoIntf; end; 164 Capítulo 4. Automatización implementation uses ComObj; class function CoMemoIntf.Create: IMemoIntf; begin Result := CreateComObject( CLASS_MemoIntf) as IMemoIntf; end; class function CoMemoIntf.CreateRemote(const MachineName: string): IMemoIntf; begin Result := CreateRemoteComObject(MachineName, CLASS_MemoIntf) as IMemoIntf; end; end. Añada MemoSrv_TLB a la cláusula uses del formulario principal, y compile el proyecto para asegurar que Delphi encuentra la unidad importada. Si se produce un error de compilación al intentar compilar MemoSrv_TLB , deberá comprobar sus opciones del entorno (menú Tools | Environment Options) para asegurarse de que $(Delphi)\Imports forma parte de las rutas de bibliotecas (ver Figura 4.10). Figura 4.10: Verificando que $(DELPHI)\Imports se encuentra en la lista de rutas de bibliotecas. Ahora podemos escribir el código para conectarse y desconectarse del servidor. Como el código utiliza interfaces, no hay nada nuevo aquí: procedure TForm1.btnConnectClick(Sender: TObject); begin FMemo := CoMemoIntf.Create; end; procedure Tform1.btnDisconnectClick(Sender: TObject); begin FMemo := nil; end; Servidores de automatización externos al proceso 165 Añadiremos tres botones al formulario para configurar el tipo de letra, el color y el contenido del control de edición remoto. Como hemos señalado anteriormente, Delphi proporciona el marshaling de tipos de letra, listas de cadenas e imágenes. Esto hace que el código que tenemos que escribir resulte trivial. procedure TForm1.btnSetFontClick(Sender: TObject); var FontDisp: IfontDisp; begin if Fontdialog1.Execute then begin Memo1.Font.Assign(FontDialog1.Font); GetOleFont(FontDialog1.Font, FontDisp); FMemo.Set_Font(FontDisp); end; end; GetOleFont en un procedimiento de Delphi que “convierte” un objeto TFont en una interfaz IFontDisp apropiada para atravesar las fronteras de un proceso. De forma similar, GetOleStrings produce una interfaz IStrings a partir de un objeto TStrings. El Listado 4.6 muestra el código de la aplicación cliente. Listado 4.6: Aplicación MemoCli - MainForm.pas unit MainForm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, MemoSrv_TLB; type TForm1 = class(TForm) btnSetColor: TButton; btnConnect: TButton; btnSetFont: TButton; btnSetText: TButton; btnDisconnect: TButton; btnGetColor: TButton; btnGetFont: TButton; btnGetText: TButton; Memo1: TMemo; FontDialog1: TFontDialog; ColorDialog1: TColorDialog; procedure btnConnectClick(Sender: TObject); procedure btnDisconnectClick(Sender: TObject); procedure btnSetColorClick(Sender: TObject); procedure btnSetFontClick(Sender: TObject); procedure procedure procedure procedure private btnSetTextClick(Sender: TObject); btnGetColorClick(Sender: TObject); btnGetFontClick(Sender: TObject); btnGetTextClick(Sender: TObject); 166 Capítulo 4. Automatización { Private declarations } FMemo: IMemoIntf; public { Public declarations } end; var Form1: TForm1; implementation uses ActiveX, AXCtrls, StdVCL; {$R *.DFM} procedure TForm1.btnConnectClick(Sender: TObject); begin FMemo := CoMemoIntf.Create; end; procedure TForm1.btnDisconnectClick(Sender: TObject); begin FMemo := nil; end; procedure TForm1.btnSetColorClick(Sender: TObject); begin if ColorDialog1.Execute then begin Memo1.Color := ColorDialog1.Color; FMemo.Set_Color(ColorToRGB(ColorDialog1.Color)); end; end; procedure TForm1.btnSetFontClick(Sender: TObject); var FontDisp: IFontDisp; begin if FontDialog1.Execute then begin Memo1.Font.Assign(FontDialog1.Font); GetOleFont(FontDialog1.Font, FontDisp); FMemo.Set_Font(FontDisp); end; end; procedure TForm1.btnSetTextClick(Sender: TObject); var Strings: IStrings; begin GetOleStrings(Memo1.Lines, Strings); FMemo.Set_Text(Strings); end; procedure TForm1.btnGetColorClick(Sender: TObject); begin Memo1.Color := FMemo.Get_Color; end; procedure TForm1.btnGetFontClick(Sender: TObject); var FontDisp: IFontDisp; begin FontDisp := FMemo.Get_Font; Servidores de automatización externos al proceso 167 SetOleFont(Memo1.Font, FontDisp); end; procedure TForm1.btnGetTextClick(Sender: TObject); var Strings: IStrings; begin Strings := FMemo.Get_Text; SetOleStrings(Memo1.Lines, Strings); end; end. La Figura 4.11 muestra la aplicación MemoCli conectada a MemoSrv. Figura 4.11: MemoCli controla al servidor de automatización MemoSrv. Recapitulando, hemos ejecutado los siguientes pasos para crear un cliente de automatización: • • • • Crear una nueva aplicación. Importar la biblioteca de tipos del servidor. Añadir en la cláusula uses de la aplicación cliente la unidad de importación. Realizar llamadas a los métodos del servidor de automatización. Hasta aquí, hemos cubierto las bases de la automatización. Ahora sabe cómo crear un servidor de automatización, y cómo puede controlar el servidor utilizando interfaces, disp-interfaces y variantes. En la próxima sección examinaremos las características más avanzadas de la automatización COM: los eventos y funciones de respuesta (callbacks).