DllStructCreate, WM_COMMAND, WM_NOTIFY,...

Tus preguntas. Algoritmos o Grupos de Comandos formando Programas Escripts.
Responder
jamaro
Hacker del Foro
Mensajes: 253
Registrado: 03 Nov 2010, 23:04

DllStructCreate, WM_COMMAND, WM_NOTIFY,...

Mensaje por jamaro »

Inicio este hilo a raíz de la pregunta que hice en otro hilo (de otro tema)
http://www.emesn.com/autoitforum/viewto ... 059#p13053

Ximorro ha contestado lo siguiente:
No sé cómo usas DllStructCreate en una función de gestión de mensaje, supongo que es para desempaquetar las dos palabras de wparam.
En principio, como su nombre indica, DllStructCreate es para crear una estructura de tipo de datos Struct de C para pasar a una DLL. Una estructura es un conjunto ordenado de varias variables (campos) que pueden ser de diferente tipo (puedes tener un número entero sin signo, un real y un carácter, por ejemplo). Los tipos son los de C, que son mucho más cercanos a la máquina que en AutoIt, por eso son más específicos.

Aunque está pensado para pasar parámetros a DLL que usan estructuras, se pueden hacer algunos trucos con ellos. Por ejemplo en una función de mensaje como la que pones wparam suele ser dos palabras de 16 bits empaquetadas en el mismo entero de 32 bits. Por ejemplo en el mensaje WM_COMMAND la primera palabra (posición alta) suele ser el código del submensaje, y la segunda (la baja) el identificador del control.

Para extraerlos sí hay otra manera, de hecho yo lo hago de otra manera: con operaciones booleanas. Por ejemplo para extraer la parte baja ponemos a cero los 16bits altos y así nos quedamos con los bajos:
$iIDFrom = BitAND($wParam, 0x0000FFFF)
(yo pongo 0xFFFF pero con los ceros se ve más claro dónde hace ceros el AND binario)
Para tomar la parte alta se hace por ejemplo un desplazamiento de 16 bits (en los desplazamientos se pierden los bits, a diferencia de las rotaciones, con lo que nos deshacemos de la palabra baja y ponemos la alta en su lugar:
$iCode = BitShift($wParam, 16)

Otra forma de hacer eso es usando una DLLStruct, yo creo que es más sencillo lo anterior, pero supongo que lo que tienes es una estructura de dos enteros de 16 bits (¿de qué tipo son? ¿short?), al rellenar esa estructura con el entero de 32 bits (wparam) puedes tomar cada una de sus partes con un DllStructGetData. Realmente ese número de 32 bits no es un número directamente, sino que es la unión de dos números de 16 bits, con la estructura se especifica esa, eeeh, estructura, valga la redundancia ;-), y luego se extraen los campos individuales con el GetData

Espero no haber liado más la cosa...

En su momento leí esta explicación, de la que me guardé incluso el enlace:

Código: Seleccionar todo

Explicación obtenida de http://www.mirrorkarpoffespanishtutor.comxa.com/manuales/asm_win32/tut19_es.html
	WM_NOTIFY
		wParam == ID del control, nada garantiza que este valor sea el único, así que nosotros no lo usamos.
					Es mejor usar hwndFrom o el miembro IDFrom de la estructura NMHDR a la que apunta lParam.
		lParam ==   Puntero a la estructura NMHDR. Algunos controles pueden pasar un puntero más largo pero
					debe tener una estructura NMHDR como primer miembro.
                    Es decir, cuando tenemos lParam, podemos estar seguros que apunta a una estructura NMHDR.

		Ahora examinaremos la estructura NMHDR.

		NMHDR struct DWORD
			hwndFrom    DWORD ?
			idFrom      DWORD ?
			code        DWORD ?
		NMHDR ends

		hwndFrom es el manejador de la ventana que envía este mensaje WM_NOTIFY.
		idFrom es el ID del control que envía este mensaje.
		code es el mensaje actual que el control quiere enviar a su ventana padre.

Desde que empecé con Autoit, he utilizado (copiado - pegado - adaptado) en varios de mis programitas funciones "personalizadas" de My_WM_COMMAND, My_WM_NOTIFY y My_WM_GETMINMAXINFO, ; bien para conocer la "reacción" de ciertos controles, bien para asegurar el tamaño mínimo de una ventana arrastrando los bordes, ... pero sin conocer exactamente su funcionamiento y el por qué de su uso.

Aunque con la explicación de Ximorro entiendo un poco mejor cómo obtener los datos, sigo sin saber quizás lo más importante: la palabra estructura ¿es esta la única manera en la que los mensajes de windows (GUIRegisterMsg) se presentan?

De hecho, al leer el mensaje de respuesta de Ximorro, he visto que en algún programa he utilizado algo como lo que propone (ver más abajo el ejemplo de My_WM_COMMAND) y sí parece más sencillo que DllStructCreate.

Por cierto ¿dónde se puede consultar un "listado" de todos los posibles "eventos" WM_NOTIFY, WM_COMMAND, WM_GETMIMAXINFO,...?

Seguiré preguntando.... :-)

----------------------------------

Pongo algunos de los ejemplos que he utilizado (copiado - pegado - adaptado) hasta la fecha:

EJEMPLO PARA "REACCIONAR" TRAS UN CLICK, DOBLE CLIC EN UN LISTVIEW (u otro control)

Código: Seleccionar todo

Func My_WM_Notify_Events($hWnd, $MsgID, $wParam, $lParam)
     Local $tagNMHDR, $event, $hwndFrom, $code,$idFrom
     $tagNMHDR = DllStructCreate("int;int;int", $lParam)	; Crea la estructura (hwndFrom, idFrom, code) con los datos de $lParam
     If @error Then Return

    $hwndFrom = DllStructGetData($tagNMHDR, 1)			; Asigna el primer valor de la estructura. Id del control HWnd($hwndFrom)
	$idFrom = DllStructGetData($tagNMHDR, 2)			; Asigna el segundo valor de la estructura. Id del control GUICtrlGetHandle($idFrom)
	$code = DllStructGetData($tagNMHDR, 3)				; Asigna el tercer valor de la estructura. Code


     Select
		Case $idFrom = $lw_direcciones_origen
			Select
				Case $code = $NM_DBLCLK
					cw("Evento: "& $code & " en Ventana: " & $hWnd & " Control:" & $idFrom)
					;msgbox(0,"",GuiCtrlRead(GuiCtrlRead($lw_direcciones_origen),1))
					GUI_ConfiguraEstaciones_Origen_a_Destino()
			EndSelect
		Case $idFrom = $lw_direcciones_destino
			Select
				Case $code = $NM_DBLCLK
					cw("Evento: "& $code & " en Ventana: " & $hWnd & " Control:" & $idFrom)
					;msgbox(0,"",GuiCtrlRead(GuiCtrlRead($lw_direcciones_destino),1))
					GUI_ConfiguraEstaciones_Destino_a_Origen()
			EndSelect
	EndSelect

	$tagNMHDR=0
	$hwndFrom=0
	$idFrom=0
	$code=0
EndFunc
EJEMPLO PARA "REACCIONAR" TRAS UNA MODIFICACIÓN DE UN COMBOBOX

Código: Seleccionar todo

Func My_WM_COMMAND($hWnd, $imsg, $iwParam, $ilParam)
    Local $setHK = False
    Local $nID = BitAND($iwParam, 0x0000FFFF)		; LoWord - this gives the control which sent the message
	Local $nNotifyCode = BitShift($iwParam, 16)		; HiWord - this gives the message that was sent
    Local $hCtrl = $ilParam

    If $nNotifyCode = $EN_CHANGE Then
        If $hCtrl = GUICtrlGetHandle($I_Filtro) Then	; Al cambiar el control $I_Filtro
			sleep(800)	; Retraso para evitar que al escribir muy rápido en el cuadro de texto no de tiempo a actualizar bien la lista_origen
			if $Viene_de_GUI_ConfiguraEstaciones_MostrarServicio=0 Then	; Se ejecuta sólo si el cambio de la casilla no viene de GUI_ConfiguraEstaciones_MostrarServicio()
				if $Relleno_lw_direcciones_origen=1 then GUI_ConfiguraEstaciones_Filtrar()	; Sólo si $lw_direcciones_origen ya se ha rellenado
			EndIf
        EndIf

    EndIf
    Return $GUI_RUNDEFMSG
EndFunc  ;==>My_WM_COMMAND
EJEMPLO PARA AJUSTAR EL TAMAÑO MÁXIMO DE UNA VENTANA (AL CAMBIAR EL TAMAÑO CON EL ARRASTRE DE LOS BORDES)

Código: Seleccionar todo

Func My_WM_GETMINMAXINFO($hWnd, $Msg, $wParam, $lParam)	; Propuesto por Ximorro (foro Autoit.es) http://www.emesn.com/autoitforum/viewtopic.php?f=4&t=2567&p=10504#p10503
	; Función para asegurar un tamaño mínimo de la ventana cuando se cambia el tamaño mediante arrastre de bordes de ventana

	if $hWnd = $GUI_Principal Then							; Si el mensaje viene de la ventana $GUI_Principal
		#cs ; Sintaxis de la estrctura de tagMINMAXINFO
		typedef struct tagMINMAXINFO {
		  POINT ptReserved;
		  POINT ptMaxSize;
		  POINT ptMaxPosition;
		  POINT ptMinTrackSize;
		  POINT ptMaxTrackSize;
		} MINMAXINFO, *PMINMAXINFO, *LPMINMAXINFO;
		#ce


	   Local $tMINMAXINFO = DllStructCreate("int[2];" & _	; La primera está reservada
			 "int MaxSize[2];int MaxPosition[2];int MinTrackSize[2];int MaxTrackSize[2]", $lParam) ; Crea la estructura con los datos de $lParam. Como los datos que tiene MINMAXINFO son tipo POIN, en este caso los manejamos con matriz de dos elementos.
	   DllStructSetData($tMINMAXINFO, "MinTrackSize", 425, 1) ; Asigna el mínimo ancho (en el primer miembro de MinTrackSize), empezando por 1.
	   DllStructSetData($tMINMAXINFO, "MinTrackSize", 365, 2) ; Asigna el mínimo alto (en el segundo miembro de MinTrackSize), empezando por 1.
	   Return 0 ;No mandamos el mensaje a Windows, ya lo hemos procesado nosotros
	   ; Return $GUI_RUNDEFMSG (variable definida en GUIConstantsEx.au3) ; Return $GUI_RUNDEFMSG en lugar de Return 0, si quisiéramos que el mensaje siguiera su curso
	EndIf
EndFunc
CONTINUACIÓN DEL ANTERIOR, PARA QUE SE AJUSTE EL TAMAÑO DE STATUSBAR AL CAMBIAR EL TAMAÑO DE LA VENTANA

Código: Seleccionar todo

Func MY_WM_SIZE($hWnd, $iMsg, $iwParam, $ilParam)
    _GUICtrlStatusBar_Resize($BarraEstado_GUI_Principal)			; Ajusta el tamaño de la barra de estado
	;_GUICtrlListView_SetColumnWidth($lw_principal, 4, $LVSCW_AUTOSIZE )	; Ajusta el ancho de la columnaN (empezando por 0)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>MY_WM_SIZE
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: DllStructCreate, WM_COMMAND, WM_NOTIFY,...

Mensaje por Ximorro »

No, los mensajes no tienen que ver necesariamente con las estructuras. Una estructura no es más que un tipo de datos complejo, como una matriz. Puede haber mensajes que tengan que utilizar arrays y otros que no, de la misma manera habrá mensajes que usarán estructuras para organizar sus datos y otros que no.
La palabra estructura es como la palabra array, es un tipo de datos que agrupa datos de tipo básico (enteros, reales, etc.) llamados campos, a los que opcionalmente le puedes dar nombre, si no se acceden por posición.

En WM_COMMAND lo de la estructura se puede utilizar como truco para extraer las dos palabras de 16 bits del número de 32 bits que viene en wparam. Otra manera es esa con BitShift y BitAnd, que como ves ya no usa estructuras para nada.

En lparam de WM_NOTIFY sí viene un puntero a una estructura de 3 enteros (en algunos casos, porque WM_NOTIFY, igual que WM_COMMAND, es una especia de mensaje de mensajes y dependiendo del tipo de "submensaje" los parámetros pueden cambiar.

WM_GETMINMAXINFO es un buen ejemplo porque sí hace uso extensivo de una estructura para organizar sus datos, y respondiendo a tu otra pregunta, ¿dónde averiguo esas cosas? pues en la MSDN.
Para empezar tienes un listado de bastantes mensajes en la ayuda de AutoIt, en el apéndice "Códigos de Mensajes de Windows". Como tengo bastante menos idea de lo que crees cuando me ha hecho falta alguno me miro la lista a ver si por nombre es lo que quiero.
Entonces busco en la MSDN, por ejemplo la ayuda de este mensaje está aquí:
http://msdn.microsoft.com/en-us/library ... 85%29.aspx

Ahí dice que en lparam viene un puntero a la estructura MINMAXINFO, entrando en el enlace que pone ahí mismo ves esto:
http://msdn.microsoft.com/en-us/library ... 85%29.aspx

donde se ve que esa estructura está formada por otras estructuras de tipo POINT (sí, simplifiqué un poco las cosas, los campos de las estructuras pueden ser otras tipos básicos, otras estructuras, matrices, etc...).
Se puede ver siguiendo el enlace que POINT es una estructura con dos enteros, que yo agrupé en arrays de dos elementos int, a los que di nombre según los campos de la estructura, así en el DllStructSetData puedo usar ese nombre (como son arrays también uso el cuarto parámetro para indicar si es la X-primer int o la Y-segundo int, del punto)

No es tema fácil, aunque no lo creas yo lo uso de forma básica y mis dolores de cabeza me dio. La MSDN suele ser de bastante ayuda, aunque con "mensajes de mensajes" como WM_NOTIFY y WM_COMMAND la cosa se complica aún más. Para el WM_COMMAND que gestiona los eventos de un Edit es fantástico el ejemplo de la ayuda de AutoIt para _GUICtrlEdit_Create. Si no normalmente esas cosas mejor se buscan desde el propio control, por ejemplo aquí están esas notificaciones en la MSDN:
http://msdn.microsoft.com/en-us/library ... 85%29.aspx

Pero buena suerte si te metes en estos temas en profundidad. Espero que nos enseñes muchas cosas ;-)
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
jamaro
Hacker del Foro
Mensajes: 253
Registrado: 03 Nov 2010, 23:04

Re: DllStructCreate, WM_COMMAND, WM_NOTIFY,...

Mensaje por jamaro »

Muchas gracias Ximorro , algo voy entendiendo.

En principio, si sólo necesitamos "leer" los datos de un mensaje, lo más sencillo parece que es el uso de BitShift($wParam, 16) y BitAND($wParam, 0x0000FFFF). Por ejemplo, para reaccionar al entrar en un control input y cambiar su color de fondo (he hecho esta prueba)

Código: Seleccionar todo

Func My_WM_COMMAND($hWnd, $Msg, $wParam, $lParam)
	Local $setHK = False ;<------------------------------------------------------------------------------------ Arrastro de un ejemplo y no sé para qué sirve
	Local $nID = BitAND($wParam, 0x0000FFFF) ; LoWord - this gives the control which sent the message
	Local $nNotifyCode = BitShift($wParam, 16) ; HiWord - this gives the message that was sent
	Local $hCtrl = $lParam

	If $nNotifyCode = $EN_SETFOCUS Then
		GUICtrlSetBkColor($nID,0xAAABCC)
	EndIf

	If $nNotifyCode = $EN_KILLFOCUS Then
		GUICtrlSetBkColor($nID,0xFFFFFF)
	EndIf

	Return $GUI_RUNDEFMSG

EndFunc
Y si necesitamos "decirle" algo a Windows, como el tema del tamaño máximo de una ventana, entonces es necesario crear una estructura para asignar los datos con DllStructSetData.

¿Voy entendiendo el uso?
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: DllStructCreate, WM_COMMAND, WM_NOTIFY,...

Mensaje por Ximorro »

Sí, pero tampoco es que haya que usar necesariamente estructuras para pasarle datos al mensaje. Eso depende del propio mensaje y hay que mirar la documentación a ver qué requiere, por ejemplo WM_GETMINMAXINFO requiere una estructura porque como son muchos datos así lo han montado, pero por ejemplo el mensaje LVM_SETCOLUMNWIDTH que se puede enviar a un ListView (en AutoIt con GUICtrlSendMsg) recibe en wparam el índice de la columna y en lparam el tamaño en píxeles, es decir, cada parámetro es un número entero normal y corriente, sin estructuras.
Fíjate en la documentación de WM_GETMINMAXINFO que enlazaba antes, donde te dice que hay que pasarle una estructura, en cambio mira la de LVM_SETCOLUMNWIDTH:
http://msdn.microsoft.com/en-us/library ... 85%29.aspx
Como ves en Parameters a ese mensaje se le pasan datos que son simplemente números, sin estructuras ni matrices ni nada complicado.

Así que cuando veas que una llamada a una DLL o un mensaje hace referencia a un Struct, tendrás que usar un DllStructCreate para montar esos datos.
Para cosas personales también se puede usar para organizarse la información, aunque no esté pensado para eso (sólo hay que ver el nombre de estas funciones, DLL... ;-) )
Yo no lo uso porque no son tan cómodos, como en AutoIt las matrices pueden contener diferentes tipos de datos uso matrices heterogéneas y accedo a los campos por posición. Con las estructuras tienes la posibilidad de dar nombre a esos campos, lo que está bien, pero como digo no las veo tan cómodas de usar (es cosa de AutoIT porque en realidad no tiene tipo de datos "estructura" y hay que usar esas funciones de DLL)

Una nota al margen, por ampliar. Los famosos objetos de la programación orientada a objetos son una ampliación (grande, eso sí) de las estructuras. Los objetos se inventaron a partir de tipos de datos tipo estructuras en las que se les ocurrió añadir además de los datos las funciones dentro del propio objeto. Luego se crearon mecanismos de herencia simple, privacidad de datos, etc. y luego la cosa se fue complicando ;-)
(Quizás debiera haber hablado de clases en vez de objetos, pero vaya, tampoco hace falta afinar)

Nota: por cierto, tu ejemplo está bien pero en un programa real tienes que comprobar que en $nID tienes el Input (o al menos un control que pueda enviar los "submensajes" EN_KILLFOCUS y EN_SETFOCUS y cambiar el color de fondo).
Piensa que todos los controles, menús y otras cosas envían mensajes WM_COMMAND casi constantemente. Primero hay que mirar qué control es, entonces puedes mirar los submensajes apropiados para ese control.
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Responder