Título: Memoria en VB Publicado por: Kizar en 16 Marzo 2006, 23:50 pm Sabeis donde puedo encontrar un manual en español que explique como se trabaja con la memoria con VB6 utilizando las apis:
CopyMemory VirtualAlloc VirtualFree VirtualLock VirtualUnlock ZeroMemory FillMemory Vamos lo que es manejar la mamoria desde vb es que tengo algunas dudas y no se bien como manejarlas. salu2 Título: Re: Memoria en VB Publicado por: .Slasher-K. en 17 Marzo 2006, 04:22 am Manejar la memoria en VB
Este texto está dedicado a la chica más maravillosa del mundo, mi inspiración y mi fuerza, aunque sé que probablemente nunca lo lea ni sepa que existe. ICH LIEBE DICH... Índice 1. Nociones digitales 2. Representación de datos binarios 3. Arquitectura de memoria 4. Argumentos como referencia o como valor 5. Comportamiento de Strings en memoria 6. Funciones de manejo de memoria 6.1 CopyMemory 6.2 CopyMemory y Strings 6.3 Asignar y liberar memoria 6.3.1 Distribución de la memoria virtual 6.3.2 Espacio de Direcciones Virtuales y Almacenamiento Físico 6.3.3 Estado de las páginas 6.3.4 Alcance de la memoria asignada 6.3.5 Funciones VirtualAlloc y VirtualFree 6.4 Protección de memoria 6.4.1 Modos de protección: VirtualProtect 6.4.2 VirtualLock, VirtualUnlock 6.5 Leer y escribir memoria de distintos procesos 6.6 Memoria compartida 6.6.1 Concepto. Importancia de la memoria compartida. Ejemplos. 6.6.2 Asignación de memoria compartida 6.6.3 Escribir, leer en memoria compartida 6.7 Validar datos y accesos en memoria 6.8 Consultar estado y datos de regiones de memoria: VirtualQueryEx 6.9 Otras funciones de memoria: ZeroMemory, FillMemory, GlobalMemoryStatus 7. Usar la memoria en otros procesos: consideraciones y ejemplos. 8. Creando programas para todas las plataformas de Windows. Algunas veces se lo expliqué a algunos pero mejor lo dejo sentado en un documento así no tengo que volver a repetirlo xD. Antes de explicar el funcionamiento de las funciones de memoria que nos proporciona el SO, hay que tener presentes algunos conceptos básicos de técnicas digitales y cómo se posicionan los datos en memoria, así que voy a empezar por ahí. 1. Nociones digitales Primero, los datos en la memoria física, o sea en la plaqueta que tenemos dentro del gabinete, se guarda en forma binaria, o sea como 1's y 0's, y esto es debido a una razón muy lógica y es que en la electrónica hay dos opciones, hay tensión (un 1) o no (un 0). Ahora, para los ojos humanos ver millones de 0s y 1s no nos diría nada y poder mostrar eso de alguna manera mediante un programa, requeriría de muchos recursos. Lo que nosotros vemos en los editores de memoria en realidad son bytes, no bits (un 0 o un 1 es un bit). Un byte es un conjunto de 8 bits, que puede adoptar un valor entre 0 y 255 en el caso de que sea sin signo, o de -128 a 127 para el caso que sea con signo. Muchos se preguntarán el por qué estos valores, y para entender algo es mejor llegar hasta el fondo, así que voy a dar el ejemplo con un nº binario. Un número de 8 bits visto en binario sería como el siguiente: Código: 00000000 Es claro, 8 bits alineados. Entonces, como en el sistema binario sólo se puede trabajar con 0s y 1s, el valor máximo que puede adoptar un número de 8 bits se alcanzará cuando todos los bits estén en 1: Código: 11111111 Lo único que nos queda ahora es transformar ese nº binario en decimal, pero primero hay que tener en cuenta unos conceptos: 1. Cada bit tiene un índice de base 0 que lo identifica, siempre contando de izquierda a derecha: Código: 7 6 5 4 3 2 1 0 <- Índices 1 1 1 1 1 1 1 1 <- Bits Dependiendo de la posición del bit, este valdrá diferente, se dice que cada bit tiene "peso", y el más pesado es el primero de la derecha, esto se debe a que cada posición adopta el valor de 2 (dos) elevado al índice, entonces el valor de cada bit sería: Código: 7 6 5 4 3 2 1 0 <- Índices 128 64 32 16 8 4 2 1 <- Valores 1 1 1 1 1 1 1 1 <- Bits Esto significa que para el índice 0, el peso del bit será 2^0, para el índice 1, será 2^1, para el 2, 2^2, etc. Ahora, para terminar la conversión, multiplicamos el valor del bit por el peso que tenga, y eso nos dará el número en decimal: Código: 1*128 + 1*64 + 1*32 + 1*16 + 1*8 + 1*4 + 1*2 + 1*1 = 255 Y voilà, ahí tenemos el 255 que es lo máximo que podemos representar con un nº de 8 bits. 2. Representación de datos binarios Una vez que lo anterior está claro, voy a explicar cómo representan los editores hexadecimales y demás visores de memoria, y el mismo sistema a los datos. Como dije antes, no hay forma de leer un 1 o un 0 de la memoria si no es aplicando álgebra de boole (AND, OR, etc). El valor mínimo que se puede leer de la memoria es 1 byte, por esta razón la representación que hacen es mostrar los datos con caracteres ascii, ya que estos ocupan 1 byte (valores de 0 a 255). Pero que quede bien claro, TODO LO QUE SEA TEXTO ES PARA MOSTRAR AL USUARIO, incluso esto que estoy escribiendo se guarda en forma binaria, pero se muestra en forma gráfica de manera que sea legible para el ojo humano. Entonces todos esos "caracteres raros" que vemos al abrir un archivo o al examinar la memoria, no es otra cosa que un conjunto de valores de 1 byte, que por comodidad se muestran gráficamente como un caracter. 3. Arquitectura de memoria Acá viene la parte más importante que es cómo están dispuestos los datos en la memoria, y el funcionamiento general de el SO en la interacción con esta. Si no leyeron lo anterior les recomiendo que lo lean, si no lo entendieron es mejor que pregunten porque sin tener los conceptos anteriores claros va a ser difícil comprender esta parte. Otra aclaración, desde esta parte voy a dejar de hablar de bits, y me voy a referir a los bytes ya que así es como trabaja el sistema en general, además se harían muy extensos los ejemplos. Voy a empezar explicando dos conceptos que adoptan los microprocesadores: Big Endian y Little Endian Como todos sabemos, el micro interactúa directamente con la memoria leyendo y escribiendo datos, pero dependiendo de la arquitectura del micro los va a escribir de diferente manera. Little Endian En este caso, el microprocesador escribe el byte más bajo de un número en la dirección más baja de memoria. Voy a dar un ejemplo con un número de tipo Long (4 bytes). Para que quede claro mostraré primero cómo estarían repartidos los bits Código: |Byte 3| |Byte 1| |Byte 2| |Byte 0| 11111111 11111111 11111111 11111111 Una arquitectura Little Endian dispondría un número de estas caractéristicas de la siguiente manera en memoria: Código: Dirección + 0 Byte 0 Dirección + 1 Byte 1 Dirección + 2 Byte 2 Dirección + 3 Byte 3 Donde Dirección es una dirección cualquiera de memoria. Lo importante a destacar es que el primer byte del número será el primero en memoria. Esta norma la adoptan los microprocesadores de Intel y compatibles (los que la mayoría usamos) Big Endian Al contrario del caso anterior, esta arquitectura dispone el byte más alto en la dirección de memoria más baja: Código: Dirección + 0 Byte 3 Dirección + 1 Byte 2 Dirección + 2 Byte 1 Dirección + 3 Byte 0 ¿A qué viene todo esto?, muy sencillo, que para saber cómo leer desde la memoria debemos conocer primero cómo guarda los datos, es una cuestión de lógica. Lo último que queda por decir de este tema es que existen otras unidades de medida además del byte, y que son los Words y los DWords o DoubleWords. Un Word equivale a 2 bytes, y un DWord equivale a 2 Words. ¿Por qué no digo que un DWord son 4 bytes?, porque así van a comprender mejor lo que sigue :P. Y ya está llegando la parte interesante que nos va a introducir adentro de la programación. Desde acá en adelante voy a explicar todo lo que resta del funcionamiento de memoria pero orientado a Visual Basic, que es lo que nos interesa. Antes de seguir, voy a poner el código de 6 funciones que nos van a servir para experimentar y ver que lo que estoy explicando en esta guía es verdad. Código: Property Get HiByte(ByVal Word As Integer) As Byte ' Devuelve el Byte alto del Word especificado. ' If Word And &H8000 Then If Not (Word Or &HFF) = &HFFFFFFFF Then Word = (Word Xor &HFF) HiByte = &H80 Or ((Word And &H7FFF) \ &HFF) Else HiByte = Word \ 256 End If End Property Property Get HiWord(Dword As Long) As Integer ' Devuelve el Word alto del DWord especificado. ' If Dword And &H80000000 Then HiWord = (Dword / 65535) - 1 Else HiWord = Dword / 65535 End If End Property Property Get LoByte(Word As Integer) As Byte ' Devuelve el Byte bajo del Word especificado. ' LoByte = (Word And &HFF) End Property Property Get LoWord(Dword As Long) As Integer ' Devuelve el Word bajo del DWord especificado. ' If Dword And &H8000& Then LoWord = &H8000 Or (Dword And &H7FFF&) Else LoWord = Dword And &HFFFF& End If End Property Property Get MakeWord(ByVal bHi As Byte, ByVal bLo As Byte) As Integer ' Crea un Word a partir de sus dos componentes Byte. ' If bHi And &H80 Then MakeWord = (((bHi And &H7F) * 255) + bLo) Or &H7FFF Else MakeWord = (bHi * 255) + bLo End If End Property Property Get MakeDWord(wHi As Integer, wLo As Integer) As Long ' Crea un DWord a partir de sus dos componentes Word. ' If wHi And &H8000& Then MakeDWord = (((wHi And &H8000&) * 65536) Or (wLo And &HFFFF&)) Or &H80000000 Else MakeDWord = (wHi * &H10000) + wLo End If End Property Cada una de las funciones está comentada, lo único que voy a destacar es que un Word (2 bytes) equivale a un Integer que ocupa lo mismo, y un DWord (4 bytes) equivale a un Long. Más adelante vamos a usar estas funciones. Lo que viene ahora no es exclusivo de VB sino de programación en general, pero será explicado con las matrices de Byte de VB. Una matiz de Byte no es más que una serie de bytes consecutivos en memoria. Vamos a suponer que tenemos una matriz de 6 bytes, y le asignamos los siguientes valores: Código: btData(0)=65 btData(1)=66 btData(2)=67 btData(3)=68 btData(4)=69 btData(5)=70 Ahora vamos a suponer que el primero elemento de la matriz está en la dirección de memoria 1000 decimal, entonces en un mapa de memoria estos datos quedarían así: Código: Pos Valor 1000 65 1001 66 1002 67 1003 68 1004 69 1005 70 El esquema en columna es sólo para demostrar, que cada byte le sucede al anterior pero esto está alineado en realidad: Código: 65 66 67 68 69 70 ' Los espacios sólo son para separar, en la realidad no existen. Como dije en una parte de este documento, los editores Hexadecimales (no sé por qué le pusieron ese nombre xD) y visores de memoria varios, siempre muestran en una parte el valor numérico y en otra parte el caracter gráfico que le corresponde a ese valor. Entonces si pasamos esta matriz a la forma "visible para el ojo humano", quedaría así: Código: 65 66 67 68 69 70 <- Valor numérico A B C D E F <- Representación gráfica Entonces queda claro que una cadena de caracteres no es más que la forma que tiene el sistema operativo de mostrarle gráficamente al usuario una serie de números de 1 byte. Lo único que falta es entender cómo guarda los DWords y los Words, y cómo se generan. Acá es donde nos van a servir las funciones anteriores. Tomaremos como ejemplo los primeros 4 elementos de la matriz anterior, 65-66-67-68, para ejemplificar la representación de números de 2 y 4 bytes. Como dije antes, un DWord son 2 Words, y un Word son 2 bytes, por lo tanto un DWord son 2 pares de bytes. Consideremos el esquema siguiente para comprender cómo se forman los números en memoria: Código: ______DWord______ _WordL_ _WordH_ BL BH BL BH __ __ __ __ 65 66 67 68 Donde BL es el 'Byte bajo' del correspondiente Word, BH es el 'Byte alto', WordL es el 'Word bajo' del DWord y WordH es el 'Word alto' del DWord. Entonces, vamos por partes como dijo jack el destripador xD, el siguiente código primero genera ambos Words y luego el DWord: Código: Sub TestMem() Dim btData(3) As Byte Dim iLoWord As Integer, iHiWord As Integer Dim lDWord As Long btData(0) = 65 ' Byte bajo del Word bajo. btData(1) = 66 ' Byte alto del Word bajo. btData(2) = 67 ' Byte bajo del Word alto. btData(3) = 68 ' Byte alto del Word alto. iLoWord = MakeWord(btData(1), btData(0)) ' Crea el Word bajo. iHiWord = MakeWord(btData(3), btData(2)) ' Crea el Word alto. lDWord = MakeDWord(iHiWord, iLoWord) ' Crea el DWord. Debug.Print Hex$(VarPtr(lDWord)) Stop End Sub Ahora vamos a necesitar un editor de memoria, el mejor editor hexa que conozco es el WinHex, pero pueden usar el que más les guste. Este procedimiento crea el DWord por cada una de sus partes, e imprime en la ventana Inmediato la dirección de la variable lDWord en hexadecimal (sólo por conveniencia porque las direcciones de memoria son números muy grandes). La función no-documentada de VB VarPtr() nos devuelve el puntero a cualquier variable. El puntero a una variable es la dirección en memoria del primer byte de los datos, se llama puntero por la razón obvia, que apunta al primer byte. Si con el editor hexadecimal nos dirijimos a la dirección que se imprime, y vemos la parte de los valores hexadecimales, veremos 4 bytes seguidos, 65-66-67-68. Para el que no quiera usar un editor hexadecimal o no lo tenga, escribí otras 2 funciones que convierten de Texto a DWord y de DWord a Texto, tal y como funciona en memoria: Código: Function DWordToString(dw As Long) As String DWordToString = Chr$(LoByte(LoWord(dw))) & _ Chr$(HiByte(LoWord(dw))) & _ Chr$(LoByte(HiWord(dw))) & _ Chr$(HiByte(HiWord(dw))) End Function Function StringToDWord(Str As String) As Long If Len(Str) < 4 Then Str = Str & String$(4 - Len(Str), 0) StringToDWord = MakeDWord( _ MakeWord( _ Asc(Mid$(Str, 4)), _ Asc(Mid$(Str, 3, 1)) _ ), _ MakeWord( _ Asc(Mid$(Str, 2, 1)), _ Asc(Mid$(Str, 1, 1)) _ ) _ ) End Function Ahora cambiemos la linea que imprime la dirección de la variable lDWord por la llamada a DWordToString(), para que vean lo que pasa :). Código: Sub TestMem() Dim btData(3) As Byte Dim iLoWord As Integer, iHiWord As Integer Dim lDWord As Long btData(0) = 65 ' Byte bajo del Word bajo. btData(1) = 66 ' Byte alto del Word bajo. btData(2) = 67 ' Byte bajo del Word alto. btData(3) = 68 ' Byte alto del Word alto. iLoWord = MakeWord(btData(1), btData(0)) ' Crea el Word bajo. iHiWord = MakeWord(btData(3), btData(2)) ' Crea el Word alto. lDWord = MakeDWord(iHiWord, iLoWord) ' Crea el DWord. Debug.Print DWordToString(lDWord) Stop End Sub Y magia, ahora sale en la ventana inmediato ABCD :), ¿qué es ABCD?, nada menos que los valores 65-66-67-68 en sus respectivos caracteres ASCII. Creo que quedó claro el funcionamiento de la memoria, ahora ya viene la parte de las funciones que nos proporciona el SO para manejarla, pero antes, la gran diferencia entre ByVal y ByRef 4. Argumentos como referencia o como valor Hay dos maneras de pasarle argumentos a las funciones, como valor o como referencia. En Visual Basic TODAS las variables se pasan como referencia a menos que se indique lo contrario usando ByVal, la palabra clave utilizada para pasar una variable como valor. La diferencia es que al pasar una variable como referencia, lo que está pasando es el puntero a dicha variable. En cambio, pasando la variable como valor lo que se pasa es el valor o 'contenido' de la variable en ese instante. Por ejemplo, tengo la variable lData y le asigno un valor cualquiera: Código: lData = 123456 Ahora escribimos dos procedimientos, uno que acepte la variable como valor y otro como referencia: Código: Sub PassByRef(Var As Long) Var = 654321 End Sub Sub PassByVal(ByVal Var As Long) Var = 654321 End Sub Luego probamos ambas funciones con el siguiente procedimiento: Código: Sub TestPass() Dim lDataByRef&, lDataByVal& lDataByRef = 123456 lDataByVal = 123456 Debug.Print "El valor actual de lDataByRef es " & lDataByRef Debug.Print "El valor actual de lDataByVal es " & lDataByVal Call PassByRef(lDataByRef) Call PassByVal(lDataByVal) Debug.Print "El valor de lDataByRef es ahora " & lDataByRef Debug.Print "El valor de lDataByVal es ahora " & lDataByVal End Sub Y sólo con mirar los outputs se dan cuenta lo que pasa. Cuando paso la variable como valor, lo que sucede es que se copia el contenido de la misma a otra parte de la memoria distinta de la dirección de esta, entonces el procedimiento modificará los datos en la dirección nueva de memoria, y al terminar (End Sub) VB borrará este 'espacio temporal' que usó para copiar el valor. Como se puede ver, pasar un argumento como valor lleva un poco más de tiempo ya que tiene que asignar más memoria y copiar los datos, pero es muy necesario cuando se llama a las APIs ya que si pasamos una variable como referencia y a esta función se le ocurre escribir más allá del tamaño de la variable, hará que el programa produzca un error y se cierre porque vaya a saber qué es lo que modificó. El caso de los datos String es algo particular, porque no se comportan como las variables numéricas. Las variables String están compuestas de dos partes, la variable en sí y los datos de la variable. Como todos sabrán las variables String ocupan 4 bytes, esto se debe a que en realidad es una dirección de memoria donde están los datos de la misma. Por ejemplo, supongamos que declaro la variable sData y le asigno un valor: Código: Dim sData$ sData = "Hola mundo" En este ejemplo hipotéticamente creeremos que la variable sData se encuentra en la dirección 8000, y los datos en 9000. Esto significa que en la dirección de memoria 8000 (el puntero a la variable sData), encontraremos el valor 9000 que indica la dirección de los datos en Unicode, ya que las cadenas de VB son todas Unicode (2 bytes por caracter) 5. Comportamiento de Strings en memoria Creo que el tema de los Strings en VB merece un apartado especial, ya que es algo muy importante y es algo difícil de ver y entender. Primero hay que destacar que existen dos tipos de String que usan las APIs y Visual Basic, LPSTR y BSTR. Las cadenas LPSTR (String Pointer) son muy simples, se trata de una serie de caracteres terminados en un caracter nulo: Código: HOLA MUNDO\0 ' \0 = Caracter nulo Se denomina puntero a la dirección de memoria del primer byte de un dato determinado. En el ejemplo anterior el puntero a esa cadena sería la dirección de 'H', o más bien del valor 72 (código ascii de 'H') Esto en Visual Basic es equivalente a una matriz de byte, ya que como dije antes no es más que una serie de valores de tipo Byte consecutivos en memoria. En cambio Visual Basic utiliza las cadenas BSTR, que son un tipo de dato de automatización, adoptado de OLE Automation (Automatización OLE). Este tipo de String está diseñado para evitar problemas comunes como lo son los BOFs (Buffer Overflow), ya que debido a su estructura es imposible que se de esta situación en la asignación. A diferencia de las LPSTR, las cadenas BSTR son SIEMPRE Unicode, esto significa que cada caracter ocupará 2 bytes (1 WORD). También hay que destacar que no terminan en un caracter nulo sino en dos caracteres nulos, como dije antes cada caracter es un WORD. Otra característica especial es que los 4 bytes anteriores al inicio de la cadena especifican el tamaño de la misma, de esta manera el sistema no deberá escanear byte por byte buscando el final del String y el proceso se hará mucho más eficaz y rápido. El siguiente esquema ilustra una cadena BSTR: Código: Len Cadena End | |H O L A M U N D O|\0\0| En el alfabeto latino y derivados sólo se usa el primer byte del WORD para almacenar el código de caracter, ya que los valores oscilan entre 0-255. En alfabetos orientales y demás tienen códigos de caracter muy superiores a 255 y por esta razón la necesidad de usar 2 bytes para guardar este valor. Por un lado ya sabemos cómo se guardan las cadenas en memoria, ahora paso a explicar el funcionamiento de las variables String. Primero hay que saber que existen 2 funciones no-documentadas de VB: VarPtr() y StrPtr() VarPtr() devuelve el puntero a una variable, cualquiera sea el tipo de variable. StrPtr() devuelve el puntero a la cadena de una variable String. Ahora, como ya comenté antes las variables String tienen dos partes, la variable en sí y los datos de la variable, que NO se encuentran en la misma dirección. Esto es mucho más sencillo explicarlo con un ejemplo. Vamos a utilizar el siguiente código de ejemplo: Código: Sub StrTest() Dim sData$ sData = "Hola mundo" Debug.Print Hex$(VarPtr(sData)) Debug.Print Hex$(StrPtr(sData)) Stop End Sub Ejecuten el procedimiento StrTest() y cuando la ejecución se detenga es donde nosotros comenzamos a experimentar con la memoria. Ahora sí, es determinante para la comprensión de este tema usar un visor de memoria/editor hexadecimal. Yo recomiendo el WinHex porque es el mejor desde mi punto de vista. Ahora en la ventana Inmediato aparecieron dos direcciones, la primera es la dirección de la variable y la de abajo la dirección de los datos. Para comprobar que esto es cierto abrimos el WinHex, abrimos la memoria completa del proceso de VB (VB6.EXE) (Alt+F9 para ver la lista de procesos) y por último apretamos Alt+G para que aparezca el cuadro de "Ir a". Ahí vamos a colocar la primera dirección, en mi caso es 7FF540. El programa se posicionará en esa dirección de memoria y veremos algo como lo que muestra la siguiente imagen: (http://img75.imageshack.us/img75/5/hex3is.jpg) Si se fijan bien en esta dirección no hay ninguna cadena, pero el valor de 32 bits que está almacenado en la posición 7FF540 (VarPtr(sData)) es 64B718, o sea StrPtr(sData) que es la dirección de la cadena. Ahora copiamos esta dirección que vemos y hacemos los mismo, Alt+G y vamos a la nueva dirección que nos manda la variable, o sea 64B718. En este momento una imagen vale más que mil palabras :) (http://img20.imageshack.us/img20/4721/hex23li.jpg) En 64B718 es donde realmente están los datos de la variable sData. Bueno, entonces resumiendo un poco (ya sé que repetí lo mismo muchas veces, pero es para que se entienda xD), si pasamos una variable String como ByRef estamos pasando la dirección de la variable, en el caso anterior sería 7FF540. En cambio al pasarla como ByVal estamos pasando la dirección de los datos, 64B718 en este ejemplo. 6. Funciones de manejo de memoria No voy a dar muchas vueltas, directamente voy a ir a lo que hace cada función y cómo se comporta en memoria. 6.1 CopyMemory Esta es la función más antigua que implementa el sistema operativo pero sigue siendo muy funcional y vigente. El prototipo de esta función es el siguiente: Código: Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long) El uso de esta función es muy simple siempre y cuando se esté bien conciente de qué se está copiando y a dónde. Los parámetros que hay que pasarle son los siguientes: Destination: Dirección de memoria a la cual se copiarán los datos. Source: Dirección de memoria desde donde se copiarán los datos. Length: Cantidad de bytes que la función leerá desde Source y copiará a Destination. El gran problema a la hora de usar esta función es que hay que saber cuándo debemos pasar un parámetro como valor o como referencia. Si no leyeron todo el documento les recomiendo que lean 'Argumentos como referencia o como valor' primero. Consideremos el siguiente ejemplo: Código: Sub CopyMem() Dim lSourceData&, lDestData& lSourceData = 123456 Call CopyMemory(lDestData, lSourceData, Len(lSourceData)) Debug.Print "Ahora lDestData es igual a " & lDestData Stop End Sub Para analizar esto hay que tener en cuenta lo siguiente: La palabra clave Any se utiliza para pasar el puntero (vease definición de puntero en la sección anterior) a CUALQUIER TIPO de variable. Pasarle una variable a una función con argumentos declarados como Any, es análogo a usar ByVal VarPtr(Variable). Por ejemplo: Código: Sub CopyMem() Dim lSourceData&, lDestData& lSourceData = 123456 Call CopyMemory(ByVal VarPtr(lDestData), ByVal VarPtr(lSourceData), Len(lSourceData)) Debug.Print "Ahora lDestData es igual a " & lDestData Stop End Sub ¿Por qué?. En mi caso particular la dirección de lSourceData es 7FF540, y de lDestData es 7FF53C. Cuando llamamos a VarPtr(), como ya notaron anteriormente, nos devuelve 7FF540 para lSourceData y 7FF53C para lDestData. Mirando el primer ejemplo, cuando se pasan las variables como Any y por referencia , lo que Visual Basic le pasa a la función CopyMemory son esos valores que devuelve VarPtr(). Si pasamos la variable como ByVal, lo que hace VB es pasarle a la función CopyMemory los datos de la misma. Por ejemplo: Código: Sub CopyMem() Dim lSourceData&, lDestData& lSourceData = 123456 Call CopyMemory(lDestData, ByVal lSourceData, Len(lSourceData)) End Sub Lo que le estoy diciendo ahí es que copie a 7FF53C lo que hay en la dirección 123456, ya que en este caso se le pasa el valor de lSourceData y CopyMemory SIEMPRE toma los argumentos como direcciones de memoria. Volviendo al ejemplo de ByVal VarPtr(Variable), lo que le estoy indicando a VB es que le pase el valor que devuelva VarPtr, o sea 7FF540 entonces CopyMemory leerá en el lugar correcto. Eso sería equivalente a lo siguiente: Código: Sub CopyMem() Dim lSourceData&, lDestData& lSourceData = VarPtr(lSourceData) lDestData = VarPtr(lDestData) Call CopyMemory(ByVal lDestData, lSourceData, Len(lSourceData)) Debug.Print "Ahora lDestData es igual a " & lDestData Stop End Sub Ya que ahora, cada variable va a contener la dirección de sí misma. Esto es cuestión de probar y ver lo que pasa, por más que yo se los cuente si no lo ven será difícil de asimilar. En el caso de una tipo definido por el usuario (TDU), es exactamente lo mismo. Lo que hay que saber, es que cada registro de los TDU se encuentran consecutivos en memoria. Veamos el siguiente ejemplo: Código: Type SomeData Size As Integer Registro1 As Long Registro2 As Long Registro3 As Currency Registro4 As String End Type En memoria estaría dispuesto de la siguiente manera: Código: 2 4 4 8 4 Posición más baja|..|....|....|........|....|Posición más alta Esta estructura ocuparía 22 bytes de memoria, ya que son 2 Long (8 bytes) más un Currency (8 bytes) más un Integer (2 bytes) más un String (4 bytes). Vamos a creer que el puntero a la estructura está en 1000 (el puntero es el primer byte del primer registro, o sea el primer byte del campo Size). Código: Offset Campo 1000 Size 1002 Registro1 ' VarPtr(Size) + 2 1006 Registro2 ' VarPtr(Registro1) + 4 1010 Registro3 ' VarPtr(Registro2) + 4 1018 Registro4 ' VarPtr(Registro3) + 8 Con esto creo que está concluída la explicación del uso de CopyMemory() con variables no-String. 6.2 CopyMemory y Strings Para este tema prefiero escribir un apartado porque si bien es similar, es mucho más importante porque puede llegar a provocar un error general del programa y que se cierre. Antes de seguir recomiendo que lean 5. Comportamiento de Strings en memoria, porque no pienso volver a repetir todo, sólo un repaso muy por arriba. Bueno ahora sí empiezo. Para copiar los datos de una variable String hay que tener en cuenta dos puntos claves: 1. SIEMPRE y sin excepciones hay que usar ByVal. 2. Antes de llamar a CopyMemory la variable de destino tiene que ser inicializada como mínimo con la misma cantidad de datos que la variable desde donde se copia (Source). Estos dos items los voy a explicar con un ejemplo CORRECTO, luego voy a mostrar la forma errónea. Código: Sub CopyMemStr() Dim sSrcData$, sDestData$ sSrcData = "Hola mundo" sDestData = String$(Len(sSrcData), 0) Call CopyMemory(ByVal sDestData, ByVal sSrcData, LenB(sSrcData)) Debug.Print sSrcData, sDestData Stop End Sub Este código funcionará correctamente ya que lo que se está haciendo es copiar el contenido de una variable a la otra. A continuación muestro los datos que hay en memoria para la variable sSrcData luego de establecer el ejecutarse la linea sSrcData = "Hola mundo": ACLARACIÓN: Para ver bien los datos del dumpeo de memoria copienlos al bloc de notas y deshabiliten el ajuste de linea. Código: Datos de sSrcData 0062D920 14 00 00 00 48 00 6F 00 6C 00 61 00 20 00 6D 00 75 00 6E 00 64 00 6F 00 ....H.o.l.a. .m.u.n.d.o. 0062D938 00 00 72 00 79 00 00 00 CC 00 00 A0 00 00 00 00 EC 0A 67 00 FF FF FF FF ..r.y...Ì.. ....ì.g.ÿÿÿÿ 0062D950 FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FC DE 62 00 ÿÿÿÿ................üÞb. 0062D968 00 00 00 00 00 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 C8 06 67 00 ....................È.g. 0062D980 FF FF FF FF 6C 13 67 00 00 00 00 00 0C 65 61 00 00 00 00 00 14 DF 62 00 ÿÿÿÿl.g......ea......ßb. 0062D998 01 00 00 00 AC A1 63 00 ....¬¡c. El puntero a la cadena es 62D924, o sea la dirección de 'H', y la cadena ocupa en memoria 20 bytes, o sea LenB(sSrcData), ya que como pueden observar se encuentra en Unicode. Lo segundo que se debe hacer es reservar la MISMA cantidad de espacio en sDestData para poder copiar los datos de sSrcData, eso es lo que hacemos con la función String(): Código: sDestData = String$(Len(sSrcData), 0) Por empezar, llenamos la cadena sDestData con caracteres nulos pero sólo es por una cuestión de comodidad, pero puede ser cualquier caracter que queramos. Una vez que asignamos espacio los datos de sDestData se verían así en memoria: Código: Datos de sDestData antes de llamar a CopyMemory 0066E048 14 00 00 00 .... 0066E060 48 00 6F 00 6C 00 61 00 20 00 6D 00 75 00 6E 00 64 00 6F 00 00 00 02 00 ........................ 0066E078 0E 00 18 02 78 00 00 A0 18 E0 66 00 80 1E 67 00 B0 2A 67 00 30 2B 67 00 ....x.. .àf.€.g.°*g.0+g. 0066E090 9C 2B 67 00 AC 2B 67 00 BC 2B 67 00 A0 29 67 00 5C 37 67 00 70 37 67 00 œ+g.¬+g.¼+g. )g.\7g.p7g. 0066E0A8 80 37 67 00 8C 37 67 00 98 37 67 00 A8 37 67 00 B4 37 67 00 C4 37 67 00 €7g.Œ7g.˜7g.¨7g.´7g.Ä7g. 0066E0C0 D8 37 67 00 E8 37 67 00 F8 37 67 00 08 38 67 00 18 38 67 00 28 38 67 00 Ø7g.è7g.ø7g..8g..8g.(8g. 0066E0D8 38 38 67 00 88g. Desde la posición 66E060 (StrPtr(sDesdData)) vemos que ahora la cadena sDestData está inicializada, entonces ya estamos preparados para llamar a CopyMemory: Código: Call CopyMemory(ByVal sDestData, ByVal sSrcData, LenB(sSrcData)) Lo que le estamos diciendo es que copie 20 bytes desde 62D924 (el puntero a los datos de sSrcData) a 66E060 (el puntero a los datos de sDestData), por lo tanto quedarían exactamente igual: Código: Datos de sSrcData 0062D920 14 00 00 00 48 00 6F 00 6C 00 61 00 20 00 6D 00 75 00 6E 00 64 00 6F 00 ....H.o.l.a. .m.u.n.d.o. 0062D938 00 00 72 00 79 00 00 00 CC 00 00 A0 00 00 00 00 EC 0A 67 00 FF FF FF FF ..r.y...Ì.. ....ì.g.ÿÿÿÿ 0062D950 FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FC DE 62 00 ÿÿÿÿ................üÞb. 0062D968 00 00 00 00 00 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 C8 06 67 00 ....................È.g. 0062D980 FF FF FF FF 6C 13 67 00 00 00 00 00 0C 65 61 00 00 00 00 00 14 DF 62 00 ÿÿÿÿl.g......ea......ßb. 0062D998 01 00 00 00 AC A1 63 00 ....¬¡c. Código: Datos de sDestData luego de llamar a CopyMemory 0066E048 14 00 00 00 .... 0066E060 48 00 6F 00 6C 00 61 00 20 00 6D 00 75 00 6E 00 64 00 6F 00 00 00 02 00 H.o.l.a. .m.u.n.d.o..... 0066E078 0E 00 18 02 78 00 00 A0 18 E0 66 00 80 1E 67 00 B0 2A 67 00 30 2B 67 00 ....x.. .àf.€.g.°*g.0+g. 0066E090 9C 2B 67 00 AC 2B 67 00 BC 2B 67 00 A0 29 67 00 5C 37 67 00 70 37 67 00 œ+g.¬+g.¼+g. )g.\7g.p7g. 0066E0A8 80 37 67 00 8C 37 67 00 98 37 67 00 A8 37 67 00 B4 37 67 00 C4 37 67 00 €7g.Œ7g.˜7g.¨7g.´7g.Ä7g. 0066E0C0 D8 37 67 00 E8 37 67 00 F8 37 67 00 08 38 67 00 18 38 67 00 28 38 67 00 Ø7g.è7g.ø7g..8g..8g.(8g. 0066E0D8 38 38 67 00 88g. Como vemos los datos son exactamente iguales. Acá lo importante es inicializar la cadena de destino, porque sino estaríamos sobreescribiendo los datos que siguen y vaya a saber qué modificamos, por eso produce un error de página no válida y se cierra el programa. Si no pasamos los datos como ByVal lo que pasaría es que modificaría la variable, no los datos, y sobreescribiría datos que no queremos por lo que se cerrará el programa. Código: Variable sSrcData (007FF540) y sDestData (7FF53C) 007FF530 00 00 00 00 00 00 00 00 00 00 00 00 60 E0 66 00 24 D9 62 00 D4 F5 7F 00 ............`àf.$Ùb.Ôõ. 007FF548 81 1B 67 00 04 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 6B 19 C0 0F .g.................k.À. Los datos de la variable sDestData son '60 E0 66 00' y los de sSrcData son '24 D9 62 00', que si usamos las funciones MakeWord y MakeDWord como lo expliqué antes veremos que obtendremos las direcciones de los datos. La cuestión es que si no usamos ByVal le estamos diciendo que copie 20 bytes desde 007FF540 (sSrcData) a 7FF53C (sDestData), o sea que reemplazaría '60 E0 66 00' por '24 D9 62 00 D4 F5 7F 00 81 1B 67 00 04 00 00 00 00 00 00 00 02' y sobreescribiría en memoria a sSrcData que está justo después de sDestData, entonces ya no se puede predecir lo que pasará porque no sabemos qué datos arruinamos, y en general se jode el programa :P. No sé si podría explicarlo más claro, cualquier consulta en este mismo post, para comprender esto hay que practicar y ver lo que pasa. 6.3 Asignar y liberar memoria El uso de estas funciones es muy sencillo, pero antes de hablar de ellas voy a describir un poco como se conforma la memoria. 6.3.1 Distribución de la memoria virtual El espacio de memoria virtual está dividido en particiones:
En general los procesos se cargan en la dirección 0x400000 ya que es compatible con todas las versiones de Windows. 6.3.2 Espacio de Direcciones Virtuales y Almacenamiento Físico El espacio de direcciones virtuales es la memoria que hay disponible para cada proceso. Se llama así ya que al usar un archivo en el disco para extender la memoria física (archivo de paginación o swap) el sistema puede crear un "mapa" de memoria idéntico para cada proceso. Esto significa que cada proceso virtualmente tiene acceso a TODA la memoria física e incluso más todavía, y además cada proceso puede escribir en la misma dirección debido a que es virtual, por lo tanto no sobreescribiría lo de otro. En resumen, Windows crea una especie de memoria RAM en el disco rígido para cada proceso. El espacio de direcciones virtuales para cada proceso es mucho mayor que el total de memoria física para todos los procesos. Como dije antes, para incrementar el tamaño del almacenamiento físico, el sistema usa el disco para almacenamiento adicional. La cantidad total de almacenamiento disponible para todos los procesos que se están ejecutando es la suma de la memoria física y el espacio libre en el disco disponible para el archivo de paginación. El almacenamiento físico y el espacio de direcciones virtuales para cada proceso están organizados en páginas, unidades de memoria, cuyo tamaño depende del microprocesador. Por ejemplo, en computadoras x86 el tamaño de página es de 4 KB (4096 bytes). Pata maximizar la flexibilidad en la memoria principal, el sistema puede mover páginas de memoria física a y desde el archivo de paginación (swap) en el disco. Cuando la página es movida o modificada en la memoria física, el sistema actualiza el mapa de páginas de los procesos afectados para que los datos de la memoria física y del archivo de paginación sean iguales. Cuando el sistema necesita espacio en la memoria física, mueve las páginas recientemente usadas de memoria física a la swap. La manipulación de la memoria física por el sistema es completamente transparente para las aplicaciones, las cuales operan sólo en sus espacios de memoria virtual. 6.3.3 Estado de las páginas Las páginas del espacio de direcciones virtuales de un proceso pueden estar en uno de los siguientes estados:
6.3.4 Alcance de la memoria asignada Toda la memoria que un proceso asigna usando las funciones de asignación de memoria (HeapAlloc, VirtualAlloc, GlobalAlloc, LocalAlloc) son accesibles sólo para dicho proceso. Sin embargo, la memoria asignada por una DLL está asignada en el espacio de direcciones del proceso que ha llamado a la DLL y no está accesible por otros procesos usando la misma DLL. Para crear memoria compartida, hay que usar el mapeado de archivos (objetos file-mapping) que se explican más adelante. Esto significa que creando una DLL podemos tener acceso a la memoria cualquier proceso, y esto se usa mucho como método de intección. Por ejemplo, si un proceso carga el archivo Injection.DLL y dentro del código de Injection.DLL se llama a VirtualAlloc, va a asignar memoria en el proceso que cargó esta librería. Otra particularidad es que las funciones GetCurrentProcess(), GetCurrentThread(), GetCurrentProcessId() y GetCurrentThreadId() también devuelven los datos del proceso que cargó a la DLL. Título: Re: Memoria en VB Publicado por: Kizar en 17 Marzo 2006, 16:39 pm Perfectamente explicado todo MAESTRO ;)
Espero ansioso la parte de usar las apis de windows para leer y escribir en memoria. Quedo todo muy claro menos lo de las Strings supongo que sera porque no entendí muy bien lo que es un puntero. Supongo que a este post le pondran chincheta. Salu2 Título: Re: Memoria en VB Publicado por: .Slasher-K. en 18 Marzo 2006, 07:03 am Voy a editar el mismo post así queda todo junto. Si algo no se entiende digan e intento explicarlo con manzanas :P.
Título: Re: Memoria en VB Publicado por: ReViJa en 18 Marzo 2006, 11:26 am Excelente...
Título: Re: Memoria en VB Publicado por: .Slasher-K. en 19 Marzo 2006, 16:16 pm Voy a ir agregando las cosas que faltan y mejorando lo que ya está a medida que tenga tiempo, seguramente una vez por día.
Mientras tanto ya pueden ir consultando lo que no se entiende para que lo explique mejor, y así voy mejorando esto porque sé que me dejé muchas cosas en el tintero, pero es por falta de tiempo. Saludos. Título: Re: Memoria en VB Publicado por: .Slasher-K. en 24 Marzo 2006, 00:12 am Actualización 23 de marzo del 2006
Título: Re: Memoria en VB Publicado por: juampivicius en 24 Marzo 2006, 21:18 pm sos muy groso Slasher...perdón ningún moderador piensa ponerle una chincheta a este gran post?...seguí asi Slash
saludos Título: Re: Memoria en VB Publicado por: LeandroA en 25 Marzo 2006, 03:49 am Muy Buena explicacion esperamos la segunda , saludos
Título: Re: Memoria en VB Publicado por: [VolkS] en 4 Abril 2006, 12:58 pm Que buen post!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! :D
Igual voy a leerlo en partes, porque si lo leo todo junto me van a quedar cosas sueltas y prefiero saberlo todo bien bien. Aprovechen este texto asi porque hay pocos :P o ninguno... Título: Re: Memoria en VB Publicado por: Kizar en 16 Abril 2006, 22:42 pm El resto ¿?
Título: Re: Memoria en VB Publicado por: LaN en 11 Mayo 2006, 16:42 pm ¿lo vas a acabar? porque está muy bien hasta aquí!!!
Título: Re: Memoria en VB Publicado por: _Sergi_ en 9 Junio 2006, 04:23 am El manual es muy bueno pero ya hace 3 meses que se escribió, osea que me parece improbable que sea continuado, también porque su autor no se pasa por el foro desde hace meses...
Ojalá me equivoque... Saludos Título: Re: Memoria en VB Publicado por: LixKeÜ en 23 Diciembre 2006, 17:46 pm lo necesito tio ;D ;D
Título: Re: Memoria en VB Publicado por: Lupin en 11 Junio 2009, 19:55 pm ..Recien leo este tutorial de la memoria..es excelente ;-) alguien sabe..si se llego a terminar..seria una lastima que no
Título: Re: Memoria en VB Publicado por: Karcrack en 11 Junio 2009, 19:59 pm Citar « Respuesta #13 en: 23 Diciembre 2006, 17:46 » Citar « Respuesta #14 en: Hoy a las 19:55 » Te parece bien revivir un tema de hace mas de dos años? Date un paseo por las reglas.... Y NO, no lo acabó... Título: Re: Memoria en VB Publicado por: Lupin en 11 Junio 2009, 20:42 pm esperaba este tipo de respuesta..pero me parecio un trabajo tan interesante y didactico que mi primer impulso fue preguntar si se termino o no...
|