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/delphicb/printsuite/
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
eds2008 @ 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-2006 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 <eds2008 @ 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://www.paxscript.com/
* 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://articles.techrepublic.com.com/5100-10878_11-5035298.html
* 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://articles.techrepublic.com.com/5100-10878_11-5147519.html
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.programmingpages.com/?r=latiumsoftwarecomenpascal
http://top100borland.com/in.php?who=20
http://www.lawebdelprogramador.com/buscar/votar.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/es/file.php?id=p36
________________________________________________________________________
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: eds2008 @ 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
________________________________________________________________________
|