Plataforma de Servicios Invocación , comúnmente conocida como P / Invoke , es una característica de Common Language Infrastructure implementaciones, como Microsoft 's Common Language Runtime , que permite código administrado para llamar a código nativo .
El código administrado, como C # o VB.NET, proporciona acceso nativo a clases, métodos y tipos definidos dentro de las bibliotecas que componen .NET Framework. Si bien .NET Framework proporciona un amplio conjunto de funcionalidades, es posible que no tenga acceso a muchas bibliotecas de sistemas operativos de nivel inferior normalmente escritas en código no administrado o bibliotecas de terceros también escritas en código no administrado. P / Invoke es la técnica que un programador puede usar para acceder a funciones en estas bibliotecas. Las llamadas a funciones dentro de estas bibliotecas se producen al declarar la firma de la función no administrada dentro del código administrado, que sirve como la función real que se puede llamar como cualquier otro método administrado. La declaración hace referencia a la ruta del archivo de la biblioteca y define los parámetros de la función y el retorno en los tipos administrados que es más probable que se clasifiquen implícitamente hacia y desde los tipos no administrados por Common Language Run-Time (CLR). Cuando los tipos de datos no administrados se vuelven demasiado complejos para una simple conversión implícita desde y hacia tipos administrados, el marco permite al usuario definir atributos en la función, el retorno y / o los parámetros para refinar explícitamente cómo se deben ordenar los datos para que no para dar lugar a excepciones al intentar hacerlo implícitamente. Hay muchas abstracciones de conceptos de programación de nivel inferior disponibles para los programadores de código administrado en comparación con la programación en lenguajes no administrados. Como resultado, un programador que solo tenga experiencia en código administrado deberá repasar conceptos de programación como punteros, estructuras y pasar por referencia para superar algunos de los obstáculos más básicos pero comunes en el uso de P / Invoke.
Arquitectura
Descripción general
Dos variantes de P / Invoke actualmente en uso son:
Explícito
- El código nativo se importa a través de bibliotecas vinculadas dinámicamente (DLL)
- Los metadatos incrustados en el ensamblaje de la persona que llama definen cómo se llamará al código nativo y se accederá a los datos ( generalmente se requieren especificadores de fuente atribuidos para ayudar al compilador a generar el pegamento marshal )
- Esta definición es la parte "explícita"
Implícito
- Al usar C ++ / CLI , una aplicación puede usar simultáneamente el montón administrado (mediante punteros de seguimiento) y cualquier región de memoria nativa, sin la declaración explícita. (Implícito)
- Un beneficio principal en este caso es que, si cambian las estructuras de datos nativas subyacentes, siempre que el nombre sea compatible, se evita un cambio radical .
- es decir, agregar / eliminar / reordenar estructuras en un encabezado nativo será compatible de forma transparente siempre que los nombres de los miembros de la estructura no cambien también.
Detalles
Cuando se usa P / Invoke, CLR maneja la carga de DLL y la conversión de los tipos anteriores no administrados a tipos de CTS (también conocido como clasificación de parámetros ). [1] [ cita requerida ] Para realizar esto, el CLR :
- Localiza la DLL que contiene la función.
- Carga la DLL en la memoria.
- Localiza la dirección de la función en la memoria y envía sus argumentos a la pila , ordenando los datos según sea necesario.
P / Invoke es útil para usar archivos DLL C o C ++ estándar (no administrados) . Se puede usar cuando un programador necesita tener acceso a la extensa API de Windows , ya que muchas funciones proporcionadas por las bibliotecas de Windows carecen de envoltorios disponibles . Cuando .NET Framework no expone una API de Win32, el contenedor de esta API debe escribirse manualmente.
Trampas
Escribir envoltorios P / Invoke puede ser difícil y propenso a errores. El uso de archivos DLL nativos significa que el programador ya no puede beneficiarse de la seguridad de tipos y la recolección de basura, como generalmente se proporciona en el entorno .NET. Cuando se usan incorrectamente, esto puede causar problemas como fallas de segmentación o pérdidas de memoria . Obtener las firmas exactas de las funciones heredadas para su uso en el entorno .NET puede ser difícil, lo que puede provocar tales problemas. Para ello existen herramientas y sitios web para obtener dichas firmas, lo que ayuda a prevenir problemas de firma. [1]
Otras trampas incluyen:
- Alineación de datos incorrecta de tipos definidos por el usuario en el lenguaje administrado: hay diferentes formas en que los datos pueden alinearse según los compiladores o las directivas del compilador en C y se debe tener cuidado de decirle explícitamente al CLR cómo alinear los datos para tipos no blittables . Un ejemplo común de esto es cuando se trata de definir un tipo de datos en .NET para representar una unión en C . Dos variables diferentes se superponen en la memoria, y definir estas dos variables en un tipo en .NET haría que estuvieran en diferentes ubicaciones en la memoria, por lo que se deben usar atributos especiales para corregir el problema.
- Interferencia con la ubicación de los datos por el recolector de basura del lenguaje administrado: si una referencia es local a un método en .NET y se pasa a una función nativa, cuando el método administrado regresa, el recolector de basura puede reclamar esa referencia. Se debe tener cuidado de que la referencia del objeto esté anclada , evitando que sea recolectada o movida por el recolector de basura, lo que resultaría en un acceso inválido por parte del módulo nativo.
Cuando se usa C ++ / CLI, el CIL emitido es libre de interactuar con objetos ubicados en el montón administrado y simultáneamente con cualquier ubicación de memoria nativa direccionable. Un objeto residente del montón administrado puede ser llamado, modificado o construido usando un simple "objeto-> campo"; notación para asignar valores o especificar llamadas a métodos. Se obtienen ganancias de rendimiento significativas al eliminar cualquier cambio de contexto innecesario, se reducen los requisitos de memoria (pilas más cortas).
Esto viene con nuevos desafíos:
- El código es propenso a doble thunking [2] si no se aborda específicamente
- El problema del bloqueo del cargador [3]
Estas referencias especifican soluciones para cada uno de estos problemas si se encuentran. Un beneficio principal es la eliminación de la declaración de estructura, el orden de la declaración de campo y los problemas de alineación no están presentes en el contexto de C ++ Interop.
Ejemplos de
Ejemplos básicos
Este primer ejemplo simple muestra cómo obtener la versión de una DLL en particular :
Firma de la función DllGetVersion en la API de Windows :
HRESULT DllGetVersion ( DLLVERSIONINFO * pdvi )
P / Invocar código C # para invocar la función DllGetVersion :
[StructLayout (LayoutKind.Sequential)] estructura privada DLLVERSIONINFO { public int cbSize ; public int dwMajorVersion ; public int dwMinorVersion ; public int dwBuildNumber ; public int dwPlatformID ; } [DllImport ("shell32.dll")] static extern int DllGetVersion ( ref. DLLVERSIONINFO pdvi );
El segundo ejemplo muestra cómo extraer un icono en un archivo:
Firma de la función ExtractIcon en la API de Windows:
HICON ExtractIcon ( HINSTANCE hInst , LPCTSTR lpszExeFileName , UINT nIconIndex );
P / Invocar código C # para invocar la función ExtractIcon :
[DllImport ("shell32.dll")] estático extern IntPtr ExtractIcon ( IntPtr hInst , [MarshalAs (UnmanagedType.LPStr)] cadena lpszExeFileName , uint nIconIndex );
El siguiente ejemplo complejo muestra cómo compartir un evento entre dos procesos en la plataforma Windows :
Firma de la función CreateEvent :
HANDLE CreateEvent ( LPSECURITY_ATTRIBUTES lpEventAttributes , BOOL bManualReset , BOOL bInitialState , LPCTSTR lpName );
P / Invocar código C # para invocar la función CreateEvent :
[DllImport ("kernel32.dll", SetLastError = true)] static extern IntPtr CreateEvent ( IntPtr lpEventAttributes , bool bManualReset , bool bInitialState , [MarshalAs (UnmanagedType.LPStr)] string lpName );
Un ejemplo más complejo
// declaración nativa typedef struct _PAIR { DWORD Val1 ; DWORD Val2 ; } PAR , * PPAIR ;
// Compilado con / clr; el uso de #pragma administrado / no administrado puede conducir a un doble procesamiento; // evitar usando un .cpp independiente con .h incluye. // Esto estaría ubicado en un archivo .h.template <> inline CLR_PAIR ^ marshal_as < CLR_PAIR ^ , PAIR > ( const PAIR & Src ) { // Tenga en cuenta el uso de de / referencing. Debe coincidir con su uso. CLR_PAIR ^ Dest = gcnew CLR_PAIR ; Dest -> Val1 = Src . Val1 ; Dest -> Val2 = Src . Val2 ; return Dest ; };
CLR_PAIR ^ mgd_pair1 ; CLR_PAIR ^ mgd_pair2 ; PAREJA native0 , * native1 = & native0 ;native0 = NativeCallGetRefToMemory ();// Usando marshal_as. Tiene sentido para tipos grandes o de uso frecuente. mgd_pair1 = marshal_as < CLR_PAIR ^> ( * native1 );// Uso de campo directo mgd_pair2 -> Val1 = native0 . Val1 ; mgd_pair2 -> val2 = native0 . val2 ;return ( mgd_pair1 ); // Regresar a C #
Herramientas
Hay una serie de herramientas diseñadas para ayudar en la producción de firmas P / Invoke.
Escribir una aplicación de utilidad que importe archivos de encabezado C ++ y archivos DLL nativos y produzca un ensamblaje de interfaz automáticamente resulta bastante difícil. El principal problema con la producción de un importador / exportador de este tipo para firmas P / Invoke es la ambigüedad de algunos tipos de parámetros de llamada de función de C ++.
Brad Abrams tiene esto que decir sobre el tema: El problema P / Invoke .
El problema radica en funciones de C ++ como las siguientes:
__declspec ( dllexport ) void MyFunction ( char * params );
¿Qué tipo deberíamos usar para los parámetros params en nuestra firma P / Invoke? Esto podría ser una cadena C ++ terminada en nulo, o podría ser una Char matriz o podría ser una salida parámetro char . Entonces deberíamos usar cadena , StringBuilder , char [] o ref char ?
Independientemente de este problema, hay algunas herramientas disponibles para simplificar la producción de firmas P / Invoke.
Una de las herramientas que se enumeran a continuación, xInterop C ++ .NET Bridge ha resuelto este problema mediante la implementación de múltiples invalidaciones del mismo método C ++ en el mundo .NET, los desarrolladores pueden elegir la correcta para realizar la llamada.
PInvoke.net
PInvoke.net es una wiki que contiene firmas P / Invoke para una gran cantidad de API estándar de Windows. Es propiedad de Redgate Software y tiene alrededor de 50000 visitas por mes.
Las firmas son producidas manualmente por los usuarios de la wiki. Se pueden buscar mediante un complemento gratuito de Microsoft Visual Studio .
PInvoker
PInvoker es una aplicación que importa archivos DLL nativos y archivos C ++ .hy exporta archivos DLL de interoperabilidad P / Invoke completamente formados y compilados . Supera el problema de la ambigüedad al incluir los parámetros de la función del puntero nativo en clases de interfaz .NET específicas de PInvoker. En lugar de utilizar tipos de parámetros estándar de .NET en las definiciones del método P / Invoke ( char [] , string , etc.) utiliza estas clases de interfaz en las llamadas a la función P / Invoke.
Por ejemplo, si consideramos el código de ejemplo anterior, PInvoker produciría una función .NET P / Invoke aceptando una clase de interfaz .NET que envuelve el nativo char * puntero. La construcción de esta clase podría ser de un cuerda o de un matriz char [] . La estructura de memoria nativa real para ambos es la misma, pero los respectivos constructores de clase de interfaz para cada tipo poblarán la memoria de diferentes maneras. Por lo tanto, la responsabilidad de decidir qué tipo de .NET debe pasarse a la función se transfiere al desarrollador.
Asistente de interoperabilidad de Microsoft
Microsoft Interop Assistant es una herramienta gratuita disponible con binarios y código fuente disponible para descargar en CodePlex . Tiene licencia de Microsoft Limited Public License (Ms-LPL).
Tiene dos partes:
- Un convertidor que toma pequeñas secciones de código de archivo de encabezado nativo C ++ que contiene definiciones de estructuras y métodos. Luego produce código C # P / Invoke para que lo copie y pegue en sus aplicaciones.
- Una base de datos en la que se pueden realizar búsquedas de definiciones de estructuras, métodos y constantes API de Windows convertidas.
Debido a que esta herramienta produce código fuente C # en lugar de un dll compilado, el usuario es libre de realizar los cambios necesarios en el código antes de usarlo. Entonces, el problema de la ambigüedad se resuelve cuando la aplicación elige un tipo de .NET en particular para usar en la firma del método P / Invoke y, si es necesario, el usuario puede cambiarlo al tipo requerido.
P / Asistente de invocación
El Asistente de P / Invoke utiliza un método similar al Asistente de interoperabilidad de Microsoft en el sentido de que acepta código de archivo nativo C ++ .hy genera código C # (o VB.NET) para que lo pegue en el código de la aplicación .NET.
También tiene opciones para el marco al que desea dirigirse: .NET Framework para el escritorio o .NET Compact Framework para dispositivos inteligentes Windows Mobile (y Windows CE).
Puente de xInterop C ++ .NET
xInterop C ++ .NET Bridge es una aplicación de Windows para crear un contenedor de C # para DLL de C ++ nativas y un puente de C ++ para acceder a ensamblados .NET, viene con una biblioteca C # / .NET que envuelve las clases estándar de C ++, como string, iostream, etc. , Se puede acceder a las clases y objetos de C ++ desde .NET.
Esta herramienta genera archivos DLL de contenedor de C # con código fuente de archivos DLL de C ++ nativos existentes y los archivos de encabezado asociados que la herramienta requiere para crear un archivo DLL de contenedor de C #. La aplicación genera las firmas P / Invoke y la clasificación de datos. El contenedor de C # resultante tiene la interfaz similar de la contraparte de C ++ con el tipo de parámetro convertido al código .NET.
Esta herramienta reconoce clases de plantilla que no se exportan desde la DLL de C ++ y crea una instancia de la clase de plantilla y la exporta a una DLL complementaria y la interfaz C ++ correspondiente se puede utilizar en .NET.
Ver también
- Tipos de blittable
- Interfaz nativa de Java , la forma estándar para que los programas Java accedan al código nativo
- Java Native Access , el equivalente en Java de P / Invoke
- Archivos de la biblioteca de Windows
- J / Direct , la API equivalente que ya no se mantiene para Microsoft Java Virtual Machine
Referencias
- ^ La clasificación de parámetros no debe confundirse con el término general clasificación , que significa serialización . Los parámetros calculados se copian en lapila CLR después de su conversión atipos CTS , pero no se serializan.
- ^ "Doble procesamiento (C ++)" .
- ^ "Inicialización de ensamblajes mixtos" .
enlaces externos
- Un sitio dedicado a P / Invoke
- J / Invoke acceso de Java a la API de Win32 o bibliotecas compartidas de Linux / Mac OS X, similar a P / Invoke
- [2] P / Invoke implícito con especial énfasis en las técnicas para extender a la plantilla de clasificación
- 3 artículos de Microsoft que contrastan estos métodos , Using Explicit PInvoke, Implicit C ++ Interop y "Una mirada más cercana a la invocación de plataforma"
- Asistente de interoperabilidad de Microsoft Página principal del Asistente de interoperabilidad de Microsoft.
- P / Invoke Wizard P / Invoke Wizard página de inicio.
- Página principal de PInvoker PInvoker.
- xInterop C ++ .NET Bridge xInterop C ++ .NET Bridge página principal