|
En este capítulo voy a dar una breve explicación de algunas técnicas para hacer que 2 o mas aplicaciones se comuniquen entre sí, pero voy escribir sobre lo mas básico, ya que estudiar las técnicas avanzadas para la comunicación de aplicaciones, requerirían de un manual totalmente independiente, por ejemplo, hablar sobre Winsock, ActiveX, COM, etc, son temas que requieren de un estudio minucioso y profundo. En este Capítulo trabajare con cosas sencillas que nos pueden sacar de aprietos cuando no necesitamos de algo tan avanzado. ATOMO En la vida el átomo se podría decir que es lo mas simple y pequeño que existe, en la computadora o para Windows es algo parecido, “Atom” o átomo, es tan sencillamente una información ya sea de tipo “cadena” o “entero”, y a esta información se le asigna un numero ID único, el cual se emplea para acceder a dicha información, que es almacenada por Windows en una tabla que el propio Windows controla. Tenemos 2 tipos de átomos “global” y “local”, los átomos locales solo son visto por la misma aplicación, mientras que los globales pueden ser visto por otras aplicaciones. Dicho átomos globales tienen restricción, y es que solo se pueden añadir 37 átomos globales. Es decir, no podemos tener 38 átomos globales en nuestra tabla, tendría que eliminar 1 para poder insertar el otro. Para este proyecto solo necesitan 8 botones y 1 etiqueta o “label”, luego pegan en el formulario este código: Ver Código
Con este proyecto van a generar un ejecutable, que lo guardan en su disco duro, luego ejecutan su aplicación 2 veces, es decir, deben de haber dos aplicaciones del mismo proyecto arrancando al mismo tiempo. Y presionan los botones de la siguiente manera, imaginemos que ustedes tienen las dos aplicaciones abiertas, a una de ellas la llamaremos App1 y a la otra App2. A la que ustedes le hayan asignado App1 presionan el botón “Añadir Atomo Global Local”, en la aplicación App2, presionarán el botón “Buscar Atomo Global Local”, y luego presionan “Obtener Atomo Global Local”, y verán que la etiqueta muestra “HolaGlobal", es decir, indirectamente App1 se esta comunicando con App2. Ya que una variable creada con App1, esta siendo vista por App2. Los 4 botones hacen lo mismo, pero a nivel “local” cuando hablo de “local” me refiero a la aplicación, es decir, el programa que fue ejecutado. No vean “local” como a la propia computadora. ¿Cómo se logra todo esto?. Voy a explicar con detalle las funciones que se usan para los átomos “globales” y la explicación es análoga para los átomos “locales”. GlobalAddAtom Api: Declare Function GlobalAddAtom Lib "kernel32" Alias "GlobalAddAtomA" (ByVal lpString As String) As Integer Función que se encarga de añadir un átomo global a la tabla de átomos del sistema, le pasamos como único parámetro la cadena “string” que queremos almacenar. Si la función es un éxito retorna un valor distinto de 0, que es el ID que le asigna Windows a dicho átomo. Ej:
atomo = GlobalAddAtom("HolaGlobal") Label1 = "ID del nuevo atomo creado = " & atomo GlobalFindAtom Api: Declare Function GlobalFindAtom Lib "kernel32" Alias "GlobalFindAtomA" (ByVal lpString As String) As Integer Esta función se encarga de buscar un átomo, en la lista de átomos, como único parámetro le pasamos la cadena que identifica al átomo, en caso de encontrarlo, la función retorna un valor distinto de 0 el cual es el ID que Windows le asigno en su creación. Ej:
Label1 = vbNullString atomo = GlobalFindAtom("HolaGlobal") Label1 = "ID del atomo encontrado = " & atomo GlobalGetAtomName Api: Declare Function GlobalGetAtomName Lib "kernel32" Alias "GlobalGetAtomNameA" (ByVal nAtom As Integer, ByVal lpBuffer As String, ByVal nSize As Long) As Long Función encargada de tomar la cadena que se almaceno en un átomo, se le pasa como primer parámetro el ID del átomo, como segundo parámetro se le pasa la variable que almacenara dicho valor y como tercer parámetro le pasamos la cantidad de bytes que queremos recuperar. Ej: Label1 = vbNullString Dim stratomo As String stratomo = Space(255) GlobalGetAtomName atomo, stratomo, 255 Label1 = Trim(stratomo) GlobalDeleteAtom Api: Declare Function GlobalDeleteAtom Lib "kernel32" (ByVal nAtom As Integer) As Integer Función encargada de borrar un átomo de la tabla de átomos, y se le pasa como único parámetro el ID del átomo creado. La función retorna 0 si el borrado fue exitoso, en caso contrario devuelve un valor distinto de 0. Ej: Label1 = vbNullString Dim borrado As Long borrado = DeleteAtom(atomo) If borrado = 0 Then Label1 = "Atomo borrado" Else Label1 = "Error al borrar" End If Como pueden ver es muy sencillo trabajar con los átomos, esto se puede ver como la forma mas simple de comunicar a dos aplicaciones. Y creo que esa es la razón de por que Windows en la documentación oficial lo llamo “Atom”. Podrían preguntarse ¿Cómo puedo comunicar 2 aplicaciones si la aplicación App2 tiene que conocer el string o cadena a buscar?. Buena pregunta, pero depende el como veamos nosotros la palabra comunicación, con átomos ustedes pueden ver lo siguiente, imaginen 2 aplicaciones, cuando arranca Aplicacion1, dicha aplicación quiere saber si Aplicacion2 se esta ejecutando, seria fácil resolver el problema usando átomos, ¿Por qué?. Aplicacion1 tendría un bucle o Timer, ejecutando la búsqueda del átomo que será un fracaso mientras Aplicacion2 no haya sido abierta, cuando Aplicación2 sea abierta, esta registra el átomo que Aplicacion1 esta buscando, y luego cuando Aplicacion1 ejecute el timer y encuentra el átomo en ese momento Aplicacion1 sabe que Aplicacion2 esta activa. ¿Eso no es comunicación?. Para mi lo es, simple pero lo es. Las siguientes funciones son totalmente análogas a las funciones explicadas con la diferencia que se usan para átomos locales: AddAtom Api: Declare Function AddAtom Lib "kernel32" Alias "AddAtomA" (ByVal lpString As String) As Integer DeleteAtom Api: Declare Function DeleteAtom Lib "kernel32" (ByVal nAtom As Integer) As Integer FindAtom Api: Declare Function FindAtom Lib "kernel32" Alias "FindAtomA" (ByVal lpString As String) As Integer GetAtomName Api: Declare Function GetAtomName Lib "kernel32" Alias "GetAtomNameA" (ByVal nAtom As Integer, ByVal lpBuffer As String, ByVal nSize As Long) As Long InitAtomTable Api: Declare Function InitAtomTable Lib "kernel32" (ByVal nSize As Long) As Long Esta función le asigna un tamaño en bytes a la tabla de átomos local, NOTA: no funciona para la tabla global, es decir, ya mencione que solo 37 átomos globales pueden ser almacenados, pero a nivel local no hay restricción alguna, y con esta función se puede incrementar o disminuir el tamaño en bytes de dicha tabla de átomos local. PORTAPAPELES Todos nosotros hemos usado el “copiar”, “pegar” y “cortar”, inclusive ha sido algo muy práctico, la pregunta es ¿Adonde va esa información?, dicha información se va para el “portapapeles” véanlo como una caja donde pueden meter de ¡TODO!, imágenes, texto, paginas web, iconos, etc. ¿Dicho portapapeles puede comunicar a dos aplicaciones?. Por supuesto que si!, ustedes pueden estar en el bloc de notas y el texto que este ahí, pegarlo en un documento en Word, indirectamente están comunicando a dos aplicaciones. Pero existe una gran diferencia, y aunque no tiene por que ser estrictamente de esa manera, la diferencia es que el portapapeles lo controla el usuario ¿Cómo es eso?. El usuario es quien decide que copia en dicho portapapeles. ¿Eso quiere decir, que no puede usar 2 aplicaciones que se transfieran imágenes entre si usando el portapapeles?. Teóricamente y en la practica si lo puedes hacer, pero no es recomendable, por el simple hecho que el usuario pueda cambiar la información cuando quiera. Es decir, puedes tener dos aplicaciones intercambiando imágenes, y resulta que el usuario no sabe que dicha aplicaciones usan el portapapeles para intercambiar dichas imágenes, y ahora imaginen que el usuario copia un texto cualquiera, las aplicaciones pueden fallar, ya que una aplicación espera de la otra una imagen, pero al copiar el texto, dicha imagen se borra, es decir, el portapapeles es algo muy volátil, ya que es el usuario quien manipula sin darse cuenta el portapapeles. Lo que quiero que vean es que el portapapeles deben de usarlo interactuando con el usuario. Para este proyecto se necesitan los siguientes controles: 5 botones, 1 etiqueta o label, 1 picture, 2 textbox y un control CommonDialog. Luego en el formulario coloquen este código: Ver Código
En el programa pueden hacer lo siguiente: copiar una imagen o copiar un texto, nuestro ejemplo se limita solo a esto, pero se pueden colocar en el portapapeles otros formatos de información. Para copiar una imagen lo que tienen que hacer es ubicar la imagen y luego presionar el botón “Copiar Imagen”, luego le dan al botón “Recuperar imagen” y dicha imagen es mostrada en el Picture1. Para copiar el texto es el mismo procedimiento, colocan el texto a copiar luego presionan “Copiar Texto” y luego “Recuperar Texto” para mostrar el texto copiado en el portapapeles. OpenClipboard Api: Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long Función encargada de abrir el portapapeles (Clipboard), el único parámetro que se le pasa es el “hwnd” de la aplicación que tendrá el control de modificar y recuperar la información del portapapeles. EmptyClipboard Api: Declare Function EmptyClipboard Lib "user32" () As Long Función que se encarga de limpiar el portapapeles, esta función debe ser ejecutada inmediatamente después de OpenClipboard, para así darle el control del portapapeles al “hwdn” de la aplicación que ejecuto OpenClipboard. CloseClipboard Api: Declare Function CloseClipboard Lib "user32" () As Long Función que cierra el portapapeles. NOTA: Antes de explicar las funciones que vienen a continuación, vamos tocar el siguiente tema que son los “formatos”, que quiero decir con esto, todos nos daremos cuenta que prácticamente podemos pegar de todo en el portapapeles, podemos pegar una imagen, un texto, una pagina web, etc. Pero esto tiene un fondo mas abstracto, por ejemplo, si nosotros tenemos Corel Draw o Photoshop, y estamos manipulando una imagen, dicha imagen en el programa es un objeto, con propiedades propias de corel draw o photoshop, es decir, tamaño, nombre, efectos, etc. Pero la pregunta seria ¿Por qué si yo copio en corel y pego en el mismo corel heredo todas esas propiedades, y en paint no?. simple respuesta Paint no esta adaptado a ese formato de imagen, pero fíjense que se pega la imagen, pero la imagen como información en bytes nada mas. Todos los extras que corel coloca no lo pega. También por ejemplo, cuando copiamos una pagina WEB, y la pegan en Word, es muy distinto al pegado en el bloc de notas. ¿Por qué en un programa copia un formato y en el otro no lo pega igual?. Depende de cómo fue diseñado el programa. Todo en el portapapeles se identifican con un formato. Esto le sirve a los programas de indicativo de que es lo que hay adentro. Por ejemplo en TextBox no pueden pegar una imagen pero si un texto. ¿Cómo sabe la maquina que hay en el portapapeles?. Eso la sabe gracias a los formatos. Existen formatos personalizados, que pueden usar los programas para copiar y pegar sus propios objetos. Como existen formatos universales, es decir, que pueden ser utilizados por cualquier programa. Como siempre los formatos son constantes y su uso lo veremos a continuación, pero primero muestro algunas de las constantes del sistema que se usan para especificar los formatos: Private Const CF_BITMAP = 2 ‘ Se utiliza para los bitmap. Private Const CF_DIB = 8 ‘ Es un objeto que presenta la estructura BITMAPINFO Private Const CF_DIBV5 = 17 ‘ Objeto que presenta la estructura BITMAP5VHEADER solo usado en NT. Private Const CF_DIF = 5 ‘ Software Arts' Data Interchange Format. Private Const CF_DSPBITMAP = &H82 ‘ Se refiere a un bitmap pero asociado a un formato privado. Private Const CF_DSPENHMETAFILE = &H8E ‘ Se refiere a un METAFILE pero asociado a un formato privado. Private Const CF_DSPMETAFILEPICT = &H83 ‘ Metafile-picture, asociado a un formato privado. Private Const CF_DSPTEXT = &H81 ‘ Texto asociado a un formato privado. Private Const CF_ENHMETAFILE = 14 ´ Nos referimos a un archivo METAFILE. Private Const CF_HDROP = 15 ‘ Nos referimos a un manejador del tipo HDROP que identifica a una lista de archivo. Private Const CF_METAFILEPICT = 3 ‘ Objeto METAFILEPICT. Private Const CF_OEMTEXT = 7 ‘ Formato de texto que contiene caracteres OEM. Private Const CF_PALETTE = 9 ‘ Se especifica un manejador de alguna paleta de colores. Private Const CF_RIFF = 11 ‘ Representa información de audio, que es mucho mas compleja que CF_WAVE. Private Const CF_SYLK = 4 ‘Microsoft Symbolic Link (SYLK) format Private Const CF_TEXT = 1 ‘ Texto en formato ANSI. Private Const CF_WAVE = 12 ‘ Representa información de audio. Private Const CF_TIFF = 6 ‘ Tagged-image file format. Private Const CF_UNICODETEXT = 13’ Texto en formato unicode. En esta manual no vamos a trabajar con formatos privados, pero pueden dirigirse a la documentación oficial para averiguar mas sobre el asunto en caso de que lo necesiten, aquí trabajaremos con los que a mi parecer son los mas usados que son el texto y la imagen. SetClipboardData Api: Declare Function SetClipboardData Lib "user32" (ByVal wFormat As Long, ByVal hMem As Long) As Long Esta función nos sirve para pegar una información en el portapapeles, como primer parámetro se le pasa el formato de la información que vamos a pegar. Como segundo parámetro le pasamos la información en memoria del objeto en cuestión. Veamos el ejemplo: Dim hBitmap As Long hBitmap = LoadImage(App.hInstance, Text1.Text, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE) ‘ Cargo el objeto. If hBitmap = 0 Then MsgBox "Error al cargar la imagen" Exit Sub End If OpenClipboard Me.hwnd ‘ Abro el portapapeles EmptyClipboard’ Vacio el portapapeles r = SetClipboardData(CF_BITMAP, hBitmap)’ Pego la imagen especificando CF_BITMAP. CloseClipboard’ Cierro el portapapeles. IsClipboardFormatAvailable Api: Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long Función que determina si el portapapeles contiene una información con un formato en específico. Como único parámetro le pasamos el formato a consultar. Si el formato esta disponible entonces retorna un valor distinto de 0, en caso de haber otro formato retornara 0. GetClipboardData Api: Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long Función que sirve para recuperar la información o el contenido almacenado en el portapapeles. Le pasamos como único parámetro el formato de la información. Ejemplo:
Dim dc As Long Dim bitmap As Long If IsClipboardFormatAvailable(CF_BITMAP) = 0 Then ‘ Verificamos si la información que hay en el portapapeles es una imagen. MsgBox "No hay una imagen en el portapapeles" Else OpenClipboard Me.hwnd bitmap = GetClipboardData(CF_BITMAP) ‘ Almacenamos la “data” o información en la variable “bitmap” dc = CreateCompatibleDC(Picture1.hdc) SelectObject dc, bitmap BitBlt Picture1.hdc, 0, 0, Picture1.Width \ 15, Picture1.Height \ 15, dc, 0, 0, vbSrcCopy DeleteDC dc CloseClipboard End If Dense cuenta que para copiar el texto que se encontraba en el textbox use un mensaje que se puede utilizar en algunos controles, como lo es WM_COPY. Ejemplo: SendMessage Text2.hwnd, WM_COPY, 0, 0 ‘ Copia el texto que se encuentra en el textbox en el portapapeles. GetOpenClipboardWindow Api: Declare Function GetOpenClipboardWindow Lib "user32" () As Long Función que retorna el “handle” de la ventana que posee el control actual del portapapeles. Recuerden que cuando usan OpenClipboard, la aplicación que la invoca es como el usuario actual del portapapeles, con esta función sabemos que aplicación controla el portapapeles en un determinado momento. Como pueden ver, esto es algo que parece muy sencillo pero puede resultar algo muy complejo, aquí presento un vistazo general al portapapeles, pero el funcionamiento completo del portapapeles puede ser un trabajo arduo y duro. En la documentación oficial podrán encontrar mucha mas información, acerca de Formatos Múltiples, y el uso de formatos privados. MAILSLOT Hemos visto maneras y formas de comunicarnos en una maquina local. Ciertamente la curiosidad estará en si es posible comunicarme con otras maquinas en red. En realidad hay muchas maneras de comunicarse con maquinas en una red, lo mas optimo seria usar WinSock, dicha tecnología provee de funciones y herramientas que optimizan y aseguran el envió de información, esto implica que aprender dicha tecnología no es tan fácil y hay que estar familiarizado con las terminologías de redes. En este manual no trabajaremos con WinSock ya que esa tecnología requieren de un manual aparte por lo largo y lo complejo que puede resultar. Habrá momentos donde necesitemos comunicar 2 computadoras para hacer algún proceso sencillo o simple. Windows provee de varias herramientas de las cuales estudiaremos el MailSlot. Esta técnica nos provee de herramientas relativamente simples para la comunicación entre dos maquinas. Un MailSlot es como un archivo en memoria en donde se almacenan los mensajes que la computadora recibe, inclusive van a ver en el código que se manipula como un archivo, por eso es importante que hayan leído el Capítulo que habla sobre archivos. Como un archivo en memoria podemos leer y escribir en él, el proceso de comunicación lo hace Windows a través de las funciones Api’s. Así que empecemos con los proyectos, para este ejemplo se necesitan 2 proyectos de VB, uno que va ser el cliente y el otro que será el servidor, a nivel de diseño son iguales y a nivel de código son parecidos excepto por unas 3 líneas. Para cada proyecto es necesario una etiqueta o “label”, un botón, un TextBox, un listbox, y en la aplicación del servidor es necesario un control timer. En el proyecto cliente colocan este código: Ver Código
Para arrancar estos programas tienen dos opciones, si tienen los proyectos abiertos pues ejecútenlos, o pueden compilar y ejecutar los respectivos .exe, haga como lo hagan abran los dos programas, en el textbox coloquen un texto cualquiera y luego denle al botón “Enviar” si estaban en el cliente el mensaje será recibido por el Servidor y viceversa, dicho mensaje lo coloco en el ListBox. Ustedes pueden preguntar ¿Pero estos programas no están red, sino local?. Tienen toda la razón, pero el programa fue diseñado para que funcione de esa manera, cuando explique las funciones involucradas en el proyecto, explicare como hacerlo compatible a una red. Y quedará en ustedes hacer la prueba. Como mencione el MailSlot es como un archivo, y como archivo hay que crearlo: CreateMailslot Api: Declare Function CreateMailslot Lib "kernel32" Alias "CreateMailslotA" (ByVal lpName As String, ByVal nMaxMessageSize As Long, ByVal lReadTimeout As Long, lpSecurityAttributes As Long) As Long Api: Declare Function CreateMailslot Lib "kernel32" Alias "CreateMailslotA" (ByVal lpName As String, ByVal nMaxMessageSize As Long, ByVal lReadTimeout As Long, lpSecurityAttributes As SECURITY_ATTRIBUTES) As Long Como recordarna de otras funciones mostradas mas arriba, en nuestro proyecto usamos la primera donde no es necesario pasarle la estructura SECURITY_ATTRIBUTES, ya que dicha estructura esta diseñada para usarla en NT. Esta función se utiliza para crear un MailSlot. Como primer parametro “lpName” le pasamos la ubicación o direccion “virtual” de nuestro MailSlot. Pueden escribirse de la manera siguiente: "\\.\mailslot\cliente" ‘ Asi creas un mailslot local. "\\NombreComputadora\mailslot\cliente" ‘ Asi creas un mailslot remoto. Puedes usar una jerarquia, por ejemplo: "\\NombreComputadora\mailslot\carpeta1\carpeta2\cliente" "\\.\mailslot\carpeta1\carpeta2\cliente" Como segundo parámetro “nMaxMessageSize”, le pasamos el tamaño máximo en bytes, de los mensajes que se abran de almacenar en el MailSlot. Si especificamos 0, estamos diciendo que puede aceptar cualquier tamaño. Como tercer parámetro “lReadTimeout” especificamos el tiempo de espera de un mensaje, especificado en milisegundos. Al colocar MAILSLOT_WAIT_FOREVER, le especificamos que espere por siempre, hasta que sea cerrado el MailSlot. Y el ultimo parámetro no es utilizado y se le pasa 0. Ejemplo: hmail = CreateMailslot("\\.\mailslot\cliente", 0, MAILSLOT_WAIT_FOREVER, ByVal 0&) Esta línea ubicada en Form_Load, crea un MailSlot, que acepta cualquier mensaje de cualquier tamaño, y espera por siempre por dicho mensajes. Luego para cerrar un MailSlot usamos la función CloseHandle: CloseHandle hmail Es muy importante que cierren los objetos que crean, recuerden que estos objetos no los controla VB por lo que si cierran el programa sin borrar los objetos ellos quedaran en memoria. ¿Como enviar informacion? Enviar información es exactamente igual que almacenar información en un archivo: hfile = CreateFile("\\.\mailslot\servidor", GENERIC_WRITE, FILE_SHARE_READ, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) ‘ Creo un archivo, en el nombre coloco la direccion del MailSlot al cual le quiero escribir o enviar un mensaje. If hfile = INVALID_HANDLE_VALUE Then MsgBox "error al cargar el archivo" End If . . . WriteFile hfile, ByVal espacio1, Len(s1), ret, ByVal 0& ‘ Escribo en el MailSlot, aqui Windows se encarga de enviar a través de la red nuestra información. Pueden ver que el envió es muy sencillo y no se necesita de ninguna función especial, sino las vistas cuando explicamos la sección de archivos. ¿Como recibimos información? Para esto si tenemos que usar una función nueva: GetMailslotInfo Api: Declare Function GetMailslotInfo Lib "kernel32" (ByVal hMailslot As Long, lpMaxMessageSize As Long, lpNextSize As Long, lpMessageCount As Long, lpReadTimeout As Long) As Long Esta función es utilizada para poder recuperar los mensajes localizados en un MailSlot. Como primer parámetro le pasamos el “handle” del Mailslot del cual queremos extraer dichos mensajes, como segundo parámetro tenemos el máximo tamaño permitido para almacenar los mensajes, como tercer parámetro “lpNextSize” tenemos el tamaño en bytes del próximo mensaje a recuperar. Y como último parámetro tenemos la cantidad de tiempo en milisegundos, que la operación de lectura puede esperar por un mensaje. Vayamos al ejemplo: hResult = GetMailslotInfo(hmail, ByVal 0&, nsize, mcount, ByVal 0&) ‘ Nos preparamos a recibir el próximo mensaje. If hResult = 0 Then Label1 = "MALO" Exit Sub End If If nsize = MAILSLOT_NO_MESSAGE Then ‘ Si nsize retorna esta constante, implica que no hay mensajes en la cola Label1 = "No hay mensajes" Exit Sub End If espacio = GlobalAlloc(GMEM_FIXED Or GMEM_ZEROINIT, nsize)’ Preparamos la memoria en donde almacenaremos la información. ReadFile hmail, ByVal espacio, nsize, ret, ByVal 0’ Leemos la informacion como si de un archivo se tratara. s2 = String$(ret, 0) CopyMemory ByVal s2, ByVal espacio, ret ‘ Preparamos la información para ser mostrada. Label1 = "Hay un mensaje" List1.AddItem s2 ‘ Mostramos el contenido. Todo esto lo pongo en un timer, con el sentido de que la aplicación puede recibir algún mensaje en cualquier momento. Como pueden ver, es relativamente sencillo trabajar con MailSlot, ya que se trata prácticamente de conocer mas las funciones de archivos que del propio MailSlot, si se dan cuenta solo usamos 2 funciones nuevas. De todas maneras en la documentación MSDN pueden encontrar información mas detallada de esta tecnología.
|