0Pricing
C# Academy · Lección

Fundamentos de P/Invoke

Declare y llame a funciones nativas mediante DllImport, y comprenda el marshaling de tipos primitivos, cadenas y estructuras.

Fundamentos de P/Invoke es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 1 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.

¿Qué es P/Invoke?

Platform Invocation Services (P/Invoke) permite que C# llame a funciones de bibliotecas compartidas nativas (.dll en Windows, .so en Linux y .dylib en macOS). Es el mecanismo estándar para llamar a las API de Win32 o a cualquier biblioteca con ABI de C desde .NET.

Su primera llamada a P/Invoke

Declare la función nativa con [DllImport] e indique el nombre de la biblioteca. El CLR se encarga de buscar y cargar la biblioteca y de convertir los argumentos entre representaciones.

using System.Runtime.InteropServices;

// Calling MessageBoxW from user32.dll (Windows)
internal static partial class NativeMethods
{
    [DllImport("user32.dll",
        EntryPoint = "MessageBoxW",
        CharSet = CharSet.Unicode,
        SetLastError = true)]
    internal static extern int MessageBox(
        IntPtr hwnd,
        string text,
        string caption,
        uint type);
}

// Call it:
NativeMethods.MessageBox(IntPtr.Zero, "Hello!", "P/Invoke", 0);

// Windows-only — wrap in RuntimeInformation.IsOSPlatform check
// for cross-platform code

Llamar a bibliotecas nativas personalizadas

P/Invoke funciona con cualquier función exportada en C, no solo con las API del sistema operativo. Compile una biblioteca nativa, exporte funciones con vinculación de C y llámelas desde C#.

// mymath.h / mymath.c:
// extern "C" double AddNumbers(double a, double b) { return a + b; }
// Compile: gcc -shared -o libmymath.so mymath.c

// C# declaration:
[DllImport("mymath",              // libmymath.so / mymath.dll
    EntryPoint = "AddNumbers",
    CallingConvention = CallingConvention.Cdecl)]
private static extern double AddNumbers(double a, double b);

// Call:
double result = AddNumbers(3.14, 2.72); // 5.86

// .NET resolves the library via:
// 1. Absolute path if provided
// 2. App directory
// 3. OS library path (PATH / LD_LIBRARY_PATH / DYLD_LIBRARY_PATH)

Conversión de tipos básicos

El CLR convierte automáticamente la mayoría de los tipos primitivos entre sus representaciones administrada y nativa. Conocer estas correspondencias ayuda a evitar errores sutiles.

// C Type       → C# Type
// int (32-bit)  → int  or  System.Int32
// long (64-bit) → long or  System.Int64
// float         → float
// double        → double
// bool          → [MarshalAs(UnmanagedType.Bool)] bool
// char*  (ANSI) → string  (CharSet.Ansi)
// wchar_t*      → string  (CharSet.Unicode)
// void*         → IntPtr
// size_t        → UIntPtr

// Example with explicit marshalling:
[DllImport("libc", EntryPoint = "strlen", CharSet = CharSet.Ansi)]
private static extern UIntPtr StrLen(
    [MarshalAs(UnmanagedType.LPStr)] string s);

int len = (int)StrLen("hello"); // 5

Pasar estructuras al código nativo

Utilice [StructLayout(LayoutKind.Sequential)] para garantizar que la estructura se distribuya en memoria exactamente como espera el código nativo.

// C struct:
// struct Point { int x; int y; };
// void DrawPoint(struct Point p);

[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int X;
    public int Y;
}

[DllImport("graphics", EntryPoint = "DrawPoint",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void DrawPoint(Point p);

// Pass by value:
DrawPoint(new Point { X = 10, Y = 20 });

// Pass by pointer (ref or out):
[DllImport("graphics", EntryPoint = "GetCenter",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void GetCenter(out Point center);
GetCenter(out var pt);

Gestión de errores: GetLastWin32Error

Las funciones de la API de Windows indican los errores mediante GetLastError(). Establezca SetLastError = true en [DllImport] y llame a Marshal.GetLastWin32Error() después de la llamada.

[DllImport("kernel32.dll",
    EntryPoint = "CreateFileW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern IntPtr CreateFile(
    string fileName,
    uint desiredAccess,
    uint shareMode,
    IntPtr securityAttributes,
    uint creationDisposition,
    uint flagsAndAttributes,
    IntPtr templateFile);

const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING  = 3;

var handle = CreateFile("test.txt", GENERIC_READ, 0,
    IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);

if (handle == (IntPtr)(-1))
{
    int error = Marshal.GetLastWin32Error();
    throw new System.ComponentModel.Win32Exception(error);
}

Conversión de cadenas y búferes

La conversión de cadenas requiere prestar especial atención a la codificación y la propiedad de los datos. Utilice StringBuilder para los búferes de salida y atributos MarshalAs para especificar la codificación.

// Read into a buffer:
[DllImport("kernel32.dll",
    EntryPoint = "GetComputerNameW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern bool GetComputerName(
    System.Text.StringBuilder lpBuffer,
    ref uint nSize);

uint size = 256;
var buffer = new System.Text.StringBuilder((int)size);
if (GetComputerName(buffer, ref size))
    Console.WriteLine(buffer.ToString());

// Return a string owned by native code (don't free it):
[DllImport("mylib", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string GetVersion();

Punteros a funciones y callbacks

Pase delegados administrados como punteros a funciones de C. El CLR crea un thunk, pero debe mantener vivo el delegado (conservando una referencia); de lo contrario, el GC lo recopilará y el código nativo se bloqueará.

// C callback signature: typedef int (*Comparer)(const void*, const void*);
// void qsort(void* base, size_t nitems, size_t size, Comparer compar);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int CompareCallback(IntPtr a, IntPtr b);

[DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
private static extern void QSort(
    int[] data, UIntPtr count, UIntPtr size, CompareCallback compare);

// Keep the delegate ALIVE for the duration of the call:
private static readonly CompareCallback _compare =
    (a, b) => Marshal.ReadInt32(a).CompareTo(Marshal.ReadInt32(b));

var data = new[] { 5, 2, 8, 1, 3 };
QSort(data, (UIntPtr)data.Length, (UIntPtr)sizeof(int), _compare);

API NativeLibrary

La clase NativeLibrary proporciona carga explícita de bibliotecas, resolución de punteros a funciones y personalización de rutas multiplataforma: es la alternativa moderna a la resolución implícita de nombres de bibliotecas.

using System.Runtime.InteropServices;

// Explicit load:
var handle = NativeLibrary.Load("/usr/lib/libssl.so.3");

// Resolve a function pointer:
var addPtr = NativeLibrary.GetExport(handle, "AddNumbers");
var addFn = Marshal.GetDelegateForFunctionPointer<Func<double,double,double>>(addPtr);
double result = addFn(1.0, 2.0);

// Unload:
NativeLibrary.Free(handle);

// Custom resolver (called when DllImport can't find a library):
NativeLibrary.SetDllImportResolver(typeof(MyNative).Assembly,
    (libName, assembly, searchPath) =>
    {
        if (libName == "mymath")
            return NativeLibrary.Load("/opt/mymath/libmymath.so");
        return IntPtr.Zero;
    });

Caso real: llamar a OpenSSL

Un ejemplo real: llamar a la función de resumen SHA-256 de OpenSSL desde C# mediante P/Invoke.

internal static class OpenSslInterop
{
    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_MD_CTX_new();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern void EVP_MD_CTX_free(IntPtr ctx);

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_sha256();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern int EVP_DigestInit_ex(IntPtr ctx, IntPtr type, IntPtr engine);

    // In practice, use System.Security.Cryptography.SHA256 instead:
    // var hash = SHA256.HashData(data);
    // P/Invoke to OpenSSL is only needed when you require
    // non-managed cryptographic operations or specific OpenSSL features
}

Comprobación rápida

¿Por qué debe mantener vivo un delegado al pasarlo como callback nativo?

Resumen: fundamentos de P/Invoke

Aspectos clave:

  • P/Invoke: llamar a funciones nativas de la ABI de C mediante declaraciones [DllImport]
  • El CLR realiza automáticamente el marshalling de los tipos primitivos; use [MarshalAs] para cadenas y tipos personalizados
  • [StructLayout(LayoutKind.Sequential)]: garantiza que el diseño de memoria de la estructura coincida con las expectativas nativas
  • Establezca SetLastError = true y llame a Marshal.GetLastWin32Error() para gestionar errores de Win32
  • Mantenga vivas las referencias a los delegados al pasarlos como callbacks nativos
  • NativeLibrary: carga explícita, resolución y resolución personalizada para controlar las rutas entre plataformas

Preguntas frecuentes

¿La lección «Fundamentos de P/Invoke» es gratis?

Sí — el texto completo de «Fundamentos de P/Invoke» 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 «Fundamentos de P/Invoke»?

Declare y llame a funciones nativas mediante DllImport, y comprenda el marshaling de tipos primitivos, cadenas y estructuras. 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 1 de 4.

¿Cuánto tiempo toma la lección «Fundamentos de P/Invoke»?

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

  1. Fundamentos de P/Invoke
  2. LibraryImport y P/Invoke generado desde el código fuente
  3. Código no seguro, punteros y búferes fijos
  4. Interoperabilidad con COM y wrappers invocables en tiempo de ejecución
← Volver a C# Academy