| Artículos | 01 JUL 2001

¿Punteros en Visual Basic? (y II)

Tags: Histórico
José M. Alarcón.
En la primera parte de este artículo hemos estudiado lo más básico respecto al manejo de punteros en Visual Basic. Ahora pasaremos a tratar temas más complicados, como el manejo de matrices directamente en memoria, que nos reportarán todavía muchas más ventajas y ganancias de velocidad.

Hemos visto que a la hora de manejar cadenas de texto podemos ganar mucha velocidad recurriendo a los punteros. Si se trata de manipular grandes cantidades de datos abstractos en memoria (como por ejemplo gráficos o archivos binarios) las mejoras son realmente espectaculares, llegando a sobrepasar en un factor de más de 100 a los métodos nativos de VB.
Para poder hacer uso de estas técnicas avanzadas se necesita aprender a manejar matrices directamente en la memoria, y para ello antes hay que comprender cómo se almacena la información en la memoria del sistema y cómo podemos acceder a ella.

Doble indirección en matrices
Al igual que ocurre con las cadenas de texto, una matriz en Visual Basic también está dotada de un tipo particular de doble indirección. Lo que realmente se guarda en una variable de tipo matriz no es un puntero a los datos de la misma, sino que se trata de un puntero a una estructura especial llamada SAFEARRAY, heredada directamente de COM. En ésta se guarda información sobre la matriz, como sus dimensiones, el tamaño de sus elementos o la dirección de memoria donde se almacenan sus elementos.
Una estructura SAFEARRAY genérica se define de la siguiente manera:

Type SAFEARRAY
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
End Type

y va siempre seguida en memoria por tantas estructuras del tipo SAFEARRAYBOUND como dimensiones tenga la matriz, hasta un máximo de 60. Estas estructuras se definen como:

Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type

y se ocupan de definir la cantidad de elementos que hay en cada una de las dimensiones de la matriz, así como en qué número se comienza a contar el índice (lo que devuelve la función de VB LBound).
A la vista de esta información, una variable que se refiera a una matriz de dos dimensiones apuntará realmente a una estructura SAFEARRAY como la siguiente, que se puede llamar, por ejemplo, SAFEARRAY2D:

Type SAFEARRAY2D
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
Bounds(0 To 1) As SAFEARRAYBOUND
End Type
Los miembros de la estructura tienen el siguiente significado:
- cDims: es el número de dimensiones de la matriz.
- ffeatures: son indicadores especiales que identifican ciertas propiedades de la matriz, especificando cómo se aloja ésta en la memoria. En Visual Basic es mejor no tocar este miembro, pero el lector interesado puede referirse a la documentación de COM contenida en MSDN (msdn.microsoft.com).
- cbElements: es el tamaño en bytes de cada elemento de la matriz. Por ejemplo, si la matriz va a apuntar a datos de tipo Integer, este elemento de la estructura valdrá 2, que es el tamaño de este tipo de dato básico.
- cLocks: es un contador de uso de la matriz del estilo del que tienen los objetos COM (contador de referencias que controla cuántos programas están haciendo referencia al objeto). No lo vamos a usar nunca.
- pvData: es el miembro más interesante de todos, ya que es un puntero a la dirección de memoria donde residen los datos de la matriz. Cambiando este miembro seremos capaces de hacer que nuestra matriz apunte a cualquier zona de la memoria que deseemos. Este concepto es el más importante para entender este artículo, ya que nos permitirá manejar cualquier porción de memoria como si se tratara de una matriz.

Pasemos a la acción
Con estos conocimientos vamos a describir la manera de usar una matriz para manipular directamente datos residentes en la memoria sin necesidad de hacer una copia temporal de los mismos, como se ha hecho hasta ahora. Esto le permitirá mejorar algunos de los programas anteriores para aprovechar más los recursos del sistema, aunque se trata de una operación más compleja y delicada.
Como ejemplo práctico, consideremos la manipulación directa de los píxeles de un mapa de bits. Concretamente vamos a escribir tres rutinas de manipulación de gráficos que nos permitirán respectivamente invertir los colores según su paleta, hacer una imagen espejo de la actual, y restaurarla de nuevo.
Hacer esto directamente con código Visual Basic corriente es una opción casi impensable, pues la velocidad sería tan reducida que no valdría la pena. De todos modos, en el código de este artículo se incluye esta posibilidad para poder compararla con las técnicas avanzadas que aprenderemos.
Veamos cómo hacerlo usando punteros para manipular directamente la memoria. De los tres ejemplos comentados veremos ahora el primero (invertir los colores del mapa de bits), pudiendo consultar el código de los otros métodos en el artículo que podrá descargar desde el sitio web de PC World (www.idg.es/pcworld). En el Listado 1 se puede ver el método utilizado. A continuación se explicará cómo funciona.
La idea es la siguiente: los píxeles que constituyen un mapa de bits se almacenan en algún lugar de la memoria de manera ordenada. Si lográsemos averiguar de alguna manera dónde están situados, podríamos asignarlos al miembro pvData de un SAFEARRAY para manipularlos como elementos de una matriz. Esto es lo básico. Ahora veamos cómo llevarlo a cabo.

Apuntando a un mapa de bits con una matriz
Para averiguar toda la información referente a un mapa de bits determinado se puede emplear la función de la API GetObject, que en este caso se ha renombrado como GDIGetObject para evitar conflictos con la función homónima de Visual Basic que sirve para obtener referencias a objetos COM, y no tiene nada que ver con este tema:

Private Declare Function GDIGetObject Lib “gdi32” Alias “GetObjectA” (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long

Esta función toma como argumentos un manejador del mapa de bits (propiedad Handle de cualquier objeto Picture de VB), el tamaño de la estructura de información que se va a rellenar, y un puntero a una estructura del tipo BITMAP. Este tipo BITMAP se define como sigue:

Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type

y el significado de sus miembros se puede consultar en la Tabla A.
El miembro de esta estructura que más nos interesa es el último, ya que nos indica a qué dirección de memoria deberá apuntar nuestra matriz para manipular los píxeles del gráfico.
Lo primero que se hace en el código del Listado 1 es determinar la información acerca del gráfico con la mencionada función GDIGetObject. Luego se rellena una estructura del tipo SAFEARRAY2D para poder acceder a los píxeles. Nótese que como ancho del mapa de bits se coge el campo bmWidthBytes y no bmWidth. Esto es debido a que los mapas de bits siempre tienen en memoria un ancho múltiplo de 2, es decir, están alineados según un WORD. Entonces, en general, ambos campos no tienen por qué coincidir, ya que el mapa de bits puede tener un ancho en píxeles que no s

Contenidos recomendados...

Comentar
Para comentar, es necesario iniciar sesión
Se muestran 0 comentarios
X

Uso de cookies

Esta web utiliza cookies técnicas, de personalización y análisis, propias y de terceros, para facilitarle la navegación de forma anónima y analizar estadísticas del uso de la web. Consideramos que si continúa navegando, acepta su uso. Obtener más información