Utilización de múltiples lenguajes en un programa

Anuncio
Utilización de múltiples lenguajes en un programa
Al utilizar múltiples lenguajes en un programa se necesitan conocimientos y posibilidad del
parte de compilador de utilizar los siguientes convenciones:
1. Convenciones con respecto a cambios del nombre de la función.
Mirar Anexo A.
2. Convenciones de cómo se pasan los parámetros entre las funciones.
Por ejemplo – en la pila dependiente del tamaño de los tipos o en los registros; con números
desde 8 hasta 16 (HP 9000); en el principio del programa que se ejecuta (Real Time
Celatron) etc. El Borland C los parámetros siempre se pasan por la pila en el orden reverso
con respecto al prototipo. Nótese que esta descripción no es completa, tal como la
granularidad de la pila y los tamaños de los punteros dependen del modelo de la memoria
que se utilice.
3. Convenciones de como se pasa el resultado de una función al programa que llama.
Por ejemplo – si el resultado es del tamaño 1-2 bytes en AX, si es del tamaño 3-4 bytes en
DX:AX si es un numero real en ST0 del 80x87, si es mas de 4 bytes - en la pila (Borland
C).
4. Convenciones de como se guarda, recupera y utiliza el contexto de ejecución en una
función. Como se reparte esta actividad entre la función que llama y la función que se
ejecuta.
El general, el contexto de ejecución tiene que recuperase (salvo los recursos que se utilizan
para pasar el resultado). El contexto de ejecución de un procesador contiene los registros del
mismo, los registros y (pseudo) pilas de sus co - procesadores, el estado de su periferia, el
estado de la pila, etc. Sin embargo al programar en lenguajes de alto nivel no nos interesan
todos los recursos del sistema y todo el marco de ejecución. Además sería muy lento
guardar todo el contexto de ejecución y recuperarlo. Por lo tanto el contexto de ejecución se
limita sólo a unos pocos recursos necesarios para guardar la integridad del programa.
Utilizando BorlandC el contexto consiste en SI, DI, BP, SP, DS, SS. Si desde nuestra
función se llama a otra función estos registros no cambiaran. En cambio, si escribimos una
función para utilizarla desde programas con BorlandC tenemos que preservar los valores de
estos registros. Los registros SI y DI se utilizan como variables de clase de memoria
register.
Tal como las convenciones son muchas y dependen del sistema operativo, del tipo de
compilador, del lenguaje de programación etc., es más importante saber cómo conseguir la
información necesaria. Lo más fácil el mirar en la documentación si esta ultima se proporciona
con el compilador. Sin embargo la documentación no siempre esta disponible o contiene
demasiados detalles para resolver el problema y por lo tanto es inútil.
Una manera fácil de conseguir la información necesaria es escribiendo un programa que
contiene un dummy o stub de la función de bajo nivel y compilarla con opción de línea de
comando que produce código de ensamblador (normalmente la opción es –S o –s).
Después se mira el código producido por el compilador (con extensión ASM o s). Podemos
utilizar el código generado por el compilador como punto de partida para escribir nuestro
código. El error más frecuente que se comete en esta manera es no guardar del todo el contexto
del procesador. Por lo tanto es aconsejable incluir en el stub suficientes variables register con
todos los tipos que vamos a utilizar y compilar sin optimización o si la optimización es
inevitable incluir código suficiente para que los variables no desaparezcan al no utilizarlas.
Las palabras claves de C: cdecl, PASCAL, extern “C” etc.
Para que sirven: Para poder llamar al una función de compilador de otro lenguaje o directo al
sistema operativo.
Por qué son necesarias: Son necesarias porque los diferentes compiladores tienen diferentes
convenciones de llamada de funciones e incluso algunos de ellos especifican el método de
llamada y el manejo de la pila en la definición del lenguaje.
Detalles:
PASCAL: probablemente las convenciones más compiladas.
1. Los parámetros se pasan en la pila en el mismo orden de la declaración (Esto
significa que en el tope de la pila esta el último argumento).
2. La pila se libera en la función que se llama. Esto significa que si se declara
incorrecto en el modulo principal el programa casca por desincronización de la pila.
3. El resultado se comunica en la misma manera que en C.
4. El contexto de la mayoría de los compiladores 80x86 contiene:
SP, BP, DS, SS o ESP, EBP, DS y SS.
Se utiliza:
1. Al llamar a una función desarrollada en PASCAL.
2. Al escribir una función en C que va a utilizarse desde un programa en PASCAL.
3. En entorno Windows: Al llamar una función de una DLL o al escribir una función
que se exporta de una DLL (para escribir funciones de DLL hay métodos mejores).
En particular al llamar las funciones del KERNEL.DLL.
cdecl – Esta es la convencía normal de llamada de una función en C.
1. Los parámetros se pasan el la pila en orden reverso de la declaración.
2. La pila se libera del programa que llama después de utilizar el resultado si este
ultimo es mas de 4 bytes.
3. El contexto esta compuesto de los siguientes registros: SP, BP, SI, DI, DS, SS, ES y
los registros de 80x87.
Se utiliza:
1. Si una función en C se llama desde un programa en C++ (en combinación con
extern “C”).
2. Para forzar la transformación del nombre de la función según las convenciones en C
(normalmente añadiendo un _ antes del nombre de la función). Esto es necesario si
el compilador tiene opciones que evitan la conversión de los nombres de las
funciones.
3. Para decir explícitamente que la función esta escrita en el lenguaje de maquina si el
compilador utiliza por defecto pseudo código (hoy en día solo para unos
microcontroladores con poca memoria interna).
Anexo A
Transformación de nombres de funciones de alto nivel:
Por qué es necesaria:
No permitir conflictos de nombres de bajo nivel
Casi todos los compiladores de alto nivel tienen posibilidad de generar código fuente en
ensamblador. Este código se utiliza en general en dos maneras: En el tiempo de desarrollo para
comprobar el compilador y en el tiempo de explotación – para optimizar a mano el código
generado del compilador (así se escriben la mayoría de las librerías de bajo nivel y los
KERNELS de OS).
Por lo tanto los nombres de los variables y las funciones no pueden coincidir.
Ejemplo:
int ddd;
...
ddd--;
...
se puede compilar (sin transformación de nombres):
ddd dw 0
...
dec ddd
...
que esta bien. Pero:
int ax;
...
ax--;
...
se compilara:
ax dw 0
...
dec ax
...
que no es correcto.
No permitir conflictos de nombres generados en alto nivel. Imaginemos que la función en
modelo Medium (datos far proc near) y en modelo Large (todo far) se llama de la misma
manera. Al utilizar la función del modelo médium en un programa del modelo Large se
desincroniza la pila. Advertencia: Esto se hace solo de los compiladores buenos y hace poco
tiempo. En los compiladores de DOS si existen este tipo de conflictos.
Re - utilizar funciones con el mismo nombre y argumentos de distintos tipos (sobrecarga de
funciones). Esta practica es común en lenguajes orientados a objetos donde métodos (funciones)
con objetos diferentes comparten el mismo nombre. En esta manera también se permite
comprobar los tipos de los argumentos.
Ejemplo C++:
void line(point x) {
// pinta linea recta desde el ultimo punto hasta x.
}
void line(point x,point y) {
// pinta linea recta de x a y
}
Si line(x) no cambia de nombre según sus argumentos no podrá distinguirse de line(z,y).
Como cambian los nombres de las funciones:
Las técnicas comunes son:
1. Añadir un prefijo constante (normalmente _ o __ o @). Esto evita la generación de
nombres de registros y palabras claves.
2. Si el lenguaje de alto nivel no es sensible a mayúsculas/minúsculas – cambiar todos
los nombres en mayúsculas.
3. Añadir sufijos del tipo del resultado, los tipos de los argumentos etc.
Ejemplo de programa de bajo nivel que se llama del programa de alto nivel y al revés.
#include <stdio.h>
extern getlpt(void);
int main(void) {
register int a=getlpt();
printf("LPT1 is %d\n",a);
return 0;
}
El programa escribe el contenido del puerto de la impresora a al pantalla.
; Demo how to call a c function from the ASM function
;
; Compile with
;
tasm /ml /mx /zi tst3.asm
;
\
\
;
\
--- Upper/lower case sensitive
;
------ Large memory model (far ptrs,far functions)
;
segment getlpt_code public 'code'
format db "The port is %x(hex) %d(decimal)",10,0
_getlpt proc far
; Get the port # of the LPT1
xor ax,ax
mov ax,040h
mov es,ax
mov dx,word ptr es:[08]
; Call printf from the assembly program.
push dx
; save dx because it is not in the context
; printf("The port is %x ... %d ... \n",dx,dx);
; Push the arguments into the stack
push dx
; to print decimal
push dx
; to print hex
mov ax,getlpt_code
; the segment of the format
push ax
mov ax,offset format
; format
push ax
call far ptr _printf
; This is from stdlib
add sp,8
; clean the stack.
; 4 bytes format ptr (2 seg + 2 offset)
; 2 bytes first dx
; 2 bytes secon dx
;-------------------; 8 bytes total
pop dx
; recover the port
; Debug
in al,dx
; read the port
xor ah,ah
; The result is in AX
ret
; Done
_getlpt endp
public _getlpt
getlpt_code ends
extrn _printf:far
end
; Notar!!! fuera del segmento!
Descargar