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.
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);
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()
Llamar a código nativo (no gestionado) tiene varias implicaciones:
- Necesitamos manejar excepciones dentro del código no gestionado. El JIT unicamente guarda algo de información en cada transición de código gestionado a código no gestionado (en una lista enlazada), llamado Ultimo Marco Gestionado (LMF). Si una excepción ocurre el entorno de ejecución mira primero si la excepción fue dentro de código gestionado. Si no está debe haber una entrada en la LMF que contenga toda la información necesaria para luego desapilar la pila.
La creación de esta estructura LMF claramente implica algo de sobrecarga, por lo que llamar a código no gestionado no es tan barato como parecía en un principio. Quizás podamos introducir un atributo especial para la creación de la LMF en métodos que hagan llamadas internas que no puedan lanzar excepciones.
- PInvoke tiene la posibilidad de convertir los tipos de argumentos. Por ejemplo las cadenas son empaquetadas como Char* (marshaled). Para cada argumento String se traduce en un char*. La codificación se especifica en el atributo CharSet del DllImport.
- 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.
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.
- 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).