> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP FFI

> Grundlagen zu PHP FFI, verfügbare Umgebungen und praktische Wrapper-Muster am Beispiel von VOICEVOX Core for PHP.

## Einleitung

PHP FFI (Foreign Function Interface) ist eine PHP-Erweiterung, mit der Sie Shared Libraries laden, C-Funktionen aufrufen und auf C-Datenstrukturen zugreifen. Sie können vorhandene C-APIs direkt aus PHP nutzen, ohne selbst eine PHP-Extension zu schreiben.

Die offizielle PHP-Dokumentation beschreibt FFI als eine Low-Level- und potenziell gefährliche Funktion. Sie sollte nur von Entwicklern eingesetzt werden, die C und die API der Zielbibliothek verstehen.

<Warning>
  FFI ist keine sicher abstrahierte, gewöhnliche PHP-API. Falsche Handhabung von Pointern, Ownership oder Freigabefunktionen kann zu Abstürzen oder Speicherfehlern führen.
</Warning>

FFI ist auch keine „Performance-Funktion". Selbst die offizielle Doku weist darauf hin, dass der Datenstruktur-Zugriff via FFI langsamer ist als native PHP-Arrays oder -Objekte. Der Grund für FFI ist nicht Geschwindigkeit, sondern die Nutzung bestehender C-Bibliotheken aus PHP heraus.

## Verfügbare Umgebungen und Einschränkungen

Die offizielle PHP-Dokumentation nennt folgende Wege, um die FFI-Erweiterung zu aktivieren:

* PHP mit `--with-ffi` bauen
* Unter Windows in `php.ini` `php_ffi.dll` aktivieren
* Über `ffi.enable` in `php.ini` die Verfügbarkeit steuern

`ffi.enable` kennt drei Werte:

| Wert      | Bedeutung                                                      |
| --------- | -------------------------------------------------------------- |
| `true`    | Die FFI-API ist aktiviert                                      |
| `false`   | Die FFI-API ist deaktiviert                                    |
| `preload` | FFI ist nur in der CLI-SAPI und für preloadete Dateien erlaubt |

<Info>
  In Webserver-Umgebungen wird häufig `ffi.enable=false` oder `ffi.enable=preload` gesetzt. Auch bei Hosting-Plattformen einschließlich Laravel Cloud lässt sich FFI in einer klassischen Webanwendung nicht garantiert nutzen.
</Info>

Wenn Sie FFI in einer Laravel-App einsetzen wollen, prüfen Sie deshalb zunächst, ob die Aufgabe in einem CLI-Tool oder Batch-Prozess erledigt werden kann. Sich darauf zu verlassen, dass FFI unter FPM oder Apache mod\_php dauerhaft aktiv ist, ist riskant.

## Grundlagen der Nutzung

Die vier zentralen Bausteine von PHP FFI sind `FFI::cdef()`, `FFI::new()`, `FFI::addr()` und `FFI::string()`.

```php theme={null}
<?php

$ffi = FFI::cdef(<<<'CDEF'
typedef unsigned int time_t;
typedef unsigned int suseconds_t;

struct timeval {
    time_t tv_sec;
    suseconds_t tv_usec;
};

struct timezone {
    int tz_minuteswest;
    int tz_dsttime;
};

int gettimeofday(struct timeval *tv, struct timezone *tz);
CDEF, 'libc.so.6');

$tv = $ffi->new('struct timeval');
$tz = $ffi->new('struct timezone');

$ffi->gettimeofday(FFI::addr($tv), FFI::addr($tz));

echo $tv->tv_sec.PHP_EOL;
```

Merken Sie sich zu diesem Beispiel:

* `FFI::cdef()` erzeugt aus einer C-Deklaration und einem Bibliotheksnamen ein FFI-Objekt.
* `$ffi->new('struct timeval')` allokiert die C-Datenstruktur.
* `FFI::addr($tv)` erzeugt ein Pointer-Argument wie `struct timeval *`.
* Auf Struktur-Felder greifen Sie über `$tv->tv_sec` zu.

Für APIs, die Strings oder Binärdaten liefern, nutzen Sie `FFI::string()`.

```php theme={null}
<?php

$jsonPtr = $ffi->new('char*');

// Annahme: Die C-Seite schreibt den Ergebnis-Pointer in $jsonPtr
$result = $ffi->some_function(FFI::addr($jsonPtr));

if ($result !== 0) {
    throw new RuntimeException('C API call failed.');
}

$json = FFI::string($jsonPtr);
```

<Tip>
  Von `FFI::new()` allokierter Speicher wird üblicherweise über PHP-Referenzzähler freigegeben. Pointer, die eine C-Bibliothek zurückgibt, müssen aber unter Umständen mit der bibliothekseigenen `free`-Funktion freigegeben werden. Prüfen Sie pro API, welche Ownership zutrifft.
</Tip>

## Header-Dateien einlesen

`FFI::load()` liest Deklarationen aus einer C-Header-Datei. Diese kann `FFI_SCOPE` und `FFI_LIB` enthalten.

```c theme={null}
#define FFI_SCOPE "mylib"
#define FFI_LIB "/absolute/path/to/libmylib.so"

typedef struct MyContext MyContext;

MyContext *mylib_new(void);
void mylib_delete(MyContext *context);
```

```php theme={null}
<?php

FFI::load(__DIR__.'/mylib.h');

$ffi = FFI::scope('mylib');
$context = $ffi->mylib_new();
```

Allerdings weist die offizielle PHP-Dokumentation darauf hin, dass weder `FFI::cdef()` noch `FFI::load()` gewöhnliche C-Präprozessor-Direktiven verarbeiten. `#include`, `#define` und Bedingungskompilate lassen sich nicht direkt übergeben.

In der Praxis wählt man daher meist einen dieser zwei Wege:

<Steps>
  <Step title="Bei einfachen APIs direkt in `FFI::cdef()` schreiben">
    Sind wenige Typen und Funktionen betroffen, betten Sie die benötigten Deklarationen direkt im PHP-Code ein.
  </Step>

  <Step title="Bei komplexen APIs eine vorverarbeitete Header-Datei pflegen">
    Verwalten Sie einen speziell für FFI aufbereiteten Header — ohne Makros und Bedingungen — als separate Datei.
  </Step>
</Steps>

## Praxisbeispiel: VOICEVOX Core for PHP

[VOICEVOX Core for PHP](/de/packages/voicevox-core-php) ist ein reales Beispiel, das die dynamische C-Bibliothek von VOICEVOX CORE aus reinem PHP ansteuert. Die dortigen FFI-Muster sind kompakt zusammengefasst und daher leichter nachzuvollziehen als abstrakte Erklärungen.

### 1. Ein FFI-eigenes Header-File pflegen

Der Original-Header von VOICEVOX Core wird nicht direkt verwendet. Stattdessen werden in `headers/voicevox_core_ffi.h` FFI-spezifische Deklarationen ausgelagert, unter anderem für opake Pointer, Structs und free-Funktionen.

```c theme={null}
typedef struct VoicevoxSynthesizer VoicevoxSynthesizer;

typedef struct VoicevoxInitializeOptions {
    int32_t  acceleration_mode;
    uint16_t cpu_num_threads;
} VoicevoxInitializeOptions;

int32_t voicevox_synthesizer_new(
    const struct VoicevoxOnnxruntime *onnxruntime,
    const struct OpenJtalkRc *open_jtalk,
    struct VoicevoxInitializeOptions options,
    struct VoicevoxSynthesizer **out_synthesizer
);

void voicevox_synthesizer_delete(struct VoicevoxSynthesizer *synthesizer);
void voicevox_json_free(char *json);
void voicevox_wav_free(uint8_t *wav);
```

Entscheidend an diesem Design ist, dass die detaillierten internen Strukturen der C-Seite nicht an PHP durchgereicht werden — es zeigen sich nur opake Handles und Funktionsaufrufe. So bleibt die zu handhabende Fläche in PHP klein, und Wrapper-Klassen fallen sauberer aus.

### 2. `FFI::cdef()` an einer Stelle konzentrieren

`src/VoicevoxFFI.php` bündelt das Header-Laden und die Bibliothekspfad-Auflösung an einem Ort.

```php theme={null}
<?php

class VoicevoxFFI
{
    private static ?FFI $ffi = null;

    public static function getInstance(): FFI
    {
        return self::$ffi ??= FFI::cdef(
            file_get_contents(__DIR__.'/../headers/voicevox_core_ffi.h'),
            self::getLibraryPath(),
        );
    }

    public static function getLibraryPath(): string
    {
        if ($path = getenv('VOICEVOX_CORE_LIB_PATH')) {
            return $path;
        }

        return match (PHP_OS_FAMILY) {
            'Darwin' => 'libvoicevox_core.dylib',
            'Windows' => 'voicevox_core.dll',
            default => 'libvoicevox_core.so',
        };
    }
}
```

Damit muss der Rest Ihrer App `FFI::cdef()` nicht direkt aufrufen. Auch Unterschiede beim Bibliothekspfad lassen sich über eine Umgebungsvariable und Betriebssystem-Erkennung abfangen.

### 3. Out-Parameter kapseln und in Objekte überführen

Der Konstruktor in `src/Synthesizer.php` allokiert einen `struct VoicevoxSynthesizer*` und übergibt dessen Adresse an `voicevox_synthesizer_new()`.

```php theme={null}
<?php

$options = $this->ffi->voicevox_make_default_initialize_options();
$options->acceleration_mode = $accelerationMode->value;
$options->cpu_num_threads = $cpuNumThreads;

$ptr = $this->ffi->new('struct VoicevoxSynthesizer*');

$result = $this->ffi->voicevox_synthesizer_new(
    $onnxruntime->handle(),
    $openJtalk->handle(),
    $options,
    FFI::addr($ptr),
);

$this->handle = $ptr;
```

Dieses Muster ist die Grundform, wie Sie den C-Ausdruck `Type **out_value` in PHP annehmen:

* `new('struct VoicevoxSynthesizer*')` legt die Pointer-Variable an.
* `FFI::addr($ptr)` liefert das `Type **`-Argument.
* Das erhaltene Handle halten Sie als Property des PHP-Objekts.

### 4. Von C zurückgegebenen Speicher in einen PHP-String kopieren und sofort freigeben

VOICEVOX Core for PHP kopiert JSON-Strings und WAV-Binärdaten zunächst mit `FFI::string()` in einen PHP-String und ruft anschließend die C-seitige Free-Funktion auf.

```php theme={null}
<?php

$jsonPtr = $this->ffi->voicevox_synthesizer_create_metas_json($this->handle);

$json = FFI::string($jsonPtr);
$this->ffi->voicevox_json_free($jsonPtr);

return $json;
```

```php theme={null}
<?php

$wavSize = $this->ffi->new('uint64_t');
$wavPtr = $this->ffi->new('uint8_t*');

$result = $this->ffi->voicevox_synthesizer_tts(
    $this->handle,
    $text,
    $styleId,
    $options,
    FFI::addr($wavSize),
    FFI::addr($wavPtr),
);

$wav = FFI::string($wavPtr, (int) $wavSize->cdata);
$this->ffi->voicevox_wav_free($wavPtr);

return $wav;
```

Dieses Muster „kopieren und sofort freigeben" ist eines der wichtigsten Implementierungsmuster für FFI. Sie halten den C-Buffer aus dem PHP-Lifecycle heraus und vereinfachen die Ownership.

## Hinweise und Best Practices

| Aspekt             | Praxis-Tipp                                                                               |
| ------------------ | ----------------------------------------------------------------------------------------- |
| Laufzeitumgebung   | Entwerfen Sie zunächst für die CLI                                                        |
| Header-Verwaltung  | Reichen Sie den Original-Header nicht durch — pflegen Sie FFI-Deklarationen separat       |
| Bibliothekspfad    | Machen Sie ihn per absolutem Pfad oder Umgebungsvariable umschaltbar                      |
| Speicherverwaltung | Kopieren Sie über `FFI::string()` und rufen Sie zwingend die zugehörige Free-Funktion auf |
| Wrapper-Design     | Halten Sie `CData` nicht app-weit sichtbar, kapseln Sie es in eigene Klassen              |
| Kompatibilität     | Prüfen Sie PHP-Version und ABI-Änderungen der Zielbibliothek gemeinsam                    |

<Warning>
  Wenn Sie FFI direkt in den Request/Response-Zyklus Ihrer Laravel-App einbetten, werden Umgebungsunterschiede und Fehleranalysen schnell schwierig. Machen Sie es zunächst in CLI-Kommandos oder als Einzelprozess außerhalb der Queue produktionsreif.
</Warning>

FFI eignet sich für Fälle, in denen eine bereits stabile C-API existiert und der Aufwand, eine PHP-Extension zu schreiben, zu groß wäre. Für Features, die im normalen Web-Hosting laufen sollen, oder für rein in PHP realisierbare Verarbeitung ist FFI nicht der richtige Weg.

## Fazit

Mit PHP FFI rufen Sie vorhandene C-Bibliotheken aus reinem PHP heraus auf. FFI ist jedoch ein Low-Level- und riskanter Mechanismus, der sich nicht immer in Webservern nutzen lässt.

Die Implementierung von VOICEVOX Core for PHP zeigt, was in der Praxis zählt:

1. Einen eigenen FFI-Header pflegen.
2. `FFI::cdef()` und die Bibliothekspfad-Auflösung an einer Stelle bündeln.
3. Opake Pointer in eigenen Klassen kapseln.
4. Von C zurückgegebenen Speicher in PHP kopieren und mit der dedizierten Free-Funktion freigeben.

Wenn Sie zusätzlich die Seite zu VOICEVOX Core for PHP lesen, verbinden Sie das Konzept von FFI direkt mit einem konkreten Paketdesign.


## Related topics

- [VOICEVOX Core für PHP](/de/packages/voicevox-core-php/index.md)
- [Installation und Konfiguration - VOICEVOX für Laravel](/de/packages/laravel-voicevox/installation.md)
- [Native-Modus: Talk (Text-Sprachsynthese) - VOICEVOX für Laravel](/de/packages/laravel-voicevox/native-talk.md)
- [Native-Modus: Song (Gesangs-Sprachsynthese) - VOICEVOX für Laravel](/de/packages/laravel-voicevox/native-song.md)
- [VOICEVOX für Laravel](/de/packages/laravel-voicevox/index.md)
