Generación de código fuente para invocaciones de plataforma

.NET 7 incluye por primera vez un generador de código fuente para P/Invokes que reconoce en LibraryImportAttribute en el código de C#.

Cuando no usa la generación de código fuente, el sistema de interoperabilidad integrado del entorno de ejecución de .NET genera un código auxiliar de IL (una secuencia de instrucciones de IL con JIT aplicado) en tiempo de ejecución para facilitar la transición de administrado a no administrado. En el siguiente código se muestra un procedimiento para definir y llamar a P/Invoke en el que se usa este mecanismo:

[DllImport(
    "nativelib",
    EntryPoint = "to_lower",
    CharSet = CharSet.Unicode)]
internal static extern string ToLower(string str);

// string lower = ToLower("StringToConvert");

El código auxiliar de IL controla la serialización de parámetros y valores devueltos y las llamadas a código no administrado, respetando la configuración de DllImportAttribute que afecta a cómo se debe invocar el código no administrado (por ejemplo, SetLastError). Dado que este código auxiliar de IL se genera en tiempo de ejecución, no está disponible en escenarios de recorte de IL o de compilador AOT. La generación de IL supone un coste importante que se debe tener en cuenta para la serialización. Este coste se puede medir en términos de rendimiento de la aplicación y compatibilidad con posibles plataformas de destino que pueden no permitir la generación dinámica de código. El modelo de aplicación de la AOT nativa aborda los problemas de generación de código dinámico mediante la compilación previa de todo el código directamente en código nativo. El uso de DllImport no es una opción en plataformas que necesitan escenarios completos de AOT nativa y, por tanto, el uso de otros enfoques (por ejemplo, la generación de código fuente) resulta más adecuado. La depuración de la lógica de serialización en escenarios de DllImport tampoco es un tema menor.

El generador de código fuente de P/Invoke, que se incluye en el SDK de .NET 7 y que está habilitado de forma predeterminada, busca LibraryImportAttribute en métodos static y partial para desencadenar la generación de código fuente en tiempo de compilación de código de serialización, lo que elimina la necesidad de generar un código auxiliar de IL en tiempo de ejecución y permite la inserción de P/Invoke. También se incluyen analizadores y solucionadores de código para ayudar con la migración desde el sistema integrado al generador de código fuente y con el uso en general.

Uso básico

LibraryImportAttribute está diseñado para usarse de manera similar a DllImportAttribute. Podemos convertir el ejemplo anterior para usar la generación de código fuente de P/Invoke mediante LibraryImportAttribute y marcando el método como partial en lugar de extern:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);

Durante la compilación, el generador de código fuente se desencadenará para generar una implementación del método ToLower que controla la serialización del parámetro string y el valor de devolución como UTF-16. Dado que ahora la serialización se genera mediante código fuente, la lógica se puede examinar y recorrer en un depurador.

MarshalAs

El generador de código fuente también respeta el atributo MarshalAsAttribute. El código anterior también se podría escribir así:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLower(
    [MarshalAs(UnmanagedType.LPWStr)] string str);

Algunas opciones de configuración de MarshalAsAttribute no se admiten. El generador de código fuente emitirá un error si intenta usar una configuración no admitida. Para obtener más información, consulte Diferencias respecto a DllImport.

Convención de llamada

Para especificar la convención de llamada, use UnmanagedCallConvAttribute, por ejemplo:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(
    CallConvs = new[] { typeof(CallConvStdcall) })]
internal static partial string ToLower(string str);

Diferencias respecto a DllImport

LibraryImportAttribute está pensado para realizar una conversión sencilla desde DllImportAttribute en la mayoría de los casos, pero hay algunos cambios deliberados:

  • CallingConvention no tiene ningún equivalente en LibraryImportAttribute. Se debe usar UnmanagedCallConvAttribute en su lugar.
  • CharSet (para CharSet) se ha reemplazado por StringMarshalling (para StringMarshalling). ANSI se ha quitado y ahora UTF-8 está disponible como opción de primer nivel.
  • BestFitMapping y ThrowOnUnmappableChar no tienen equivalente. Estos campos solo eran relevantes al serializar una cadena ANSI en Windows. El código generado para serializar una cadena ANSI tendrá el comportamiento equivalente de BestFitMapping=false y ThrowOnUnmappableChar=false.
  • ExactSpelling no tiene equivalente. Este campo era una configuración centrada en Windows y no tenía ningún efecto en implementaciones que no son del sistema operativo de Windows. El nombre del método o EntryPoint debe reflejar la ortografía exacta del nombre del punto de entrada. Este campo tiene usos históricos relacionados con los sufijos A y W utilizados en la programación de Win32.
  • PreserveSig no tiene equivalente. Este campo era una configuración centrada en Windows. El código generado siempre convierte la signatura directamente.
  • El proyecto debe estar marcado como inseguro mediante AllowUnsafeBlocks.

También hay diferencias en la compatibilidad con algunas opciones de configuración en MarshalAsAttribute, en la serialización predeterminada de algunos tipos y en otros atributos relacionados con la interoperabilidad. Para obtener más información, consulte nuestra documentación sobre las diferencias de compatibilidad.

Vea también