Inleiding
PHP FFI (Foreign Function Interface) is een PHP-extensie waarmee je shared libraries laadt, C-functies aanroept en toegang krijgt tot C-datastructuren. Zonder zelf een PHP-extensie te bouwen, kun je bestaande C-API’s direct vanuit PHP gebruiken. De officiële PHP-documentatie beschrijft FFI als een low-level en gevaarlijke functie. Alleen ontwikkelaars die C en de API van de doelbibliotheek begrijpen, zouden het moeten gebruiken. FFI is ook geen “functie om dingen sneller te maken”. Ook de officiële PHP-documentatie legt uit dat toegang tot datastructuren via FFI trager is dan native PHP-arrays en -objecten. De reden om FFI te kiezen is niet snelheid, maar dat je een bestaande C-bibliotheek vanuit PHP wilt gebruiken.Omgevingen en beperkingen van FFI
De officiële PHP-documentatie beschrijft het inschakelen van de FFI-extensie als volgt:- PHP bouwen met
--with-ffi - Op Windows
php_ffi.dllinschakelen inphp.ini - Met
ffi.enableinphp.inibepalen of FFI gebruikt mag worden
ffi.enable kent drie waarden:
In webserveromgevingen wordt vaak
ffi.enable=false of ffi.enable=preload gebruikt. Ook in hostingomgevingen, inclusief Laravel Cloud, is FFI niet altijd bruikbaar in een gewone webapp.Basisgebruik
De basis van PHP FFI bestaat uit vier onderdelen:FFI::cdef(), FFI::new(), FFI::addr() en FFI::string().
FFI::cdef()maakt een FFI-object van een string met C-declaraties en de naam van de shared library$ffi->new('struct timeval')alloceert een C-datastructuurFFI::addr($tv)maakt een pointerargument zoalsstruct timeval *- Je benadert structvelden als
$tv->tv_sec
FFI::string().
Headerbestanden inladen
MetFFI::load() laad je declaraties uit een C-headerbestand. In het headerbestand kun je FFI_SCOPE en FFI_LIB opnemen.
FFI::cdef() als FFI::load() geen gewone C-preprocessorinstructies ondersteunt. #include, #define en conditionele compilatie kun je niet zomaar doorgeven.
In de praktijk kies je daarom meestal een van deze twee opties:
1
Bij een simpele API: direct in FFI::cdef() schrijven
Zijn er weinig afhankelijke types en functies, dan neem je alleen de benodigde declaraties op aan de PHP-kant.
2
Bij een complexe API: een voorbewerkte header maken
Beheer een aparte, FFI-specifieke header waaruit macro’s en conditionele compilatie uit de oorspronkelijke header zijn verwijderd.
Een echte use case: VOICEVOX Core for PHP
VOICEVOX Core for PHP is een praktijkvoorbeeld waarin de dynamische C-bibliotheek van VOICEVOX CORE vanuit pure PHP wordt gebruikt. Het bundelt de praktische FFI-patronen en is daardoor begrijpelijker dan abstracte uitleg.1. Een voor FFI geschikt gemaakte header bijhouden
De oorspronkelijke header van VOICEVOX Core wordt niet direct gebruikt; inheaders/voicevox_core_ffi.h zijn de declaraties voor FFI apart gezet. Daarin worden opaque pointers, structs en free-functies expliciet gemaakt.
2. FFI::cdef() op één plek concentreren
src/VoicevoxFFI.php concentreert het inladen van de header en het bepalen van het bibliotheekpad op één plek.
FFI::cdef() niet aan te roepen. Ook verschillen in bibliotheekpaden vang je op met omgevingsvariabelen en OS-detectie.
3. Out parameters wrappen tot objecten
De constructor vansrc/Synthesizer.php alloceert een struct VoicevoxSynthesizer* en geeft het adres daarvan door aan voicevox_synthesizer_new().
Type **out_value uit een C-API in PHP op te vangen:
- Maak een pointervariabele met
new('struct VoicevoxSynthesizer*') - Geef
Type **door metFFI::addr($ptr) - Bewaar de verkregen handle als property van een PHP-object
4. Door C teruggegeven geheugen naar een PHP-string kopiëren en direct vrijgeven
VOICEVOX Core for PHP kopieert JSON-strings en WAV-binaries na ontvangst eerst metFFI::string() naar een PHP-string en roept daarna de free-functie aan de C-kant aan.
Aandachtspunten en best practices
FFI is geschikt wanneer er al een stabiele C-API bestaat en het bouwen van een eigen PHP-extensie te ver gaat. Het is juist ongeschikt voor functionaliteit die op gewone webhosting moet draaien of voor verwerking die volledig in PHP kan.
Samenvatting
Met PHP FFI kun je bestaande C-bibliotheken vanuit pure PHP aanroepen. Maar FFI is een low-level en gevaarlijk mechanisme, en het is niet altijd beschikbaar op een webserver. Kijk je naar de implementatie van VOICEVOX Core for PHP, dan blijken in de praktijk deze vier punten belangrijk:- Houd een aparte header voor FFI bij
- Concentreer
FFI::cdef()en het bepalen van het bibliotheekpad op één plek - Wrap opaque pointers in speciale klassen
- Kopieer door C teruggegeven geheugen naar PHP en geef het daarna vrij met de speciale functie