Boletín Pascal #36
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
![]() |
Boletín Pascal #36 - 02-JUN-2002 INDICE 1. UNAS PALABRAS DEL EDITOR 2. LA FIRMA DE DELPHI 3. USANDO ADO EN DELPHI 6 4. USANDO ARCHIVOS DE AYUDA HTML HELP EN TUS PROGRAMAS 5. PROGRAMACIÓN ORIENTADA A OBJETOS (POO) 6. INLINE ASSEMBLER EN DELPHI (I) 7. FOROS 8. DELPHI EN LA RED - Componentes, librerías y aplicaciones . Shareware/Comercial . Freeware - Artículos, trucos y consejos - Tutoriales - Otros enlaces ________________________________________________________________________ 1. UNAS PALABRAS DEL EDITOR Me gustaría agradecer a los autores de los artículos colaborados para esta edición, y en esta ocasión me complace hacer entrega a S.S.B. Magesh Puvananthiran la licencia de Greatis Print Suite, un juego de componentes de vista previa e impresión, provisto por Greatis Software: http://www.greatis.com/printsuite.htm En la próxima edición, uno de nuestros colaboradores recibirá una licencia de SMImport, un juego de componentes para convertir desde los formatos de datos populares, provisto por Scalabium: http://www.scalabium.com/smi/index.htm A quienes son suscriptores del Boletín para Desarrolladores les recuerdo que continúa la búsqueda de corresponsales para visitar sitios web y seleccionar los enlaces al contenido nuevo más interesante hallado en los mismos, algo parecido a lo que hace Dave Murray en la sección "Delphi en la Red" de este boletín. Como probablemente muchos ya sepan, Borland ha tomado una importante decisión sobre el futuro de la BDE. Supongo ya todos lo veíamos venir... Los SQL Links de la BDE (para acceder a datos en servidores de bases de datos) se continuarán incluyendo con los productos Borland hasta fin de año, pero serán marcados como "deprecados" (no se les harán más actuali- zaciones ni mejoras). La BDE, sin los SQL Links, se continuará inclu- yendo con los productos Borland, pero será marcada como "congelada" (significando que Borland continuará enviando, probando y dando soporte para el soporte de tablas locales de la BDE, pero no se le harán mejoras y no hay planes de nuevas características ni corrección de errores). Puedes leer más sobre esto en el artículo de John Kaster publicado en la Borland Developer Network: http://community.borland.com/article/0,1410,28688,00.html ¿Cómo impacta esta decisión de Borland en tus proyectos? ¿Seguirás usando la BDE para bases de datos locales, o planeas migrarlas a Interbase usindo IBX, o escogerás otra solución? ¿Qué opinas de ADO? ¿Son los SQL Links todavía una opción para proyectos nuevos, o planeas migrar a DataSnap Direct (aka dbExpress)? Me gustaría escuchar tus comentarios y planes para el futuro cercano. Saludos, Ernesto De Spirito eds2004 @ latiumsoftware.com ________________________________________________________________________ JfControls Lib. Multilenguaje. Multiapariencia. Skins. Privilegios. Más de 40 componentes integrados y personalizables. Múltiples problemas de programación resueltos. Administración centralizada de recursos. Para Delphi 3-7 y C++ Builder 3-6. http://www.jfactivesoft.com/spindex.htm ________________________________________________________________________ 2. LA FIRMA DE DELPHI Por José Manuel Rodriguez (JMR) <jmr@clubdelphi.com> Intro ===== Hace poco ví por la lista de correo delphi-intermedio una pregunta sobre como se podía saber si un programa estaba hecho en Delphi. Aparte de recomendaciones de que usara algún que otro programa comercial o shareware como el famoso PE Explorer, este mensaje no obtuvo ninguna respuesta concreta y el caso es que es que hay una forma, y bastante directa, de averiguarlo: a partir de Delphi 3.0, todos los ficheros creados con Delphi, ya sean ejecutables (con interfaz gráfico o aplicaciones de consola) o DLLs (incluidos los controles ActiveX pero no los packages) llevan la firma de Delphi en su interior. Recursos de Windows =================== La mayor parte de las aplicaciones de Windows contiene Recursos (Resources). Los recursos se introdujeron en una época temprana de Windows dado su carácter visual gráfico: aunque es posible (y de hecho los compiladores de recursos lo hacen) generar a base de código los elementos gráficos de una aplicación, sería una tarea enojosa e inacabable. En su lugar éstos se diseñan visualmente aparte, con un programa especial, por ejemplo se dibuja un cuadro de diálogo de forma gráfica, y sobre él se colocan los botones, los edits, etc. Cuando tiene por fin el aspecto que se desea, se compila y ya está listo para ser incluido en nuestro ejecutable. Por lo tanto tenemos que los Recursos de Windows son datos binarios que son incluidos para su posterior uso en los ejecutables, entendiéndose como ejecutables cualquier fichero que tiene tiene el formato PE (Portable Executable) de Win32, como los EXE, DLL, OCX, etc. Dado que hay una serie de estos datos binarios que se usan de forma muy habitual y son comunes a la mayoría de las aplicaciones Win32, se definieron una serie de recursos estándar: Accelerator table Bitmap Cursor Dialog box Enhanced metafile Font Icon Menu Message-table entry String-table entry Version information Aparte de estos recursos estándar, existen otros tipos de recursos, los recursos a medida (custom resources) o recursos específicos de la apli- cación. Estos recursos los define el programador según sus necesidades y pueden ir desde formatos binarios que sólo tienen sentido para el programador hasta otros formatos conocidos pero que no son recursos estándar, como por ejemplo, ficheros JPEG, WAV, etc. Volviendo a Delphi, el primer pensamiento podría ser que todos los formularios que creamos desde el IDE se generarían como el recurso estándar "Dialog Box" enumerado anteriormente. Sin embargo, esto no es así. Por motivos de la tecnología "two way" del editor, la RTTI, etc., los creadores de Delphi optaron por que todos los formularios generados por Delphi, junto con los controles que contienen, se crearan como recursos a medida. Pero hay más cosas, además de incluir los recursos a medida provenientes de los formularios, a partir de la versión 3.0 Delphi incluye dos recursos a medida más: uno sobre los packages usados y otro que es la firma del entorno de programación. Este último es un recurso específico de Borland llamado DVCLAL (de Delphi VCL Access License), y es incluido automáticamente, sin intervención alguna del programador, en todas las aplicaciones hechas en Delphi. Los recursos en un programa =========================== Ya hemos dicho que los recursos son datos binarios, pues bien, cuando se incluye un recurso en un fichero PE, ya no sufre ningún tipo de manipu- lación, sino que se empotra tal cual en el ejecutable final (o lo que sea: DLL, OCX, etc., de aquí en adelante vamos a referirnos al fichero generado por Delphi como ejecutable sin perder generalidad, pero que el lector tenga en cuenta que cuando lea ejecutable, en realidad puede ser cualquier tipo de fichero PE de Win32). Por lo tanto, podemos abrir cualquier ejecutable y observar directamente los recursos, de hecho, aunque es algo demasiado conocido, podemos abrir cualquier ejecutable con un editor de recursos, y por ejemplo, cambiar todas las cadenas de texto que dicho ejecutable contuviese como recursos, y de esta forma podemos traducir cualquier programa. El API de Windows es rico en funciones para acceder y modificar (aunque algunas de estas últimas sólo funcionan en NT) estos recursos, y como veremos en un futuro próximo, la VCL nos facilita aún más el manejo de éstos. En nuestro caso, para saber si un ejecutable ha sido creado con Delphi, nos basta con abrirlo, examinar sus recursos, comprobar si tiene algún recurso a medida que se llame DVCLAL y si es así comprobar si el formato binario de este recurso coincide con la "firma de Delphi". De darse todas estas circustancias podremos afirmar que el programa ha sido realizado en Delphi. Manos a la obra =============== Lo primero que tenemos que hacer es abrir el fichero, para lo cual vamos a utilizar la función del API de Windows LoadLibraryEx. ¿Porqué usamos esta función y no la función más habitual LoadLibrary? Bien, la función LoadLibrary se usa principalmente para cargar DLLs en el espacio de direcciones de un proceso, y por tanto, además de cargarla en memoria, realiza tareas adicionales como mapearla, buscar sus puntos de entrada, perparala para ejecución, etc. En este caso, como sólo cargamos el ejecutable para examinarlo, no es necesario nada de eso, por lo que llamando a LoadLibrary con el parámetro LOAD_AS_DATAFILE evita todo el procesamiento extra y es más eficiente hModule := LoadLibraryEx(PChar(cModuleName), 0, LOAD_LIBRARY_AS_DATAFILE); siendo hModule una variable de tipo THandle. Una vez que tenemos el ejecutable a examinar cargado en memoria y refe- renciado por nuestro Handle hModule, el siguiente paso es examinar todos los recursos del ejecutable hasta encontrar los recursos a medida. Windows tiene un identificador estándar para todos los tipos estándar: RT_BITMAP, RT_STRING, RT_DIALOG, etc., los recursos a medida vienen indicados por el identificador RT_RCDATA. Para ver los distintos tipos de recursos contenidos en el ejecutable, el API de Windows nos ofrece una función de enumeración: EnumResourceTypes. Dichos tipos de funciones tienen una forma de trabajo que para muchos programadores Delphi puede resultar chocante al principio hasta que se acostumbran a ella, la sintaxis de EnumResouceTypes es: function EnumResourceTypes(hModule: HMODULE; lpEnumFunc: Pointer; lParam: Longint): BOOL; stdcall; Los parámetros que reciben son el Handle que hemos conseguido al cargar el ejecutable en memoria (hModule) y un puntero a una función "callback" (a veces las funciones callback son traducidas como retrollamadas), el tercer parámetro es un valor cualquiera de 32 bits que podemos usar (o no) para nuestros propios propósitos. La pregunta del millón es ¿qué es una función "callback"? Bien, es una función que en vez de ser llamada explícitamente por nosotros desde nuestro código, se la pasamos al sistema operativo para que éste la llame cuando lo considere oportuno, o sea, yo me defino la función lpEnumFunc, pero luego en vez de llamarla yo, le paso un puntero a la misma a Windows y Windows la usará cuando haga falta, en este caso Windows la llamará por cada tipo de recurso que encuentre en el ejecutable. Obsérvese que al ser una función llamada por Windows, y no por nosotros, seguirá los convenios de llamada de Windows, de ahí que tenga que ser declarada a su vez como stdcall. Otra pregunta que viene inmediatamente al caso es que que tipo de función es lpEnumFunc y si puede ser cualquiera. Bueno, pues no puede ser cual- quiera, tiene que ser una función del tipo: function EnumResTypeProc(hModule: THandle; szType: PChar; lParam: LongInt): Boolean; stdcall; Por cada tipo de recursos que EnumResourceTypes encuentre, Windows llamará a EnumResTypeProc con el Handle del módulo que le hemos pasado a EnumResourceTypes, el tipo de recurso encontrado y el parámetro opcional. EnumResourceType continuará enumerando los tipos de recursos encontrados hasta que los haya enumerado todos o bien hasta que EnumResTypeProc devuelva False, así que tendremos que definir nuestra función callback de forma que devuelva False una vez hayamos encontrado el tipo de recursos RT_RCDATA. Una cosa muy importante es fijarse que en la definición de EnumResourceTypes, el segundo parámetro (lpEnumFunc) es de tipo Pointer. En realidad, tal y como viene definida en Windows.pas, viene maquillada como function EnumResourceTypes(hModule: HMODULE; lpEnumFunc: ENUMRESTYPEFUNC; lParam: Longint): BOOL; stdcall; pero el tipo ENUMRESTYPEFUNC no es otra cosa que otro nombre para el tipo Pointer. Como la función espera un puntero, cualquier puntero que le pasemos a la función será admitido en tiempo de compilación, con lo que hay que tener mucho cuidado al llamar a la función. Si el puntero no es exactamente un puntero a una función con esa declaración, los resultados pueden ser cualquiera, pero le garantizo que nada agradables y con mucha probabilidad catastróficos. Más apropiado hubiera sido que en Windows.pas el tipo TEnumResTypeFunc se hubiese definido como function(hModule: THandle; szType: PChar; lParam: LongInt): Boolean; de modo que cualquier función que requiriese una variable de tipo TEnumResTypeProc como parámetro y recibiese otra cosa sería detectada en tiempo de compilación evitando males mayores. Volviendo al tema, nuestra función callback quedaría en principio como: function EnumTypes(hModule: THandle; szType: PChar; lParam: LongInt): Bool; stdcall; begin if szType = RT_RCDATA then .......... Result := False; else Result := True; end (*EnumTypes*); Como vemos, si el tipo no es el esperado devuelve True, y si el tipo es el esperado, se procesa (ya sustituiremos los puntos más adelante) y se devuelve False para que cese la enumeración. Sería llamada: hModule := LoadLibraryEx(PChar(cModuleName), 0, LOAD_LIBRARY_AS_DATAFILE); if hModule <> 0 then EnumResourceTypes(hModule, @EnumTypes, 0) Observése que usamos el operador arroba ("@") para pasarle la dirección de (o sea, un puntero a) la función EnumTypes. Ya tenemos el esqueleto de nuestro programa para abrir un ejecutable y examinarlo hasta que encontremos los recursos a medida (RT_RCDATA) si es que los hay. El siguiente paso es, si existen los recursos RC_DATA, hallar si hay alguno que se llame DVCLAL. Pues bien al igual que como acabamos de ver hay una función que enumera todos los tipos de datos de un ejecutable dado, hay otra función similar que enumera todos los nombres de los recursos de un tipo dado en un ejecutable determinado. La función es: function EnumResourceNames(hModule: HMODULE; lpType: PChar; lpEnumFunc: Pointer; lParam: Longint): BOOL; stdcall; Y el tipo de función callback que espera es del tipo: function EnumResNamesProc(hModule: THandle; szType: PChar; szName: PChar; lParam: LongInt): Bool; stdcall; Su filosofía de trabajo es muy similar a la de la que acabamos de ver. Para cada tipo de recurso que le pasemos, la función llamará a la función callback con cada uno de los nombres de cada recurso de ese tipo encontrados, hasta que esta función callback devuelva False o bien se enumeren todos los recursos de ese tipo. Ya que es en todo similar a lo que ya hemos visto, no voy a comentar más sobre esta función. La parte principal de nuestro programa quedaría: function EnumNames(hModule: THandle; szType: PChar; szName: PChar; lParam: LongInt): Bool; stdcall; begin if szName = 'DVCLAL' then begin Boolean(Pointer(lParam)^) := True; Result := False; exit; end (*if*); Result := True; end (*EnumNames*); function EnumTypes(hModule: THandle; szType: PChar; lParam: LongInt): Bool; stdcall; begin if szType = RT_RCDATA then Result := EnumResourceNames(hModule, szType, @EnumNames, lParam) else Result := True; end (*EnumTypes*); function IsDelphiModule(const cModuleName: String): Boolean; var hModule: THandle; begin Result := False; hModule := LoadLibraryEx(PChar(cModuleName), 0, LOAD_AS_DATAFILE); if GetLastError = 0 then EnumResourceTypes(hModule, @EnumTypes, LongInt(@Result)) else RaiseLastWin32Error; end (*IsDelphiModule*); Como se ve es simplemente una anidación de llamadas. Para un ejecutable en concreto, en primer lugar se llama a LoadLibraryEx para cargarlo en nuestra memoria y obtener su Handle. A continuación se llama a EnumResourceTypes con este Handle, a su vez EnumResourceTypes llama a EnumTypes con cada tipo encontrado. Si el tipo encontrado es RC_DATA, EnumTypes llama a EnumResourceNames con el Handle y el tipo, y EnumResourceNames llama a EnumNames por cada recurso de tipo RT_RCDATA encontrado. EnumNames examina si el nombre del recurso es DVCLAL, si es así se debería hacer una comprobación de que realmente el contenido binario (RAW DATA) del recurso llamado DVCLAL es realmente el que incluye Delphi y no se trata de una coincidencia (no lo hemos hecho aquí porque todavía no he explicado todavía como acceder a un recurso determinado y por lo que se explica a continuación sobre el contenido real de DVCLAL), y de ser así se para la enumeración (puesto que ya se ha encontrado). Aquí hemos empleado un truco (yo lo llamaría más bien una elegante técnica, pero es que no tengo abuelas): si recuerda, había un parámetro de 32 bits que podíamos usar a nuestro antojo, pues bien, ya que a EnumResourceTypes lo llamamos desde una función que devuelve un resultado boolean, lo que pasamos en este parámetro es un puntero a este valor, lo que equivale a pasarlo por referencia, de esta forma, como dicho parámetro es "arrastrado" a lo largo de la cadena e anidaciones, tenemos en todo momento una referencia al resultado de la función llamadora y podemos modificarlo en cualquier momento, cosa que hacemos cuando encontramos un recurso de tipo RT_RCDATA que se llame DVCLAL (lo ponemos a True). En cuanto a la estructura binaria de DVCLAL, ha tenido que ser deter- minada empíricamente, ya que ni he encontrado información al respecto ni he podido hasta el momento desentrañarla. Los valores encontrados han sido, para Delphi 3: DVCLAL RCDATA { 'A2 8C DF 98 7B 3C 3A 79 26 71 3F 09 0F 2A 25 17' } Y para Delphi 4, 5 y 6: DVCLAL RCDATA { '26 3D 4F 38 C2 82 37 B8 F3 24 42 03 17 9B 3A 83' } Un problema que no ha sido mencionado, es que al menos Borland C++ Builder 4 y 5 tienen la misma firma que Delphi 4 y superiores, por lo que el programa interpretará erróneamente como hechas en Delphi las aplicaciones realizadas en estas versiones de Builder. Tal vez comparando la información contenida en PACKAGEINFO ayudase a deshacer esta dicotomía e incluso dar información precisa sobre la versión concreta, pero hasta el momento no he sido capaz de desentrañar tampoco esta información. ¿Y ahora qué? ============= De momento ya nos hemos familiarizado con los recursos de Windows y hemos aprendido a examinar cualquier ejecutable y conocer sus recursos, en una próxima entrega veremos como aparte de la "firma de Delphi" podemos examinar todos los recursos de las aplicaciones hechas en Delphi y podemos descifrar e incluso mostrar los formularios contenidos en ellas a pesar de que en el ejecutable sólo sean un recurso binario hecho a medida. José Manuel Rodriguez (JMR) __________________ Cualquier puntualización, comentario, sugerencia o duda será bien recibida en la dirección del autor: jmr@clubdelphi.com. Todo el código mencionado en el articulo se encuentra, en forma de aplicación de ejemplo, empaquetado en el archivo zip que se adjunta a este boletín. ________________________________________________________________________ 3. USANDO ADO EN DELPHI 6 Por S.S.B. Magesh Puvananthiran <sesbaNOSPAM@hotmail.com> Este artículo pretende demostrar como usar los componentes ADO disponibles en Delphi. He escrito una simple aplicación usando componentes ADO para obtener los DSNs ("Data Source Names", nombres de orígenes de datos), nombres de tablas, nombres de campos, nombres de procedimientos y una opción para escribir una consulta, ejecutarla y mostrar el resultado en una rejilla. La función de la aplicación: Cuando ejecutes la aplicación, recuperará todos los DSNs de ODBC del sistema y los listará en un cuadro de lista. Si seleccionas un origen de datos, se te preguntará el nombre de usuario y la contraseña. Una vez que ingreses el nombre de usuario y contraseña correctos, las tablas y procedimientos disponibles en el origen de datos serán listados. Si haces clic en un nombre de tabla, se listarán sus campos. En el campo memo puedes ingresar una consulta SQL y hacer click en el botón Ejecutar para ejecutar la consulta y mostrar el resultado en la rejilla de abajo. Además puedes guardar la consulta en un archivo de texto haciendo click en el botón Guardar. Si haces clic-derecho en la lista de DSNs, encontrás una opción de menú Refrescar para refrescar los orígenes de datos ODBC. Se adjunta el código completo de la aplicación. Esto sería en realidad una simple versión de un Query Builder y le podemos agregar cuantas características queramos, y yo simplemente quería compartirlo con ustedes. Aunque hay tantos query builders disponible, yo sólo quería intentar usar los componentes ADO de Delphi y después expandir esto agregando más características. Me gustaría recibir sus ideas al respecto (en inglés). Gracias. Magesh. ________________________________________________________________________ 4. USANDO ARCHIVOS DE AYUDA HTML HELP EN TUS PROGRAMAS Por Dave Murray <irongut @ vizzavi.net> ¿Deseabas pasar de WinHelp a HTML Help en tus programas? La unidad adjunta (dmHTMLHelp.pas) convierte las llamadas WinHelp en HTML Help permitiéndose actualizarte con el mínimo esfuerzo. Este artículo está basado en un artículo que escribí para Delphi3000.com: http://www.delphi3000.com/articles/article_2994.asp Guarda esta unidad en un directorio en tu Library path (Tools| Environment Options|Library|Library Path) y agrégala a la cláusula Uses de tu proyecto. Las llamadas WinHelp ahora serán traducidas a llamadas HTML Help de modo que puedas usar tu nuevo archivo de ayuda como lo hacías antes. Especifica tu archivo *.chm como el archivo de ayuda en las opciones de proyecto (Project Options|Application|Help file). La ayuda sensible al contexto funcionará como lo hacía antes. También puedes usar TApplication.HelpCommand para enviar comandos de ayuda. Ejemplo: Application.HelpCommand(HELP_KEY, DWORD(keyData)); NOTA: * Esta unidad asigna su propio manejador del evento Application.OnHelp. NO asignes tu propio manejador del evento Application.OnHelp. * Esta unidad ignora la propiedad HelpFile de un formulario. (Delphi 4+) ________________________________________________________________________ 5. PROGRAMACIÓN ORIENTADA A OBJETOS (POO) Por Oscar Adrián Esqueda Cortés <oesqueda @ att.net.mx> Revisión histórica ================== Desde los tiempos en que la programación se hacía más que a bajo nivel en mainframes o primeras computadoras (no quiero decir la Mark1 o ENIAC, pero por ahí), claro está, pasando desde tarjetas perforadas y cintas (santos por lo que pasaron por esto), el arte de programar --es eso exactamente, un arte-- ha ido pasando de generación en generación, pero quizás el principal problema encontrado ha sido la manera o técnica de hacerlo. Cuando nuestros "ancestros" empezaron programando computadoras BIT POR BIT, hacer un programa era titánico. La computadora en realidad se programaba en Hexadecimal, y entonces crear un programa en el dialecto nativo de la computadora era para genios. El momento más importante de la historia de la programación (creo yo) se dio cuando se creó el lenguaje ensamblador, donde con unas cuantas instrucciones elaborabas un programa completo, pero claro está que tenía su inconveniente, además que el programa hecho en un lenguaje ensam- blador solo corría en computadoras donde fue diseñado, seamos honestos, no era muy claro que digamos el ensamblador. Que puedes hacer mara- villas, es otra cosa. Pero llegaron los lenguajes de nivel más alto, reduciendo los tiempos de desarrollo considerablemente, además de la curva de aprendizaje, ya que aprender un lenguaje de nivel más alto a un ensamblador era mucho más sencillo, sobre todo si tenías conocimientos del idioma inglés. En estos lenguajes tenemos a C, Pascal y Basic por nombrar sólo unos pocos conocidos. Además se intentó (primer intento frustrado) que un software hecho en un lenguaje de cuarto nivel funcionara en cualquier plataforma simplemente con volver a compilarlo. De hecho C fue el que más se acercó a esto, ya que podías tener tus programas en C de Unix y C de DOS por ejemplo. Con esto olvidamos los lenguajes y nos preocupamos en las técnicas. Empezamos con Basic en forma lineal, para aquellos que recuerden el GoTo, donde podíamos saltar de línea en línea y depurar un programa era más que mera genialidad. Después, tendríamos la aparición de procedi- mientos, vuelvo a mencionar al Basic con sus Sub xzxxxx, y la programación se convertía en Modular y podíamos separar el código en distintos archivos por procedimientos y funciones. Pero aun así no era fácil mantener un sistema así, por lo que a finales del siglo pasado (ya llovió), empezaron a idear la Programación Orientada a Objetos, POO (OOP por sus siglas en inglés). Principios básicos ================== ¿Que es POO? ------------ Básicamente no es un lenguaje ni tampoco es una técnica para hacer ventanitas y usar el Mouse, sino más bien es un conjunto de técnicas y metodologías (filosofías si quieren decirlo) para incrementar nuestra productividad al desarrollar software. El aumento de productividad significa tanto crear nuestros programas en poco tiempo, como también poder darles mantenimiento de una manera más rápida y eficaz. La POO nos permite crear código que podemos reutilizar más adelante en otro desarrollo, quizás modificando las partes que nos sean necesarias o agregando nuevas características. Las partes vitales de la POO son las clases y objetos, pero la piedra filosofal son la Encapsulación, la Herencia, y el Polimorfismo. Definición de Clase ------------------- Una clase, es simplemente una abstracción que hacemos de nuestra expe- riencia sensible. Simplemente tendemos a hacer las cosas mas fáciles para asimilarlas más rápidamente. Por ejemplo tenemos la clase Vehículo de Motor, de la cual podemos derivar automóviles, camiones, motoci- cletas, barcos, aviones, es decir, todo vehículo de motor. Recuerden como se clasificaron las especies animales, eso sería todo un ejemplo, se los dejo de tarea, tomen un libro y busquen Taxonomía. Definición de Objeto -------------------- Un objeto es un conjunto de datos y métodos que se comporta de acuerdo a las reglas de su clase. Cuando uno define una clase, debe hacer uso de ella a través de un objeto. En sí, un objeto es una variable cuyo tipo de datos es una clase. Encapsulación ------------- El concepto de encapsulación consiste en implementar el comportamiento de una clase dentro de la misma, sin que terceros ojos sepan como funciona por dentro, es decir, al crear una clase que dibuje un cuadro en la pantalla, necesitas propiedades para indicar las coordenadas y métodos para dibujarlo o quitarlo, eso es visible al usuario final de la clase, pero como funciona el método y las propiedades no lo son. Además de permitir un elevado nivel de abstracción, de esta forma el día de mañana podremos realizar modificaciones en la forma en que interna- mente funciona la clase, sin necesidad de realizar cambios en el código que hace uso de la clase. Por ejemplo, si en nuestra clase que dibuja un cuadro modificamos el método de dibujo para que use la API de Windows en vez de los métodos de un Canvas, el cambio será transparente para el programador que use la clase. Herencia -------- Crear una clase a partir de otra, pero tomando todas sus características y funcionamiento es heredar una clase, pero ¿para que heredar una clase? Tienes una clase, la cual no sabes como funciona (y aunque supieras, no te interesa), y te gustaría que hiciera un par de cosas más, y entonces pues simplemente tomamos la Clase Madre y heredamos una nueva, agregamos el par de características y listo, tenemos una nueva clase a partir de otra, que no necesariamente sabemos como se hizo. Esa es una de las características de la herencia. La implementación está oculta a los demás programadores, gracias al encapsulamiento, pero eso no significa que no puedas ampliar las virtudes de una clase. TForma = Class(TForm) Aquí estamos heredando de la clase TForm de Delphi para crear una nueva que va a tener todas las características de TForm llamada TForma. Después de heredarla podemos agregarle más propiedades a esta clase para que cumpla con lo que necesitamos. Polimorfismo ------------ La idea del polimorfismo es que una clase base tenga métodos llamados virtuales o dinámicos, que sus clases derivadas puedan reescribir ("override") para modificar así su comportamiento para adaptarlo a sus particularidades, y que dado un objeto referenciado por una variable de tipo de la clase base (variable que puede referenciar tanto un objeto de dicho tipo como de una clase derivada), al llamar a un método virtual, no se ejecute necesariamente el método tal cual está escrito para la clase base, sino el correspondiente al verdadero tipo del objeto referenciado por la variable. Veamos esto con calma. Tengo una clase que representa una circunfe- rencia: TCircunferencia = Class Public Centro: TPoint; Radio: Integer; Procedure Dibujar(Lienzo: TCanvas); End; {TCircunferencia} El procedimiento Dibujar se implementará para que dibuje una circunfe- rencia en el lienzo que se pasa como parámetro (en las coordenadas indicadas en la propiedad Centro y con el Radio indicado). También tenemos la clase TCirculo que deriva de la anterior, pero que representa un círculo: TCirculo = Class(TCircunferencia) Public Procedure Dibujar(Lienzo: TCanvas); End; {TCirculo} El procedimiento Dibujar de TCirculo se implementará de manera que en vez de sólo el borde, como en el caso de la circunferencia, se rellene toda la superficie. Ahora bien, si yo tengo una variable Figura declara como de tipo TCircunferencia para que sea genérica y pueda apuntar tanto a un objeto de clase TCircunferencia como de clase TCirculo, y realizo la llamada Figura.Dibujar; Pregunta: ¿Se dibujará una circunferencia o el círculo? En otras palabras, se llamará a TCircunferencia.Dibujar o a TCirculo.Dibujar? Independientemente de la clase de objeto a la que apunte Figura (que como se dijo puede ser TCircunferencia o TCirculo), se llamará siempre a TCircunferencia.Dibujar (o sea, se dibujará una circunferencia), porque Dibujar es un método estático y entonces el compilador asumirá que se llame al método correspondiente al tipo de la variable (que como dijimos, es de tipo TCircunferencia). Modifiquemos ahora la declaración de las clases agregando los modifi- cadores "virtual" y "override" a los métodos: TCircunferencia = Class Public : Procedure Dibujar(Lienzo: TCanvas); virtual; End; {TCircunferencia} TCirculo = Class(TCircunferencia) Public Procedure Dibujar(Lienzo: TCanvas); override; End; {TCirculo} Siguiendo con el caso anterior, esta vez al llamar a Figura.Dibujar se llamará a TCircunferencia.Dibujar o a TCirculo.Dibujar según la verda- dera clase del objeto apuntado por la variable Figura, que puede ser de clase TCircunferencia o de clase TCirculo, es decir que se dibujará sólo el borde o el círculo relleno respectivamente, según el caso. Como la verdadera clase del objeto apuntado por Figura no se conoce en tiempo de compilación, la determinación del método a llamar se realizará en tiempo de ejecución. Los métodos virtuales, que son los que permiten el polimorfismo, tienen por lo tanto un pequeño costo en el rendimiento al ser llamados (hay que determinar cuál es la dirección del método virtual que corresponde llamar según el objeto), pero nos permiten hacer generalizaciones y lograr que un objeto reaccione a un método según su clase, algo que seguramente los desarrolladores de componentes saben apreciar. Implementación ============== Campos de clase --------------- Por lo general en un objeto tenemos dos características principales: campos y métodos. Los campos son los datos de una clase. Las propiedades son nombres que se usan como si fueran campos, pero que pueden ser de sólo lectura o de lectura y escritura, y pueden referenciar a campos o a métodos. Ejemplo: TSeguridad = Class Private FUsuario, FClave:String; FLlave:Integer; Public FModificadorExterno:Integer; Property Usuario:String Read FUsuario Write FUsuario; Property Clave: String Read FCLave Write FClave; End; {TSeguridad} Los campos de clase FClave y FLlave son declarados privados, pero son accesibles públicamente a través de las declaraciones en Public de las propiedades Usuario y Clave, mientras que el campo FLlave no es visto por el que use la clase y FModificadorExterno es un campo de clase no oculto. Expliquemos esto, tenemos cuatro posibles vías para declarar un campo de clase: · Private: El campo de clase no es visto al momento de heredar o usar la clase. · Protected: El campo de clase si es visto al momento de heredar pero no al usar la clase. · Public: El campo de clase si es visto en una herencia y al momento de usar la clase. · Published: Como Public, pero es visible al Inspector de Objetos de Delphi. Las propiedades nos brindan un nivel más de abstracción y encapsula- miento, además de cierta comodidad y en muchos casos optimización. Los eventos no son parte de una clase en realidad, sino una ventaja que los RAD como Delphi y VB nos dieron para hacernos la vida mas fácil. En Delphi los eventos son propiedades (generalmente publicadas), cuyo tipo es un puntero a un método de un objeto. Métodos y funciones ------------------- Los métodos de clase que crees, deben de ser para uso de la clase. Si piensas agregar una función genérica que piensas compartir, pero que no es exclusiva (con esto quiero decir que no afecta el comportamiento de la clase), entonces no debes agregarla a la clase, ya que esto rompe la regla del encapsulamiento. Una clase no es un conjunto de funciones relacionadas, es decir que no puedes pensar en forma procedural para crear clases, ya que una clase no debe verse como una caja para código. Constructores y destructores ---------------------------- Aunque tengamos nuestras clases definidas, necesitamos tenerlas materialzadas como objetos, y para esto tenemos los constructores. Cuando diseñe su clase, recuerda agregar un constructor. Por lo regular un constructor, además de ser el que reserva memoria para el objeto, es donde inicializan los valores de los campos del mismo. Debido a la naturaleza de la sobrecarga podemos tener más de un constructor, que pueda dejarnos crear objetos de distintas maneras. Ahora, ya construimos un objeto, pero al terminar de usarlo debemos destruirlo, esto es, liberar memoria y otros recursos, y realizar el trabajo extra que necesitemos hacer cuando se termine el ciclo de vida del mismo. Debes crear un procedimiento destructor y marcarlo para esto. En Delphi, esto se logra así: Constructor Create(AOwner:TComponent); Destructor Destroy; Vean como el Constructor no devuelve un objeto, pero al estar marcado como constructor al crear un objeto, devuelve la clase instanciada (o sea, un objeto, que es una instancia de una clase): Forma := TForm.Create(Application); Para destruirlo: Forma.Destroy; Llamarse así mismo ------------------ Cuando uno crea un objeto, puede acceder a él por la variable de objeto que se hizo: Var Forma: TForm; Begin Forma := TForfm.Create(Application); Forma.ShowModal Forma.Free; End; Si leemos el código anterior, Forma es la variable y podemos acceder al objeto por medio de ésta, pero dentro de la implementación de la clase, ¿cómo hacemos esto? Existen diferentes maneras en los lenguajes: en C y Java es con la clave This, en Delphi es con Self. Procedure TForm.ShowModal; Begin ShowWindow(Self.Handle, SW_Normal); End; Como ejemplo, tenemos el código anterior, necesito acceder al objeto que esta usándose en ese momento y lo logro con el parámetro implícito Self. Llamar a la madre ----------------- Aunque suene muy ridículo el título, es verdad, cuando heredamos una clase, necesitaremos llamar a los métodos de la clase madre, digamos que heredo de la clase Forma y yo siempre dibujo una carita feliz. TForma = Class(TForm) Private Protected Procedure WMPaint(MSG:TMessage); Message WM_Paint; End; Aquí estoy creando una clase nueva llamada TForma, que esta sobrescri- biendo (override), al método WMPaint, que es un método que captura un mensaje de Windows (el mensaje WM_Paint para ser más preciso). Ésta sería la implementación: Procedure TForma.WMPaint(MSG:TMessage); Begin Self.canvas.ellipse(10, 10, 100, 100); End; Hasta aquí, lo que pasa es que al dibujarse la forma, se dibuja una elipse, pero como sobrescribí el método de dibujado de la forma, pues ya no se dibuja la misma de nuevo, por lo que tengo que llamar al método WMPaint de la clase madre de donde heredé para que haga su trabajo: Procedure TForma.WMPaint(MSG:TMessage); Begin Inherited; Self.canvas.ellipse(10, 10, 100, 100); End; En este caso Inherited llama al método que sobrescribimos y trabajo terminado. Resumen ------- La programación orientada a objetos bien utilizada nos dará herramientas más que suficientes para crear los sistemas más complejos y poderles dar mantenimiento de una manera más fácil de la que sería con otras técnicas. Claro está que como POO no es un lenguaje sino más bien una técnica, cada quien debe saber como usarla y aplicarla. Sistemas operativos, aplicaciones de escritorio, y bases de datos han tenido en la POO una manera de desarrollo más simple al implementar soluciones al usuario final. __________________ NOTA: El autor es el titular de Information Technology Consulting, una empresa de Mexico dedicada a la informática vista de manera integral, abarcando -entre otras cosas- el desarrollo de aplicaciones y sitios web, diseño gráfico y animaciones, venta, instalación y mantenimiento de equipos, redes y software, consultorías y asesorías en informática, y servicios de capacitación. >>>>> http://www14.brinkster.com/itcmx/ <<<<< ________________________________________________________________________ 6. INLINE ASSEMBLER EN DELPI (I) Por Ernesto De Spirito <eds2004 @ latiumsoftware.com> Este artículo se prepone simplemente introducirlos al mundo del "inline assembler" (ensamblador en línea) en Delphi. Apenas les dará una idea, y no se propone explicar todos los detalles de la programación en ensamblador, que probablemente requeriría un libro entero... Cuándo y por qué ================ Si le dan una mirada al código fuente de la RTL y la VCL, verán sentencias de inline assembler en varios lugares. ¿Por qué Borland programaría partes de la RTL y la VCL en ensamblador? La respuesta es bastante simple: para lograr gran velocidad de ejecución. Sabemos que el compilador produce código rápido, pero un compilador nunca puede ser mejor que un programador en ensamblador profesional. Ahora, si el ensamblador es tan bueno, ¿por qué no está toda la RTL y la VCL programada en ensamblador? La respuesta es también simple: porque es más fácil codificar, depurar, leer y mantener con un lenguaje de programación de un nivel más alto, haciendo que valga la pena sacrificar algo de velocidad por esta conveniencia. Esto ayudaría a explicar cuándo debemos utilizar código en ensamblador. En términos simples, aparte del acceso de bajo nivel al sistema, el ensamblador se utiliza cuando la diferencia en velocidad justifica la incomodidad de programar en ensamblador. Por ejemplo, en la unidad Math.pas hay mucho ensamblador, principalmente para acceso de bajo nivel al sistema (específicamente, para tener acceso a prestaciones del coprocessor), y verán muchos bloques de ensamblador, este vez para velocidad, en system.pas, sysutils.pas, y classes.pas, lo que no es extraño puesto que ellas pueden considerarse las unidades base de la RTL y la VCL. En general, los procedimientos y las funciones que muy probablemente serán llamadas bastante a menudo en un programa deben ser altamente optimizados, pero la codificación en ensamblador debe evitarse todo lo posible. Si deseamos la velocidad, antes de considerar programar en ensamblador, debemos primero mejorar el algoritmo, y después optimizar nuestro código Pascal. Si nos decidimos por el ensamblador, el código optimizado en Pascal será útil para propósitos de documentación, y puede ser que actúe como cierta clase de "código de respaldo" en caso que en el futuro tengamos problemas en mantener el código fuente en ensamblador. Los registros de la CPU ======================= Los registros de la CPU son como variables predefinidas, pero residen en la CPU, y tienen a veces propósitos especiales. No tienen un tipo, pero pueden considerarse como números enteros de 32 bits con signo o sin signo, o como punteros, dependiendo del caso. Puesto que los registros están situados en la CPU, es más rápido tener acceso a los valores almacenados en los registros que a los valores almacenados en memoria, por lo que los registros se utilizan mucho para cachear valores. Como las variables, los registros tienen nombres. Los nombres de los registros que utilizaremos más son EAX, EBX, ECX, EDX, ESI, EDI, EBP y ESP. Cada registro tiene una particularidad distintiva: - Para algunas instrucciones, la CPU se optimiza para usar el registro EAX (también conocido como "acumulador"), o por lo menos la codifica- ción de las instrucciones es más pequeña. EAX se usa en multiplica- ciones y divisiones (para los 32 bits de orden bajo del operando y del resultado), instrucciones de cadena, instrucciones de E/S de puertos, instrucciones de ajuste ASCII y decimal, y algunas instrucciones especiales (como CDQ, LAHF, SAHF y XLAT). - EBX es un registro de propósito general y es implícitamente usado por XLAT. - ECX (también conocido como "contador") tiene un uso especial con la instrucción LOOP, instrucciones de rotación y desplazamiento de bits, e instrucciones de cadena. - EDX se usa en multiplicaciones (32 bits más altos del resultado de una multiplicación, y 32 bits más altos del dividendo y resto de una división), y en algunas operaciones especiales (como CDQ). - ESI y EDI (conocidos como "índice origen" -o "source index"- e "índice destino" -o "destination index"- respectivamente) son como punteros usados por las instrucciones de cadena para identificar el origen y destino de los datos respectivamente. - EBP (conocido como "puntero base" -o "base pointer") se utiliza gene- ralmente para direccionar valores en la pila (parámetros y variables locales). - ESP (conocido como el "puntero de pila" -o "stack pointer") se utiliza para controlar la pila. Es modificado automáticamente por las instruc- ciones como PUSH, POP, CALL y RET, pero puede ser modificado por código y hasta se puede usar como registro de propósito general (siempre y cuando sea debidamente preservado). Los registros EBX, ESI, EDI, EBP, y ESP se deben preservar en los bloques de código ensamblador, significando esto que antes de usar estos registros debemos salvar sus valores en alguna parte (generalmente en la pila o en otro registro), y cuando hayamos terminado debemos restaurar sus valores originales (este salvar y restaurar implica instrucciones y por lo tanto tiempo), así que utilizaremos estos registros solamente cuando la diferencia la justifique o cuando sean inevitablemente necesarios. Probablemente hayan notado que todos los nombres de registro comienzan con la letra "E". Viene de "Extendido". En el viejo Intel 80286, los registros tenían 16 bits y tenían nombres como AX, BX, CX, etc. Estos registros todavía existen, y son los 16 bits menos significativos (las "palabras bajas") de EAX, EBX, ECX, etc., respectivamente. A propósito, los registros AX, BX, CX y DX se dividen en dos registros de 8 bits cada uno. AL, BL, CL y DL son los bytes bajos de AX, BX, CX y DX respectiva- mente, mientras que AH, BH, CH y DH son los bytes altos de AX, BX, CX y DX respectivamente. Por ejemplo, si el valor de EAX es $7AFD503C, entonces el valor de AX es $503C, el valor de AH es $50 y el valor de AL es $3C: 7A FD 50 3C AH AL /----/ AX /------------/ EAX Si por ejemplo almacenamos $99 en AH, entonces el valor de EAX sería $7AFD993C. Hay un registro especial, el registro de bandera ("flags register"), que tiene las banderas binarias que establecen las instrucciones matemá- ticas y lógicas, o explícitamente por código, y que son utilizadas por las instrucciones de salto condicional. La bandera de acarreo también se usa en algunas instrucciones de rotación de bits, y la bandera de dirección se usa en instrucciones de cadenas. Este registro no es direccionable por nombre como los otros registros, sino que puede ser salvado en la pila y restaurado usando las instrucciones de PUSHF y POPF respectivamente. Instrucciones en ensamblador ============================ Las instrucciones en ensamblador se incluyen en bloques asm..end, y tienen la forma [etiqueta:] [prefijo] opcode [operando1 [, operando2 [...]]] El opcode ("código de operación") es el nombre de la instrucción, como MOV, ADD, PUSH, etc. Las instrucciones se pueden separar por punto y coma, saltos de línea o comentarios. A propósito, los comentarios siguen el estilo de Object Pascal, es decir que el punto y coma no es el comienzo de un comentario hasta el fin de línea pues como lo es en ensamblador normal. Lo que sigue es un ejemplo de un bloque asm..end que mezcla diversas clases de comentarios y de separaciones de instrucciones: asm xchg ebx, edx; add eax, [ebx]; {Los punto y coma separan sentencias} // Los saltos de línea separan sentencias mov ebx, p sub eax, [ebx] (*los comentarios separan sentencias*) mov ebx, edx end; La convención es usar saltos de línea para separación, así: asm xchg ebx, edx add eax, [ebx] mov ebx, p sub eax, [ebx] mov ebx, edx end; En el código de fuente de la VCL verán que los opcodes y los nombres de registro están escritos en mayúsculas, y las instrucciones están indentadas ocho caracteres, pero no seguiremos esa convención aquí. Los bloques asm..end pueden ocurrir en cualquir parte del código fuente donde puede ocurrir una sentencia Pascal, y podemos escribir procedi- mientos y funciones 100% en ensamblador usando "asm" en vez de "begin", de esta forma: procedure prueba; asm // sentencias ensamblador end; Notemos que estas dos implementacions son diferentes: function f(parámetros): tipo; begin asm // sentencias ensamblador end; end; function f(parámetros): tipo; asm // sentencias ensamblador end; La razón es que el compilador realiza ciertas optimizaciones cuando implementamos procedimientos y funciones totalmente en ensamblador (sin usar el bloque begin..end). Las etiquetas ("labels") deben declararser en la sección Label, como en cualquier código Object Pascal, a menos que sean prefijadas con "@@": function EsNumeroMagico(x: integer): boolean; asm cmp eax, NumeroMagico je @@Bingo xor eax, eax ret @@Bingo: mov eax, 1 end; Las etiquetas prefijadas con "@@" son locales al bloque de ensamblador en el que se las usa. Esto generará un error de compilación: begin .... asm .... @@destino: .... end; .... asm .... jnz @@destino // Error .... end; .... end; Para corregirlo, debe usarse una etiqueta convencional, local a la función o procedimiento: label destino; begin .... asm .... destino: .... end; .... asm .... jnz destino // Correcto .... end; .... end; Operandos ========= A veces un operando u operandos son implícitos. Por ejemplo, la instruc- ción CDQ ("Convert Dword to Qword") aparentemente no toma operandos, pero trabaja con EDX y EAX (extiende el bit más significativo de EAX, el bit de "signo", en EDX, de modo que EDX:EAX representará el entero en EAX convertido a Int64, donde EAX contendrá los 32 bits menos significa- tivos, y EDX los 32 32 bits más significativos). Para la mayoría de las instrucciones, los operandos pueden ser registros. Por ejemplo mov eax, ecx // EAX := ECX; copia el valor de ECX en EAX. Muchos operandos pueden ser valores inmediatos. Por ejemplo: mov eax, 5 // EAX := 5; mov eax, 2 + 3 // Constante resuelta en tiempo de compilación mov al, 'A' // El código ASCII de 'A' es $41 (65) mov eax, 'ABC' // Equivalente a MOV EAX, $00414243 Muchos operandos pueden ser referencias de la memoria. Por ejemplo: mov [ebx], eax // EBX^ := EAX; Las referencias de la memoria pueden tener varias formas: mov eax, [$000FFFC] // Dirección absoluta mov eax, [ebx] // Registro mov eax, [ebp-12] // Registro más/menos desplazamiento constante mov eax, [ebp+ebx] // Registro más desplazamiento en registro mov eax, [ebp+ebx+8] // Registro más desplazamiento en registro // más/menos desplazamiento constante mov eax, [ebp+ebx*4] // Registro más desplazamiento en registro // multiplicado por una constante mov eax, [ebp+ebx*4+8] // Registro más desplazamiento en registro // multiplicado por una constante, // más/menos desplazamiento constante El uso de identificadores Pascal se traduce a una de las formas de arriba: mov eax, parametro // mov eax, [ebp + desplazamiento_constante] mov eax, varlocal // mov eax, [ebp - desplazamiento_constante] mov eax, varglobal // mov eax, [dirección_absoluta] call nombreproc // call dirección_absoluta Primer ejemplo ============== Estamos listos para aprender algunos opcodes con un par de ejemplos. Podemos comenzar con una función sencilla: function f(x: integer; y: integer): integer; // f(x,y) = (-x-y+5)*7 { begin Result := (-x - y + 5) * 7; end; } asm // Los parámetros se pasan en EAX (x) y EDX (y); neg eax // EAX := -EAX; // EAX = -x sub eax, edx // EAX := EAX - EDX; // EAX = -x-y add eax, 5 // EAX := EAX + 5; // EAX = -x-y+5 imul 7 // EAX := EAX * 7; // EAX = (-x-y+5)*7 end; Los primeros tres parámetros (de izquierda a derecha) se pasan en EAX, EDX y ECX. Para los métodos, el primer parámetro es Self (pasado en EAX), y el primer parámetro declarado explícitamente es en realidad el segundo parámetro (pasado en EDX), y el segundo parámetro explícito sería en realidad el tercero (se pasa en ECX). El valor de retorno se debe poner en EAX para los valores ordinales de 32 bits (AX y AL se deben utilizar para devolver valores ordinales de 16 y 8 bits respectivamente). Los comentarios explican los opcodes bastante bien, pero para IMUL tenemos que agregar dos cosas: * IMUL considera los operandos (EAX y 7 en el ejemplo) como enteros con signo (debemos utilizar MUL cuando los operandos no tengan signo) * El resultado de la multiplicación es un número entero de 64 bits (los 32 bits más significativos del resultado quedan en EDX). Las multiplicaciones son bastante costosas en términos de tiempo de CPU, y a veces es más rápido substituirlas con desplazamientos de bits (cuando multiplicamos y dividimos por potencias de dos), adiciones y sustracciones. Por ejemplo: a * 7 = a * (8 - 1) = a * 8 - a = a * 2^3 - a a * 7 = a shl 3 - a En vez de IMUL 7, podemos hacer lo siguiente: mov ecx, eax // ECX := EAX; // ECX = -x-y+5 shl eax, 3 // EAX := EAX shl 3; // EAX = (-x-y+5)*8 sub eax, ecx // EAX := EAX - ECX; // EAX = (-x-y+5)*8 - (-x-y+5) // EAX = (-x-y+5)*7 Veamos otro ejemplo: function resto(x: integer; y: integer): integer; // Devuelve el resto de x dividido y { begin Result := x mod y; end; } asm // Los parámetros se pasan en EAX (x) y EDX (y); mov ecx, edx // ECX := EDX; // EDX = y cdq // EDX:EAX := Int64(EAX); // EAX = x idiv ecx // División entera con signo de 32 bits: // EAX := Int64(EDX:EAX) div integer(ECX); // EDX := Int64(EDX:EAX) mod integer(ECX); mov eax, edx // Result := EDX; // resto end; La pila ======= Cuando se carga un programa, se le asigna una pila, que es una región de memoria usada como una estructura LIFO (Last In, First Out -o "último en entrar, primero en salir"), controlada por el registro ESP, que apunta a la cima de la pila. ESP comienza apuntando al final de la región, así que cada vez que "empujamos" (push) un valor de 32 bits en la pila, el registro ESP se decrementa en 4 (bytes), y el valor se almacena en la ubicación apuntada por ESP. | | +-----------+ | | +-----------+ | $01234567 | <- ESP +-----------+ | | PUSH $89ABCDEF // SUB ESP,4; MOV [ESP],$89ABCDEF | | +-----------+ | $89ABCDEF | <- ESP +-----------+ | $01234567 | +-----------+ | | Inversamente, cuando "soltamos" (pop) un valor de 32 bits de la pila, el valor se recupera de la ubicación apuntada por ESP, y a ESP se lo incrementa en 4 (bytes). POP EAX // MOV EAX,[ESP]; ADD ESP,4 | | +-----------+ +-----------+ | $89ABCDEF | EAX | $89ABCDEF | +-----------+ +-----------+ | $01234567 | <- ESP +-----------+ | | La pila se usa para almacenar la dirección de retorno de procedimientos y funciones, los parámetros, las variables locales y los resultados intermedios. En el ejemplo siguiente utilizamos la pila para salvar el valor de un registro para su uso posterior: function IntDiv(x: integer; y: integer; r: pinteger = NIL): integer; // Devuelve el cociente entero de x / y, y el resto en r { begin Result := x div y; if r <> NIL then r^ := x mod y; end; } asm // Los parámetros se pasan en EAX (x), EDX (y) y ECX (r) push ecx // Salvar ECX (r) para uso posterior mov ecx, edx // ECX := EDX; // ECX = y cdq // EDX:EAX := Int64(EAX); // EAX = x idiv ecx // División estera de 32 bits con signo: // EAX := Int64(EDX:EAX) div integer(ECX); // EDX := Int64(EDX:EAX) mod integer(ECX); pop ecx // Restaura ECX (ECX := r) cmp ecx, 0 // if ECX = NIL then jz @@end // goto @@end; mov [ecx], edx // ECX^ := EDX; // resto @@end: // etiqueta local (precedida por "@@") end; Nótese que por cada PUSH que hagamos, debemos hacer un POP, de modo que el valor de ESP quede sin cambios (ESP es uno de los registros que debemos preservar). La instrucción CMP resta el segundo operando del primero (ECX-0 en este caso), como la instrucción SUB, pero el resultado no se almacena en ningún lado, pero sí la "bandera Cero" (Zero flag) será establecida (encendida) o limpiada (apagada) dependiendo de si el resultado es cero o no, tal como sucede con todas las instrucciones matemáticas y lógicas (excepto en ciertos casos). Podemos sacar partido de este hecho y en vez de escribir cmp ecx, 0 podemos escribir or ecx, ecx // ECX := ECX or ECX; El resultado de ECX Or ECX es ECX mismo, así que el valor almacenado en ECX es el mismo que tenía, pero -como dijimos arriba- la bandera Cero será encendida si el resultado es cero (es decir, si ECX era cero). Usamos OR en vez de CMP porque OR opera con dos registros, tomando dos bytes para codificar la instrucción, mientras que CMP opera un registro con un valor inmediato de 8 bits, tomando tres bytes para codificar, pero CMP no escribe en el destino (ECX en este caso) como sí lo hace OR, lo que a veces resulta importante al escribir código optimizado para Pentium. TEST ECX, ECX es habitualmente preferido porque combina lo mejor de ambos mundos (dos bytes para codificar y no escribe en el registro: sólo realiza una operación de AND bit a bit para establecer las banderas en base sl resultado, el que es descartado). JZ ("Jump if Zero", o "salto si cero"), va (salta) a la etiqueta indicada como operando si la bandera Cero está encendida, o continúa con el flujo normal de la ejecución si la bandera Cero está apagada. Pasando parámetros en la pila ----------------------------- Volvamos a la pila. Dijimos que los primeros tres parámetros de una función se pasan en EAX, EDX y ECX, pero ¿qué pasa si tenemos más parámetros? Los parámetros adicionales se pasan en la pila, de izquierda a derecha, así que el último parámetro, será el primero en la pila. Supongamos que tenemos una función function Sum(a, b, c, d, e: integer): integer; begin Result := a + b + c + d + e; end; y que deseamos hacer la llamada Sum(1,2,3,4,5); En ensamblador, sería como esto: mov eax, 1 mov edx, 2 mov ecx, 3 push 4 push 5 call Sum La instrucción CALL "empuja" (push) el valor de retorno en la pila y salta a (comienza a ejecutar) la función. La instrucción RET (RETurn) (generada por el compilador cuando se llega al final de la función) "suelta" (pop) esta dirección de la pila y salta a ella para continuar la ejecución desde allí. Nótese que empujamos los parámetros en la pila, pero que no los soltamos después. Esto es porque, excepto en la convevión de llamada CDECL, la limpieza de los parámetros es responsabilidad de la función llamada, no del llamador. Para limpiar los parámetros, la instrucción RET se usa con un operando que indica la cantidad de bytes que ESP debe ser incre- mentado, 8 en este caso (ESP fue restado 4 bytes por parámetro cuando éstos fueron empujados). El compilador se encarga de esto, así que nosotros no tenemos que preocuparnos por ello, pero si ven la ventana de depuración de la CPU y se preguntan qué es ese RET $08, ahora ya saben. Al entrar a Sum, la pila en teoría se verá así: | | +-----------+ |Dir_Retorno| <- ESP +-----------+ | $00000005 | (parámetro e) +-----------+ | $00000004 | (parámetro d) +-----------+ | | Cuando una función tiene parámetros en la pila (o variables locales), el compilador genera algunas instrucciones que se llaman "marco de pila" ("stack frame"). Al entrar a la función (en el "asm"), EBP se empuja en la pila (para preservarlo) y se le asigna ESP, y antes de dejar la función (en el "end;"), el valor original de EBP se restaura de la pila: function Sum(a, b, c, d, e: integer): integer; asm // push ebp; mov ebp, esp; .... end; // pop ebp; ret 8; De este modo, cuando entramos a Sum, la pila en realidad se vería así: | | +-----------+ | EBP orig. | <- EBP, ESP +-----------+ |Dir_Retorno| +-----------+ | $00000005 | <- EBP+8 (parámetro e) +-----------+ | $00000004 | <- EBP+12 (parámetro d) +-----------+ | | En [EBP] encontramos el valor original de EBP que empujamos en la pila para preservarlo al construir el marco de pila, en [EBP+4] encontramos la dirección de retorno del procedimiento, y en [EBP+8] encontramos el último parámetro (el último parámetro es empujado al último, y por entonces es el primer parámetro en la pila). El siguiente parámetro (de derecha a izquierda) está en [EBP+12] y así sucesivamente si tuviéramos más parámetros. Ahora escribamos la función Sum en ensamblador: function Sum(a, b, c, d, e: integer): integer; { begin Result := a + b + c + d + e; end; } asm add eax, b add eax, c add eax, d add eax, e end; Notemos que en el bloque asm..end utilizamos "b", "c", "d" y "e" en vez de "EDX", "ECX", "[EBP+12]" y "[EBP+8]" respectivamente. Podemos hacer eso porque el compilador realizará las substituciones apropiadas. Variables locales en la pila ---------------------------- Si nuestra función tiene variables locales, en funciones completas en inline assembler el compilador hará espacio para ellas en la pila moviendo el puntero de pila (ESP), así que el marco de pila para una función con dos variables locales enteros se verá así: push ebp mov ebp, esp sub esp, 8 // Mueve ESP como si hubiésemos "empujado" 8 bytes ... add esp, 8 // Mueve ESP como si hubiésemos "soltado" 8 bytes pop ebp Para propósito de ejemplo, he aquí una variante de la función Sum introducida arriba, pero usando dos variables locales: function SumL(a, b, c, d, e: integer): integer; var f, g: integer; { begin f := b + c; g := d + e; Result := a + f + g; end; } asm // push ebp; mov ebp, esp; sub esp, 8; add edx, ecx mov f, edx // b + c mov edx, d add edx, e mov g, edx // d + e add eax, f add eax, g end; // add esp, 8; pop ebp; ret 8 Dentro de la función, la pila se vería así: | | +-----------+ | var. g | <- EBP-8, ESP +-----------+ | var. f | <- EBP-4 +-----------+ | EBP orig. | <- EBP +-----------+ |Dir_Retorno| +-----------+ | Param e | <- EBP+8 +-----------+ | Param d | <- EBP+12 +-----------+ | | Lo que sigue ============ En la continuación de este artículo aprenderemos más instrucciones, y veremos como pasar y devolver otros tipos de parámetros, y como trabajar con arreglos, acceder a campos de un registro u objeto, llamar a métodos y más. _________________________________________________________________________ 7. FOROS Recordamos a los suscriptores las direcciones de nuestros foros. Para unirse a algún foro, lo más recomendable es hacerlo desde la web para así tener acceso a todas las áreas del foro y la configuración de las opciones de suscripción, pero también es posible suscribirse por email. Para suscribirse desde la web es necesario poseer un ID de Usuario de Yahoo! (si no tienes el tuyo, puedes conseguirlo gratis registrándote como usuario de Yahoo!). * Delphi-abierto. Programación en Delphi (todos los niveles). Si estás en la etapa de aprendizaje o si no te agradan los foros discriminados por niveles, este foro es para ti. http://espanol.groups.yahoo.com/group/delphi-abierto Suscripción: http://espanol.groups.yahoo.com/group/delphi-abierto/join delphi-abierto-subscribe@gruposyahoo.com * Delphi-intermedio. Programación en Delphi (nivel intermedio). Si ya sabes mucho de Delphi, pero todavía te falta un largo trecho para ser un gurú (o no tanto), este foro es para ti. http://espanol.groups.yahoo.com/group/delphi-intermedio Suscripción: http://espanol.groups.yahoo.com/group/delphi-intermedio/join delphi-intermedio-subscribe@gruposyahoo.com * Delphi-avanzado. Programación en Delphi. Sólo para gurús. http://espanol.groups.yahoo.com/group/delphi-avanzado Suscripción: http://espanol.groups.yahoo.com/group/delphi-avanzado/join delphi-avanzado-subscribe@yahoogroups.com * GrupoKylix. Programación en Kylix. http://espanol.groups.yahoo.com/group/GrupoKylix Suscripción: http://espanol.groups.yahoo.com/group/GrupoKylix/join GrupoKylix-subscribe@yahoogroups.com * FreePascal-es. Programación en Free Pascal (freepascal.org). http://espanol.groups.yahoo.com/group/freepascal-es Suscripción: http://espanol.groups.yahoo.com/group/freepascal-es/join freepascal-es-subscribe@yahoogroups.com * Desarrolladores-Software. Un lugar para tratar todos aquellos temas relacionados con el desarrollo de software y su comercialización, y para compartir experiencias en el ámbito laboral, profesional o comercial con otros. http://es.groups.yahoo.com/group/desarrolladores-software Suscripción: http://es.groups.yahoo.com/group/desarrolladores-software/join desarrolladores-software-subscribe@yahoogroups.com * Componentes. Un foro para buscar/recomendar componentes de software (componentes VCL y CLX, objetos ActiveX, librerías DLL, etc.), así como utilidades, tutoriales, información, etc. http://espanol.groups.yahoo.com/group/componentes Suscripción: http://espanol.groups.yahoo.com/group/componentes/join componentes-subscribe@yahoogroups.com _________________________________________________________________________ 8. DELPHI EN LA RED Por Dave Murray <irongut @ vizzavi.net> Componentes, librerías y aplicaciones ===================================== Shareware/Comercial ------------------- * llPDFLib v1.1 - por llionsoft, Shareware ($70, $280 con fuentes) llPDFLib en una biblioteca en puro Object Pascal para crear documentos PDF. No usa ninguna DLL ni software externo de terceras partes para generar ficheros PDF. La librería consiste del componente TPDFDocument con propiedades y métodos como los del TPrinter de Delphi, pero diseñado para generar un fichero PDF. http://www.llion.net/ * TPasScript v6.52 - Alexander Baranovsky, Shareware($80) (DELPHI/KYLIX) Un intérprete del lenguage Object Pascal que soporta todos los tipos de datos de Object Pascal, excepto interfaces. El componente TPasScript le permite incrustar el intérprete en su aplicación Delphi, de modo que pueda extender y adaptar la aplicación son tener que recompilarla. Usando TPasScript usted puede escribir y ejecutar scripts, llamar rutinas definidas para script, llamar rutinas en una DLL, e importar clases, rutinas y variables definidas en Delphi. http://users.ints.net/virtlabor * MaxSpace - by Zecos Software, Shareware ($15) MaxSpace hace que usar Delphi y C++ Builder sea más cómodo tornando la barra de herramientas del IDE y el Inspector de Objetos en barras de herramientas auto-ocultables, otorgándole increíble libertad para la edición de programas y el diseño de formularios. http://www.zecos.com/maxspc Freeware -------- * Free Pascal Compiler v1.0.6 - GPL This is un apdated an fixed version of the previous v1.0 release v1.0.4 which was released in early 2001. http://www.freepascal.org/sdown.html * TRealSpinEdit v1.1 - Igor Popov, FREEWARE with source (DELPHI/KYLIX) Based on Visual C++ TSpinEdit, for all who need Value to be a float type. Now with negative value support. http://igp.dax.ru * FastReport CLX v2.46 - by Alexander Tzyganenko, FREEWARE with source The first powerful cross-platform report generator for Delphi + Kylix. Create highly efficient cross-platform reports for with a minimum of coding. Development environment includes: Visual Report Designer, Visual Dialog Designer, Object Inspector, and Component Palette. http://www.fast-report.com * DBDateTimePicker - by Stefano Carfagna, FREEWARE with source Database aware DateTimePicker component for "DateTime" fields. http://www.data-ware.it Artículos, trucos y consejos ============================ * The Future of the Borland Database Engine (BDE) and SQL Links The Borland RAD Team reveals plans for future database access in Delphi and C++Builder. You need to read this if you haven't already! http://community.borland.com/article/0,1410,28688,00.html * Top Picks: Reporting Tools - by Zarko Gajic Easily create complex reports that are linked directly into your EXE. Most of the tools from this list provide all the means for you to develop reports including a report engine, designer, previewer, etc. http://delphi.about.com/library/toppicks/aatpreporting.htm * Choosing the best Help Format for your project - by Meredith Little No developer wants to work on documentation but it is a necessary evil. This article explores the concept of developing help and describes the available help formats. http://builder.com.com/article.jhtml?id=u00220020429lit01.htm * Choose the right Tool for creating Online Help - by Meredith Little Creating online help is often seen as a burden, but many tools are available to simplify the process. Here's a look at some choices. http://builder.com.com/article.jhtml?id=u00220020430lit01.htm * Functional vs. Design in documentation - by William T. Kelly Do you need a design or functional specification? It depends on who your audience is and where you are in the product development cycle. http://builder.com.com/article.jhtml?id=u00420020503WTK01.htm * How to make a polygon hole on a form? http://www.swissdelphicenter.ch/en/showcode.php?id=995 * How to get the associated icon of a file shortcut? http://www.swissdelphicenter.ch/en/showcode.php?id=1108 * How to list all Subdirectories of a Directory? http://www.swissdelphicenter.ch/en/showcode.php?id=1125 * How to get the Windows Version? http://www.swissdelphicenter.ch/en/showcode.php?id=1126 * How to color text in a TRichEdit? http://www.swissdelphicenter.ch/en/showcode.php?id=1129 * How to use the IP Address Control in a Form? http://www.swissdelphicenter.ch/en/showcode.php?id=1132 * How to display the exported Dll functions? http://www.swissdelphicenter.ch/en/showcode.php?id=1133 * How to display the 'Choose Computer' dialog? http://www.swissdelphicenter.ch/en/showcode.php?id=1135 * How to use steganography? http://www.swissdelphicenter.ch/en/showcode.php?id=1139 * How to resolve environment variables? http://www.swissdelphicenter.ch/en/showcode.php?id=1140 * How to determine, if a Edit field has password characters? http://www.swissdelphicenter.ch/en/showcode.php?id=1141 * How to determine the user's logon domain? http://www.swissdelphicenter.ch/en/showcode.php?id=1142 * How to highlight HTML-Tags in TRichEdit? http://www.swissdelphicenter.ch/en/showcode.php?id=1143 * How to store more than 64 KB in a TRichEdit? http://www.swissdelphicenter.ch/en/showcode.php?id=1144 * How to select a TreeView node with the right mouse button? http://www.swissdelphicenter.ch/en/showcode.php?id=1145 * How to assign an event handler to all components? http://www.swissdelphicenter.ch/en/showcode.php?id=1148 * How to crack a URL into its component parts? http://www.swissdelphicenter.ch/en/showcode.php?id=1149 * How to delete the 'Temporary Internet Files'? http://www.swissdelphicenter.ch/en/showcode.php?id=1150 * How to get the windows file type? http://www.swissdelphicenter.ch/en/showcode.php?id=1153 * How to convert a partition/file size to a String? http://www.swissdelphicenter.ch/en/showcode.php?id=1155 * How to implement Forward/Back/Cancel buttons in TWebbrowser? http://www.swissdelphicenter.ch/en/showcode.php?id=1158 * Catch debug information of an application - by Tommy Andersen Ever wanted to read an application's debug information? http://www.delphi3000.com/articles/article_3216.asp * Hide application in Windows 2000 - by Eber Irigoyen Hiding your application from the taskbar and "Applications". http://www.delphi3000.com/articles/article_3220.asp * Retrieve email addresses from Outlook mails - by Alain Godinas-Andrien http://www.delphi3000.com/articles/article_3222.asp * Maping / Unmaping virtual drives - by Eber Irigoyen Two functions that let you create Virtual Drives for commonly used folders (local or network folders) so they appear on "My Computer" as a new drive for quick access. Like DOS SUBST command. http://www.delphi3000.com/articles/article_3224.asp * What Custom Compiler messages can do for you - by Yoav Abrahami Create reminders that generate compiler warnings and errors. http://www.delphi3000.com/articles/article_3228.asp * How do you subclassing a versatile TList? - by Max Kleiner You can use a TList almost for everything, so an own class leads to better design and maintainability so this article shows how and why. http://www.delphi3000.com/articles/article_3229.asp * Modifying Contents of the Taskbar from Delphi - by Igor Kurilov Using ITaskbarList interface. http://www.delphi3000.com/articles/article_3230.asp * Easy Parsing an XML File - by Max Kleiner How do you get the elements from an XML file? Using MS XML DOM parser. http://www.delphi3000.com/articles/article_3231.asp * Using Autohint - by David Knight Using hints without the flybys that cover data and often annoy users. http://www.delphi3000.com/articles/article_3234.asp * Function DBGridToHtmlTable - by Zswang Wangjihu http://www.delphi3000.com/articles/article_3236.asp Tutoriales ========== * Kylix Tutorial Series - by Brian Long Ten part tutorial series previously published in Linux Format. http://www.blong.com/articles.htm#KylixTut * Remedial XML: Say HELLO to DOM - by Lamont Adams Get acquainted with W3C's Document Object Model (DOM) in this part of the remedial XML series. A language-neutral look at how you can put this model to work. http://builder.com.com/article.jhtml?id=u00220020522adm01.htm Otros enlaces ============= * VCL Scanner 2002 Sweepstakes winners - by Anders Ohlsson Thanks to all who participated in the VCL Scanner 2002 project! http://community.borland.com/article/0,1410,28665,00.html ________________________________________________________________________ ¡TÚ PUEDES AYUDARNOS! Por favor danos una mano y ayúdanos a llegar a los 10.000 suscriptores en los próximos meses. Una forma en que puedes ayudarnos es enviando este enlace a tus amigos: http://www.latiumsoftware.com/es/pascal/index.php boletin-pascal-subscribe@gruposyahoo.com Otra forma es votándonos en alguno de estos rankings para darle más visibilidad a nuestro sitio web y aumentar así el número de suscrip- ciones al boletín: http://www.sandbrooksoftware.com/cgi-bin/TopSite2/rankem.cgi?id=latium http://news.optimax.com/topdelphi/links.exe/click?id=70C517ECAE6E http://www.programmingpages.com/?r=latiumsoftwarecomenpascal http://www.top219.org/cgi-bin/vote.cgi?delphi&83 http://top100borland.com/in.php?who=20 http://top200.jazarsoft.com/delphi/rank.php3?id=latium http://213.65.224.200/cgi-bin/toplist.cgi/hits?Id=80 http://www.programacion.net/votar-enlace.php?id=474 http://www.lawebdelprogramador.com/buscar/enlace.php?id=615 Por favor vota. Son sólo unos segundos para ti que REALMENTE pueden hacer la diferencia. Necesitamos tu ayuda para poder continuar. ________________________________________________________________________ Si no has recibido el archivo con el código fuente completo de los ejemplos que se presentan en este boletín, puedes descargarlo de la siguiente dirección: http://www.latiumsoftware.com/descarga/p0036.zip ________________________________________________________________________ Página principal: http://www.latiumsoftware.com/es/pascal/index.php Página del grupo: http://espanol.groups.yahoo.com/group/boletin-pascal/ Para suscribirse / apuntarse: boletin-pascal-subscribe@gruposyahoo.com Para cancelar / removerse: boletin-pascal-unsubscribe@gruposyahoo.com Para reportar problemas con la suscripción: eds2004 @ latiumsoftware.com ________________________________________________________________________ Este boletín se provee "TAL Y COMO ESTA", sin garantía de ninguna clase. Su uso implica la aceptación de nuestros términos de licencia y de la ausencia de garantía que puedes leer en nuestro sitio web. Allí también encontrarás una nota sobre marcas registradas. Te animamos a que redis- tribuyas este boletín, siempre y cuando lo hagas en forma completa (incluyendo la información de copyright), sin modificaciones y de manera gratuita. Los artículos son copyright de sus respectivos autores y se reproducen aquí con el permiso de los mismos. ________________________________________________________________________ Latium Software http://www.latiumsoftware.com/es/index.php Copyright (c) 2002 por Ernesto De Spirito. Todos los derechos reservados ________________________________________________________________________ |
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
¿Errores? ¿Omisiones? ¿Comentarios? Por favor contáctanos!






