Como crear una Extensión en C para usar una DLL de C# en PHP

En este artículo, exploraremos cómo crear una biblioteca en C# que actúe como una extensión de PHP y devuelva objetos complejos. Usaremos un ejemplo simple con una clase Page y una colección de páginas que filtraremos según un criterio dado.

Objetivos

  1. Crear una clase Page en C#.
  2. Implementar una función que filtre las páginas según un nombre.
  3. Devolver una lista de objetos Page como una cadena JSON a PHP.
  4. Deserializar el JSON en PHP para trabajar con objetos complejos.

Estructura del Proyecto

A continuación, se muestra la estructura del proyecto que implementaremos:

MyCSharpLibrary/
│
├── MyCSharpLibrary.csproj        # Archivo de proyecto C#
├── Program.cs                    # Archivo de entrada (opcional)
├── Page.cs                       # Clase Page
├── Pages.cs                      # Clase Pages
└── MyCSharpLibrary.dll           # Biblioteca compilada (versión final)

Paso 1: Crear la Clase en C#

Primero, definimos la clase Page y una clase Pages que hereda de List<Page>. Esta última contendrá métodos para gestionar las páginas.

Código C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Json;

public class Page
{
    public string Name { get; set; }
}

public class Pages : List<Page>
{
    // Constructor para agregar páginas de ejemplo
    public Pages()
    {
        this.Add(new Page { Name = "Page1" });
        this.Add(new Page { Name = "Page2" });
        this.Add(new Page { Name = "AnotherPage" });
    }

    // Método que devuelve páginas filtradas como JSON
    [UnmanagedCallersOnly(EntryPoint = "GetPages")]
    public static IntPtr GetPages(IntPtr filterPtr)
    {
        // Convertir el puntero a una cadena
        string filter = Marshal.PtrToStringAnsi(filterPtr);
        Pages pages = new Pages();

        // Filtrar las páginas que contienen el filtro en el nombre
        var filteredPages = pages
            .Where(page => page.Name.Contains(filter, StringComparison.OrdinalIgnoreCase))
            .ToList();

        // Serializar la lista de objetos a JSON
        string jsonResult = JsonSerializer.Serialize(filteredPages);

        // Devolver el resultado como puntero a cadena ANSI
        return Marshal.StringToHGlobalAnsi(jsonResult);
    }
}

Explicación del Código

  • Clase Page: Representa una página con una propiedad Name.
  • Clase Pages: Hereda de List<Page> y agrega un constructor para inicializar con algunas páginas de ejemplo.
  • Método GetPages:
    • Usa Marshal.PtrToStringAnsi para convertir el puntero a una cadena.
    • Filtra las páginas basándose en el nombre.
    • Serializa la lista filtrada a JSON usando JsonSerializer.
    • Devuelve el resultado como puntero a una cadena ANSI con Marshal.StringToHGlobalAnsi.

Paso 2: Compilar la Biblioteca C

Compila la biblioteca como una DLL para usarla como extensión en PHP:

dotnet publish -c Release -r linux-x64 --self-contained

Versión Compilada

Después de compilar, deberías tener un archivo llamado MyCSharpLibrary.dll en la carpeta bin/Release/net6.0/linux-x64/publish/. Este archivo es la biblioteca que usarás en PHP.

Paso 3: Crear la Extensión en C para PHP

Ahora, necesitamos crear una extensión en C que permita llamar a la función GetPages desde PHP. Aquí está el código para la extensión:

Código C

#include <php.h>
#include <dlfcn.h>

// Definir el tipo de la función que se va a usar
typedef const char* (*GetPagesFunc)(const char* filter);

// Implementación de la función PHP que llama a GetPages
PHP_FUNCTION(get_pages)
{
    char *filter;
    size_t filter_len;

    // Obtener el argumento de PHP
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &filter, &filter_len) == FAILURE) {
        RETURN_FALSE;
    }

    // Cargar la biblioteca compartida
    void* handle = dlopen("MyCSharpLibrary.so", RTLD_LAZY);
    if (!handle) {
        php_error(E_WARNING, "Cannot load library: %s", dlerror());
        RETURN_FALSE;
    }

    // Obtener el símbolo de la función GetPages
    GetPagesFunc get_pages = (GetPagesFunc)dlsym(handle, "GetPages");
    if (!get_pages) {
        php_error(E_WARNING, "Cannot load symbol: %s", dlerror());
        dlclose(handle);
        RETURN_FALSE;
    }

    // Llamar a la función GetPages
    const char* pages = get_pages(filter);
    dlclose(handle);
    RETURN_STRING(pages);
}

// Tabla de funciones
const zend_function_entry my_functions[] = {
    PHP_FE(get_pages, NULL)
    PHP_FE_END
};

// Definición del módulo
zend_module_entry my_module_entry = {
    STANDARD_MODULE_HEADER,
    "my_extension",
    my_functions,
    NULL, NULL, NULL, NULL, NULL,
    "0.1",
    STANDARD_MODULE_PROPERTIES
};

// Inicialización del módulo
ZEND_GET_MODULE(my_extension)

Explicación del Código

  • get_pages: Esta función recibe un argumento de filtro, carga la biblioteca compartida y obtiene el puntero a la función GetPages.
  • dlopen: Carga la biblioteca compartida.
  • dlsym: Busca la función GetPages en la biblioteca.
  • RETURN_STRING(pages): Devuelve la cadena JSON a PHP.

Paso 4: Compilar la Extensión

Compila la extensión usando los siguientes comandos:

phpize
./configure
make
sudo make install

Asegúrate de que el archivo de configuración de PHP (php.ini) tenga la siguiente línea para habilitar la extensión:

extension=my_extension.so

Paso 5: Usar la Función en PHP

Finalmente, puedes utilizar la función get_pages en tu script PHP para obtener el objeto complejo.

Código PHP

<?php
// Llamar a la función get_pages con un filtro
$json = get_pages("Page");

// Deserializar el JSON en un array de objetos
$pages = json_decode($json);

// Iterar sobre las páginas y mostrar sus nombres
foreach ($pages as $page) {
    echo "Name: " . $page->Name . "\n";
}
?>

Explicación del Código

  • get_pages("Page"): Llama a la función que filtra las páginas.
  • json_decode($json): Deserializa la cadena JSON a un array de objetos PHP.
  • $page->Name: Accede a la propiedad Name de cada objeto Page y la muestra.

Conclusión

En este artículo, hemos aprendido cómo crear una biblioteca en C# que actúa como una extensión de PHP, devolviendo objetos complejos en forma de JSON. Este enfoque permite una integración efectiva entre C# y PHP, lo que te permite aprovechar las capacidades de ambos lenguajes.

Consideraciones Finales

  • Asegúrate de tener instaladas las herramientas necesarias para compilar y ejecutar el código.
  • Puedes expandir este ejemplo para incluir más propiedades en la clase Page o manejar diferentes tipos de filtros.
  • Siempre maneja adecuadamente los errores, especialmente al cargar bibliotecas y funciones dinámicamente.

Este artículo te proporciona una guía clara y detallada sobre cómo integrar C# y PHP usando una DLL, mostrando cómo devolver objetos complejos y procesar esa información en PHP. ¡Espero que lo encuentres útil!