12 septiembre 2008

ENVIAR MENSAJES ENTRE APLICACIONES SIN COMUNICACIONES

La forma más usual de copiar información entre dos aplicaciones de Windows es usando algún protocolo de comunicaciones, como TCP o UDP. Pero si la comunicación se va a efectuar entre dos aplicaciones que se encuentran en el mismo PC podemos enviar la información sin utilizar ningún protocolo de comunicación.

Para ello se utiliza un mensaje especial que tiene la API de Windows llamado WM_COPYDATA. Lo que permite este mensaje es copiar cualquier tipo de dato, ya sea texto o binario entre dos aplicaciones Windows tal como si se pasara por el portapapeles. De este modo la comunicación es instantánea y asíncrona.

Una aplicación práctica que se le puede dar a este método es la de dividir nuestro programa en varios ejecutables y se que envíen la información entre sí (siempre que estemos dentro el mismo ordenador).

CREANDO LA APLICACIÓN QUE ENVÍA EL MENSAJE

Lo primero que vamos a hacer es crear un nuevo proyecto y al formulario principal de la aplicación lo vamos a llamar FEnviar:


Este formulario sólo va a contener un objeto de la clase TMemo llamado Mensaje para escribir el texto a enviar y un botón para hacerlo (TButton). El proyecto lo podemos guardar con el nombre Enviar.exe.

Para poder transmitir un mensaje es necesario definir una estructura de datos llamada TCopyDataStruct:

TCopyDataStruct = packed record
dwData: DWORD; // de uso libre: para indicar por ejemplo el tipo de información a transmitir
cbData: DWORD; // el tamaño en bytes de los datos que se van a enviar
lpData: Pointer; // puntero a los datos que se van a enviar
end;

Esta estructura ya viene predefinida en la API original de Windows (en C) del siguiente modo:

typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;

El parámetro dwData se le deja libre al programador para que pueda utilizarlo a su propio criterio. Por ejemplo se podría utilizar para decirle al receptor el tipo de dato que vamos a enviar.

En el parámetro cbData debemos indicar el número de bytes que vamos a enviar y lpData es un puntero a los datos que enviamos.

Por último hacemos el evento OnClick del botón Enviar:

procedure TFPrincipal.BEnviarClick(Sender: TObject);
var
CopyDataStruct : TCopyDataStruct;
hReceptor: THandle;
Respuesta: integer;
begin
CopyDataStruct.dwData := 0; // use it to identify the message contents
CopyDataStruct.cbData := Mensaje.Width;
CopyDataStruct.lpData := PChar( Mensaje.Text );

// Comprobamos si existe el receptor

hReceptor := FindWindow( PChar( 'TFRecibir' ), PChar( 'Recibir' ) );

if hReceptor = 0 then
begin
ShowMessage( 'No he encontrado al receptor.' );
Exit;
end;

// Enviamos el mensaje y recogemos la respuesta del receptor

Respuesta := SendMessage( hReceptor, WM_COPYDATA, Integer( Handle ),
Integer( @CopyDataStruct ) ) ;

if Respuesta = 1234 then
ShowMessage( 'El mensaje ha sido recibido satisfactoriamente.' );
end;

Lo primero que hace es almacenar el mensaje en la estructura de datos y luego trata de buscar una ventana llamada Recibir de la clase TFRecibir (que vamos a crear ahora después) que va a recoger la información que se envía.

El programa comprueba si existe esta ventana antes de enviar la información con SendMessage. Como el receptor puede mandar cualquier número entero como respuesta, he establecido el número 1234 como mensaje de que todo ha ido bien.

CREANDO LA APLICACIÓN QUE RECIBE EL MENSAJE

Creamos un nuevo proyecto llamado Recibir.exe cuya ventana principal se llamara FRecibir (de la clase TFRecibir) y que sólo va a contener un objeto TMemo llamado Mensaje que va a recoger la información:


Aquí también tenemos que definir la estructura de datos TCopyDataStruct definida en el proyecto anterior. Otro registro que también vamos a definir es una estructura encargada de recibir de Windows el mensaje de tipo WM_COPYDATA:

TWMCopyData = packed record
Msg: Cardinal;
From: HWND;// Handle de la ventana que envía la información
CopyDataStruct: PCopyDataStruct; // datos enviados
Result: Longint; // usado para enviar la respuesta
end;

Ahora viene la parte especial del programa. Debemos definir un procedimiento dentro del formulario principal que recoja el mensaje de tipo WM_COPYDATA:

TFRecibir = class(TForm)
Mensaje: TMemo;
procedure WMCopyData( var Msg: TWMCopyData ); message WM_COPYDATA;
private
{ Private declarations }
public
{ Public declarations }
end;

Esta sería su implementación:

procedure TFRecibir.WMCopyData( var Msg: TWMCopyData );
begin
Mensaje.Text := PChar( Msg.CopyDataStruct.lpData );
Msg.Result := 1234; // Mensaje de respuesta de que todo ha ido bien
end;

En cuanto la aplicación Enviar.exe pulse el botón Enviar se ejecutará en la aplicación Recibir.exe automáticamente el procedimiento WMCopyData de manera instantánea:


Lo que he hecho en ese procedimiento es mandar al Memo llamado Mensaje el texto que hemos recibido.

Una cosa importante que hay que tener en cuenta es que si hay varios receptores de mensaje ejecutándose a la vez (Recibir.exe) entonces sólo lo recibirá el primero de ellos.

Como he mencionado al principio del artículo, este tipo de mensaje es muy útil si tenemos varios ejecutables dentro del mismo PC que necesitan enviarse mensajes de información rápidamente, ya sea como activación de procesos automáticos y como sincronización entre distintas aplicaciones.

Pruebas realizadas en RAD Studio 2007.

3 comentarios:

Anónimo dijo...

Hola!, buen Blog! no sabía que existía y tienes un monton de cosas interesantes.

Ánimo, no lo dejes.

Anónimo dijo...

enorabuena por el blog, delphi power!!!!

Makarren dijo...

Genial tu ayuda, me ha venido estupendamente.

Publicidad