Más acerca de PInvoke y llamadas internas

Dietmar Maurer

(C) 2001 Ximian, Inc.

Alejandro Sánchez Acosta

Mono Hispano

Table of Contents
1. Prefacio
2. ¿Qué es PInvoke?
3. ¿Qué son las llamadas internas?
4. Consideraciones en tiempo de ejecución
5. ¿Cuando/como hace el entorno de ejecución para llamar código PInvoke no gestionado?
6. ¿cuando/cómo hace el entorno de ejecución para hacer llamadas internas a una llamada nogestionada?
7. ¿Qué se almacena en el LMF?

1. Prefacio

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be found in GNU Free Documentation License.


2. ¿Qué es PInvoke?

PInvoke significa Invocación de plataforma. Es posible llamar a funciones que contengan librerias compartidas nativas, por ejemplo puedes declarar:

[DllImport("cygwin1.dll", EntryPoint="puts"]
public static extern int puts (string name);

Si entonces llamas a "puts(...)" llama a la función nativa "puts" en "cygwin1.dll". Es también posible espeficar varios atributos de marshaling en los argumentos, por ejemplo, puedes especificar que la función puts() que espera la cadena en códificación ANSI configurando el atributo CharSet:

[DllImport("cygwin1.dll", EntryPoint="puts", CharSet=CharSet.Ansi)]
public static extern int puts (string name);

3. ¿Qué son las llamadas internas?

Algunas funciones de la libreria clase están implementadas en C, debido a esto no es posible implementarlas en C# o a causa de que se quiere ganar rendimiento. Aquí se muestra un ejemplo para la implementación de nuestro array:

[MethodImplAttribute(MethodImplOptions.InternalCall)]
public extern int GetRank ();

Si llamas a la función GetRank() invoca (llama) a ves_icall_System_Array_GetRank() dentro del entorno de ejecución de mono.

Si escribes tu propio entorno de ejecución puedes añadir llamadas internas con la función mono_add_internal_call()


4. Consideraciones en tiempo de ejecución

Llamar a código nativo (no gestionado) tiene varias implicaciones:


5. ¿Cuando/como hace el entorno de ejecución para llamar código PInvoke no gestionado?

- LDFTN, CALLI, Delegate::Invoke, Delegate::BeginInvoke: Debemos generar un código que haga de wrapper cuando carguemos la función con LFDTN, por lo que todos los argumentos son enviados (marshaled) en el formato correcto. Tambien necesitamos guardar/restaurar el LMF.

- MethodBase::Invoke (invocación en tiempo de ejecución): Necesitamos enviar (marshal) todos los argumentos en el formato correcto para guardar/restaurar el LMF

-CALL:Necesitamos enviar (marshal) todos los argumentos en el formato correcto para guardar/restaurar el LMF.

La forma más facil de implementar esto es crear siempre una función que envuelva (wrapper) las llamadas PInvoke, que lleve a cabo el envio de argumentos (marshal) y guarde/restaure el LMF.


6. ¿cuando/cómo hace el entorno de ejecución para hacer llamadas internas a una llamada nogestionada?

No necesitamos convertir ningún argumento, por lo que necesitamos solo tener cuidado de la estructura LMF.

- LDFTN, CALLI, Delegate::Invoke, Delegate::BeginInvoke: Tenemos que generar un código que haga de wrapper cuando carguemos la función con LDFTN que guarde/restaure el LMF.

- MethodBase::Invoke (llamada en tiempo de ejecución): Necesitamos guardar/restaurar el LMF.

- CALL: Necesitamos guardar/restaurar el LMF.

- CALLVIRT ( a través de la vtable): Necesitamos generar un código wrapper que guarde/restaure el LMF.

Porfavor notifica que podemos llamar a funciones internas con CALLVIRT, por ejemplo, podemos llamar a esas funciones a través de una VTable. Pero no podemos conocer antes si un slot de la vtable contiene una llamada interna o código gestionado, Por lo que de nuevo es mejor generar funciones wrappers para las llamadas internas para guardar/restaurar el LMF.

Desafortunadamente necesitamos meter todos los argumentos en la pila dos veces debido a que nosotros guardarmos el LMF, y el LMF está actualmente situado en la pila. Por lo que la pila parecería algo como:

|------------------------------------|
|       argumentos del método 	     |
|------------------------------------|
|              LMF                   |
|------------------------------------|
|  	argumentos copiados  	     |
|------------------------------------|

Por lo que yo conozco esta es la manera como el ORP funciona. Otra manera es asignar el LMF en otro lugar que no sea la pila, pero entonces tendríamos una sobrecarga en la asignación/liberación de las estructuras LMF (y otra llamada a arch_get_lmf_addr).

Quizás sea posible eliminar esta copia adiccional para llamadas internas introduciendo el LMF en la función de C. A continuación vamos a ver como tenemos una función puts() al tratarse de una llamada interna.

ves_icall_puts (MonoString *string);

Si sencillamente modificamos eso al incluir el LMF podemos eliminar la copia de todos los argumentos.

ves_icall_puts (MonoLMF lmf, MonoString *string);

Pero esto depende de como sean las conveciones para las llamadas y no sé si funcionara en todas las plataformas.


7. ¿Qué se almacena en el LMF?

- Todas las llamadas guardadas en los registros (ya que podemos manejar código nogestionado)

- Puntero a la instrucción de la ultima instrucción gestionada

- Un puntero a MonoMethod para la función no gestionada

- La dirección del puntero al hilo local lfm_addr (para eliminar otra llamada a arch_get_lmf_addr cuando se restaure el LMF)

El LFM está almacenado en la pila, por lo que también sabemos la posicion de la pila a devolver (unwinding).