En este tutorial, exploraremos la tubería [pipe], qué es y qué podemos hacer por él. Para hacerlo más interesante, me meto en la técnica de cómo cambiar el color del fondo y del texto de una ventana de edición.
Bajar el ejempo aquí.
Una tubería [pipe] es un conducto o vía de comunicación entre dos terminales. Puedes usar una tubería para intercambiar datos entre dos procesos diferentes, o dentro del mismo proceso. Es como un walkie-talkie. Das a la otra parte una configuración y esta parte puede usarla para comunicarse contigo.
Hay dos tipos de tuberías: anónima y con nombre. Una tubería anónima es, como lo dice el nombre, anónima: es decir, puedes usarla sin saber su nombre. Una tubería nombrada es lo opuesto: tienes que conocer su nombre antes de usarla.
También puedes clasificar las tuberías de acuerdo a su propiedad: un-sentido (one-way) o dos-sentidos (two way). En una tubería de un sentido, los datos pueden fluir sólo en una dirección: de un terminal a otro. Mientras que en una tubería de dos sentidos, los datos pueden ser intercambiados entre dos terminales.
Una tubería anónima siempre es de un sentido mientras que una tubería nombrada puede ser de un sentido o de dos sentidos. Usualmente se usa una tubería nombrada en un entorno de red donde un servidor puede conectarse a varios clientes.
En este tutorial, examinaremos con cierto detalle las tuberías anónimas. El propósito principal de una tubería anónima es ser usada como un canal de comunicación entre un proceso padre y un proceso hijo o entre procesos hijos.
La tubería anónima es realmente útil cuando tratamos con una aplicación de cónsola. Una aplicación de cónsola es un tipo de programa win32 que usa una cónsola para su entrada y su salida. Una consola es como una caja DOS. Sin embargo, una aplicación de cónsola es un programa totalmente en 32-bit. Puede usar cualquier función GUI, como otros porgramas GUI. Lo que ocurre es que tiene una cónsola para su uso.
Una aplicación de cónsola tiene tres manejadores [handles] que pueden usarse para entrada y salida. Los llamamos manejadores estándard. Hay tres de ellos: entrada estándard, salida estándar y error estándard. El manejador de entrada estándard es usado para leer/recobrar la información de la cónsola y el manejador de salida eestándar es usado para información salida/impresión para la cónsola. El manejador de error estándar es usado para reportar condiciones de error ya que su salida no puede ser redireccionada.
Una aplicación de cónsola puede recuperar estos tres manejadores llamando a la función GetStdHandle, especificando el manejador que quiere obtener. Una aplicación GUI no tiene una cónsola. Si llamas a GetStdHandle, regresará un error. Si en realidad quieres usar una consola, puedes llamar a AllocConsole para localizar una nueva cónsola. Sin embargo, no olvides llamar a FreeConsole cuando hayas hecho lo que tienes que hacer con la cónsola.
Con más frecuencia se emplea una tubería anónima para redireccionar entrada y/o salida de una aplicación de cónsola hija. Para que esto trabaje el proceso padre puede ser una aplicación de cónsola o una GUI, pero el proceso hijo debe ser una aplicación de cónsola. Como debes saber, una aplicación de cónsola usa manejadores estándard para su entrada y salida. Si queremos redireccionar entrada y/o salida de una aplicación de cónsola, podemos reemplazar su manejador con el manejador de un terminal de una tubería. Una aplicación de cónsola no sabe que está usando el manejador de un terminal de una tubería. Lo usará como un manejador estándar. Esto es un tipo de polimorfismo, como se diría en la jerga POO [OOP: Object Oriented Programming = Programación Orientada a Objetos]. Esta aproximación al problema es poderosa ya que no necesitamos modificarde ninguna manera el proceso hijo .
Otra cosa que deberías saber sobre las aplicaciones de cónsola es de donde obtiene los manejadores estándar. Cuando se crea una aplicación de cónsola, el proceso padre tiene dos posiblidades: puede crear una nueva cónsola para la hija o puede dejar que la hija herede su propia cónsola. Para que trabaje la segunda opción, el proceso padre debe ser una aplicación de cónsola, pero si es una aplicación GUI, debe llamar primero a AllocConsole para localizar una cónsola.
Comencemos a trabajar. Con el fin de crear una tubería anónima necesitas llamar a CreatePipe. CreatePipe tiene el siguiente prototipo:
CreatePipe proto pReadHandle:DWORD, \
pWriteHandle:DWORD,\
pPipeAttributes:DWORD,\
nBufferSize:DWORD
Si la llamada tiene éxito, el valor regresado es distinto de cero. Si falla, el valor regresado es cero.
Después de que la llamada tiene éxito, obtendrás dos manejadores, uno para el terminal de lectura de la tubería y otro para el terminal de escritura.
Ahora resumiremos los pasos para redireccionar la salida estándard de un programa de cónsola hijo hacia nuestro propio proceso. Nota que mi método difiere del de la referencia de la api de win32 suministrada por Borland. El método en la referencia de la api win32 asume que el proceso padre es una aplicación de cónsola y por eso el proceso hijo puede heredar los manejadores estándar de él. Pero en muchas ocasiones, necesitaremos redireccionar la salida desde una aplicación de cónsola a una GUI.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.libWinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDR_MAINMENU equ 101 ; el ID del menú principal
IDM_ASSEMBLE equ 40001.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0.data?
hInstance HINSTANCE ?
hwndEdit dd ?.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eaxWinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke Reg es terClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke D es patchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endpWndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
or startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start
El ejemplo llamará a ml.exe para ensamblar un archivo llamado test.asm y redireccionar la salida de ml.exe al control de edición en su área cliente.
Cuando el programa es cargado, registra la clase de ventana y crea la ventana principal como es usual. Lo primero que hace durante la creación de la ventana principal es crear un control de edición que será usado paera desplegar la salida de ml.exe.
Ahora la parte interesante, cambiaremos el color del texto y del fondo del control de edición. Cuando un control de edición va a pintar su área cliente, envía el mensaje WM_CTLCOLOREDIT a su padre.
wParam contiene el manejador del dispositivo de contexto que usará el control de edición para escribir su propia área cliente. Podemos aprovechar esto para cambiar las características de HDC.
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
SetTextColor cambia el color del texto a amarillo. SetTextColor cambia el color de fondo del texto a negro.Y finalmente, obtenemos el manejador a la brocha negra que queremos regresar a Windows. Con el mensaje WM_CTLCOLOREDIT, debes regresar un manejador a la brocha que Windows usará para pintar el fondo del control de edición. En nuestro ejemplo, quiero un fondo negro así que regreso a Windows un manejador a la brocha negra.
Ahora el usuario selecciona el elemento de menú Assemble, crea una tubería anónima.
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
Antes de llamar a CreatePipe, primero debemos llenar la estrcutura SECURITY_ATTRIBUTES. Nota que podemos usar NULL en el miembro lpSecurityDescriptor si nos tiene sin cuidado la seguridad. Y el miembro bInheritHandle debe ser TRUE para que los manejadores de la tubería sean heredados al proceso hijo.
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
Después de eso llamamos a CreatePipe que, si tiene éxito, llenará las variables hRead y hWrite con los manejadores a los terminales de lectura y escritura respectivamente.
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
Ahora vamos a llenar la estructura STARTUPINFO. Llamamos a GetStartupInfo para llenar la estructura STARTUPINFO con los valores por defecto del proceso padre. Debes llenar la estructura STARTUPINFO con esta llamada si quieres que tu código trabaja con win9x y NT. Después que regrese la llamada a GetStartupInfo, puedes modificar los miembros que son importantes. Copiamos el manejador del terminal de escritura de la tubería dentro de hStdOutput y hStdError ya que queremos que el proceso hijo lo use en vez de los manejadores estándar de salida/error. También queremos esconder la ventana de la cónsola del proceso hijo, así que ponemos el valor SW_HIDE dentro del miembro wShowWidow. Por último, debemos indicar que los miembros hStdOutput, hStdError y wShowWindow son válidos y deben ser usados especificando las banderas STARTF_USESHOWWINDOW y STARTF_USESTDHANDLES en el miembro dwFlags.
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
Ahora creamos el proceso hijo con la llamada a CreateProcess. Nota que el parámetro bInheritHandles debe ser establecido a TRUE para que trabaje el manejador de la tubería.
invoke CloseHandle,hWrite
Después de que creamos satisfactoriamente el proceso hijo, debemos cerrar el terminal de escritura de la tubería. Recuerda que pasamos el manejador de escritura al proceso hijo a través de la estructura STARTUPINFO. Si no cerramos el manejador de escritura de nuestro terminal, habrán dos terminales de escritura. Y la tubería no trabajará. Debemos cerrar el manejador de escritura después de CreateProcess pero antes de leer los datos del terminal de lectura.
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
Ahora estamos listos para leer los datos de la salida estándar del proceso hijo. Nos mantenemos en un bucle infinito hasta que no hayan más datos que leer desde el terminal de lectura de la tubería. Llamamos a RtlZeroMemory para llenar el buffer con ceros y luego llmamos a ReadFile, pasando el manejador de lectura de la tubería en lugar de un manejador de archivo. Nota que sólo leemos un máximo de 1023 bytes ya que necesitamos que los datos sean una cadena ASCIIZ que podemos pasar al control de edición
Cuando regresa ReadFile con los datos en el buffer, llenamos los datos dentro del control de edición. Sin embargo, aquí hay un pequeño problema. Si usamos SetWindowText para poner los datos dentro del control de edición, los nuevos datos sobreescribirán los datos existentes! Queremos que los datos se anexen al final de los datos existentes.
Para alcanzar esa meta, primero movemos la careta alfinal del texto en el control de edición enviando el mensaje EM_SETSEL con wParam==-1. Luego, anexamos los datos en ese punto enviando el mensaje EM_REPLACESEL.
invoke CloseHandle,hRead
Cuando ReadFile retgresa NULL, rompemos el
bucle y cerramos el manejador de escritura.
Iczelion's Win32 Assembly Homepage
n u M I T_o r's Programming Page
Este tutorial, original de Iczelion, ha sido traducido por: n u M I T_o r