LibraryImport y P/Invoke generado desde el código fuente
Use [LibraryImport] (C# 11+) para obtener marshaling compatible con AOT y generado desde el código fuente, con mejor rendimiento que DllImport.
LibraryImport y P/Invoke generado desde el código fuente es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.
¿Por qué LibraryImport?
[LibraryImport] se introdujo en .NET 7 (C# 11) como reemplazo compatible con AOT y generado mediante código fuente para [DllImport]. El DllImport clásico depende del marshalling en tiempo de ejecución mediante reflexión, lo que resulta problemático para Native AOT. LibraryImport genera todo el código de marshalling en tiempo de compilación.
Declaración de un método LibraryImport
Marque un método static partial con [LibraryImport]. El generador de código fuente completará la implementación. También debe marcar la clase contenedora como partial.
using System.Runtime.InteropServices;
internal static partial class NativeMethods
{
// Source generator creates the P/Invoke body at compile time
[LibraryImport("mylib", EntryPoint = "add_integers")]
internal static partial int AddIntegers(int a, int b);
// String marshaling must be explicit in LibraryImport
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
internal static partial int ProcessString(string text);
}Marshalling de cadenas en LibraryImport
A diferencia de DllImport, LibraryImport requiere especificar explícitamente el marshalling de las cadenas. Puede usar la enumeración StringMarshalling o atributos MarshalAs, de modo que el coste del marshalling sea visible y controlable.
// Option 1: StringMarshalling enum (all strings in method)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16)]
static partial bool CreateDirectory(string lpPathName, nint lpSecurityAttributes);
// Option 2: Per-parameter MarshalAs
[LibraryImport("libc")]
static partial int Open(
[MarshalAs(UnmanagedType.LPUTF8Str)] string path,
int flags);
// Option 3: MarshalUsing for custom marshalers
[LibraryImport("mylib")]
static partial void Process(
[MarshalUsing(typeof(Utf8StringMarshaller))] string name);Marshalling de estructuras
Para las estructuras, añada [NativeMarshalling] para definir cómo se asigna el tipo administrado a su representación nativa. El generador de código fuente usa el tipo marshaller para generar código seguro que minimiza las asignaciones.
[NativeMarshalling(typeof(PointMarshaller))]
public struct Point
{
public int X;
public int Y;
}
[CustomMarshaller(typeof(Point), MarshalMode.Default, typeof(PointMarshaller))]
public static class PointMarshaller
{
public static Point ConvertToManaged(NativePoint native)
=> new Point { X = native.X, Y = native.Y };
public static NativePoint ConvertToUnmanaged(Point managed)
=> new NativePoint { X = managed.X, Y = managed.Y };
[StructLayout(LayoutKind.Sequential)]
public struct NativePoint { public int X, Y; }
}Comparación entre DllImport y LibraryImport
DllImport se interpreta en tiempo de ejecución: inicio lento, dependencia de la reflexión e incompatibilidad con el recorte de AOT. LibraryImport genera código C# optimizado en tiempo de compilación: sin reflexión en tiempo de ejecución, seguro para el recorte y mediblemente más rápido en las pruebas de rendimiento.
// Old — DllImport (still works, but avoid for new AOT code)
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool MoveFile(string src, string dst);
// New — LibraryImport (AOT-safe, source-generated)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool MoveFile(string src, string dst);
// The compiler generates the actual P/Invoke wrapper bodySetLastError y manejo de errores
Establezca SetLastError = true en [LibraryImport] para capturar el código de error del sistema operativo. Use Marshal.GetLastPInvokeError() (preferido) o Marshal.GetLastWin32Error() para recuperarlo después de la llamada.
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool CreateDirectory(string path, nint secAttr);
bool ok = CreateDirectory(@"C:\Temp\NewDir", 0);
if (!ok)
{
int err = Marshal.GetLastPInvokeError();
// err is ERROR_ALREADY_EXISTS (183) if folder exists
throw new Win32Exception(err);
}Marshalling de Span<T> y Memory
Una de las ventajas de P/Invoke generado mediante código fuente es la compatibilidad de primera clase con Span<T>. Pasar un ReadOnlySpan<byte> evita el anclaje y la asignación que requeriría DllImport con arrays.
[LibraryImport("mylib")]
static partial int ProcessBuffer(
ReadOnlySpan<byte> data,
int length);
// Usage — no fixed or GCHandle needed
byte[] buffer = Encoding.UTF8.GetBytes("hello");
int result = ProcessBuffer(buffer, buffer.Length);
// For output buffers use Span<byte>
[LibraryImport("mylib")]
static partial int FillBuffer(Span<byte> output, int maxLen);Habilitación de la generación de código fuente
La generación de código fuente se habilita automáticamente al hacer referencia al espacio de nombres System.Runtime.InteropServices en un proyecto destinado a .NET 7 o posterior. Para destinos anteriores, necesita el paquete de analizador Microsoft.Interop.SourceGeneration.
<!-- In your .csproj — no extra package needed on .NET 7+ -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<!-- Enable Roslyn analyzers and source generators -->
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- AllowUnsafeBlocks may be needed for some marshalers -->
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>Inspección del código generado
Añada <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> a su csproj para escribir los archivos generados en obj/. Esto le permite inspeccionar exactamente lo que produce el generador de código fuente, una excelente práctica de aprendizaje.
// The generator emits something like this for a LibraryImport method:
// (simplified view of generated stub)
internal static partial int AddIntegers(int a, int b)
{
// Direct call — no reflection, no boxing, fully inlineable
return __PInvoke(a, b);
[System.Runtime.InteropServices.DllImportAttribute(
"mylib",
EntryPoint = "add_integers",
ExactSpelling = true)]
static extern int __PInvoke(int a, int b);
}Caso real: envoltura de una biblioteca de C
Un patrón habitual consiste en definir una clase contenedora estática con todas las declaraciones LibraryImport y, después, exponer una API segura de alto nivel. Mantenga las declaraciones partial como internas o privadas y exponga públicamente solo las envolturas seguras.
internal static partial class LibSodiumNative
{
private const string LibName = "libsodium";
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_keybytes")]
internal static partial nuint KeyBytes();
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_easy")]
internal static partial int SecretBoxEasy(
Span<byte> ciphertext,
ReadOnlySpan<byte> message,
ulong mlen,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> key);
}
// Public safe wrapper hides the native signature
public static byte[] Encrypt(byte[] message, byte[] key, byte[] nonce)
{
var cipher = new byte[message.Length + 16];
LibSodiumNative.SecretBoxEasy(cipher, message, (ulong)message.Length, nonce, key);
return cipher;
}Comprobación rápida
¿Cuál es la principal ventaja de [LibraryImport] frente a [DllImport]?
Resumen: LibraryImport y P/Invoke generado mediante código fuente
Aspectos clave:
[LibraryImport]sustituye a[DllImport]para usar P/Invoke de forma segura con AOT- El marshalling se genera en tiempo de compilación, sin reflexión en tiempo de ejecución
- Requiere un método
static partialy una clasepartial - El marshalling de cadenas debe especificarse explícitamente mediante
StringMarshallingoMarshalAs - Compatibilidad de primera clase con
Span<T>sin el coste del anclaje - Inspeccione el código generado con
EmitCompilerGeneratedFiles
Preguntas frecuentes
¿La lección «LibraryImport y P/Invoke generado desde el código fuente» es gratis?
Sí — el texto completo de «LibraryImport y P/Invoke generado desde el código fuente» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.
¿Qué aprenderé en «LibraryImport y P/Invoke generado desde el código fuente»?
Use [LibraryImport] (C# 11+) para obtener marshaling compatible con AOT y generado desde el código fuente, con mejor rendimiento que DllImport. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar C# Academy?
No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «LibraryImport y P/Invoke generado desde el código fuente»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de C# Academy?
Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Fundamentos de P/Invoke
- LibraryImport y P/Invoke generado desde el código fuente
- Código no seguro, punteros y búferes fijos
- Interoperabilidad con COM y wrappers invocables en tiempo de ejecución