© Andrey Suslov/Shutterstock.com
Vor- und Nachteile des Foreign Function Interface

PHP FFI: Anwendung und Funktionsweise


PHP 7.4 brachte eine ganze Reihe neuer Funktionen mit sich. Eine davon ist die Erweiterung Foreign Function Interface, die direkt im Core implementiert wurde. In diesem Artikel werden wir herausfinden, wie fast jede C Library direkt aus PHP heraus aufgerufen werden kann, wie man gängige Pitfalls überwindet, wann man diesen Ansatz verwenden sollte und vor allem, wann nicht.

Betrachtet man sich die Release Notes von PHP 7.4 genauer, so fällt auf, dass das Update eine ganze Reihe neuer Funktionen enthält. Einige wurden schon lange erwartet, wie etwa die Typed Properties, anderen waren eher überraschend. Unabhängig davon ist das FFI (Foreign Function Interface) wohl das am wenigsten selbsterklärende.

Was bedeutet FFI? Nach Wikipedia kann es wie folgt charakterisiert werden: „Ein Mechanismus, mit dem ein in einer Programmiersprache geschriebenes Programm Routinen aufrufen oder in einer anderen Programmiersprache geschriebene Dienste in Anspruch nehmen kann“ [1]. Aber das ist lediglich eine Buchdefinition, die nichts darüber aussagt, welche Probleme in der realen Welt gelöst werden können. Damit bleibt eine weitere Frage offen: Welche Probleme löst FFI?

FFI in PHP

FFI kann dabei helfen, einen Code wiederzuverwenden, der bereits in einer völlig anderen Programmiersprache geschrieben wurde. Als Beispiel sei ein Konnektor zu einer Datenbank genannt, die noch nicht nach PHP portiert wurde. Eine andere mögliche Verwendung ist die Beschleunigung einiger Teile des Codes. In diesem Fall kann ein Algorithmus in der Programmiersprache geschrieben werden, der eine bessere Leistung für den Gebrauch bietet; zum Beispiel zur Durchführung einer komplizierteren wissenschaftlichen Berechnung. Schließlich können auch Dinge realisiert werden, die in der Sprache nicht unterstützt werden – etwa das Kommunizieren mit einer Hardware. PHP-Programmierer stellen sich nun zu Recht die Frage, ob all das nicht schon mit bereits vorhandenen Erweiterungen getan werden kann. Die Antwort ist ja – wir können all die genannten Aufgaben mit Hilfe von PHP-Erweiterungen erledigen. Aber FFI hat dennoch seine Vorzüge, denn es verspricht eine einfachere Nutzung, Wartung und Deployment und eine bessere Portabilität zwischen den PHP-Versionen. Der Hauptgrund dafür ist, dass man alles in einfachem PHP realisieren kann, sodass es nicht notwendig ist, ein Compilation Toolkit einzurichten oder die Deployment-Verfahren zu ändern. Was die Portabilität betrifft, so kann das API der PHP-Erweiterung je nach Version variieren, was in der Regel dazu führt, dass die gesamte Erweiterung neu kompiliert werden muss. Ein weiterer möglicher Vorteil ist, dass keine neue Programmiersprache erlernt werden muss. Wir sind immer noch bei PHP, richtig? Nun, das ist etwas komplizierter, wie wir später sehen werden.

Im Vergleich zu den Erweiterungen hat das FFI allerdings auch einige Einschränkungen. So kann die interne Funktionsweise von PHP nicht einfach modifiziert werden, es gibt also keine einfache Möglichkeit, Debug-Tools wie Xdebug zu erstellen. Außerdem sind PHP-Erweiterungen in der Regel schneller, da sie in kompilierten Sprachen erstellt werden. Die letzte Einschränkung ist, dass FFI nur eine Zielsprache unterstützt; oft ist diese Sprache C. FFI wird in PHP 7.4 durch eine brandneue Erweiterung implementiert, die als Teil des Cores zu jeder Zeit verfügbar ist. Sie ist auch ausgereifter als frühere Versuche, FFI im PHP-Ökosystem und plattformübergreifend einzusetzen, zumindest bis zu einem gewissen Grad. Der wichtigste Vorbehalt ist, dass die Erweiterung es dem Programmierer lediglich erlaubt, nur C Libraries oder Libraries aufzurufen, die das sogenannte C ABI bereitstellen.

Erste Schritte

Wir werden die grundlegende Verwendung von FFI durch Umschreiben der Funktion abs() veranschaulichen, die einen absoluten Wert der angegebenen Zahl zurückgibt, wie Listing 1 zeigt.

Listing 1: abs.php

<?php $ffi = FFI::cdef( 'int abs(int j);', // function declaration in C language 'libc.so.6' // library from which the function will be called ); var_dump($ffi->abs(-42)); // int(42)

Als Erstes muss man ein Proxy-Objekt zwischen der Bibliothek und PHP erstellen. Eine der größten Herausforderungen bei FFI, unabhängig von der Programmiersprache, besteht darin, wie Funktionen aus einer Programmiersprache in einer anderen Programmiersprache abgebildet werden können. Die Autoren von FFI in PHP entschieden, dass das am besten durch das Parsen einer C-Funktionsdefinition gelöst werden kann. Es ist also nicht überraschend, dass das erste Argument eine Funktionsdeklaration in C enthält. Der zweite Parameter muss der Name der Bibliothek sein, aus der die Funktion aufgerufen werden soll. In diesem Fall versuchen wir, Funktionen aus der Standard-C-Bibliothek zu verwenden.

Codebeispiele

Alle bereitgestellten Codebeispiele erfordern eine Mindestversion von PHP 7.4 und das Betriebssystem Linux oder Windows mit WSL. Sie können nicht unter Standard-Windows oder macOS ausgeführt werden. Der vollständige Quellcode ist zusammen mit einem vorbereiteten Docker Image unter https://github.com/kambo-1st/php-ffi-article verfügbar.

Die Funktionssignaturen sind in PHP und in C recht ähnlich, schließlich ist PHP eine Sprache im C-Stil. Dennoch gibt es einige Unterschiede:

  • Variablentypen müssen immer definiert werden.

  • Der Rückgabetyp wird vor dem Funktionsnamen deklariert.

Die Funktion wird über eine gleichnamige Methode auf dem Proxy-Objekt aufgerufen. Einfache Datentypen wie Integer, Float und Boolean werden beim Funktionsaufruf automatisch konvertiert. Die automatische Konvertierung kann manchmal ein zweischneidiges Schwert sein, insbesondere für PHP-Entwickler, wie wir in Listing 2 veranschaulichen werden.

Listing 2: abs-data-type.php

<?php $ffi = FFI::cdef( 'int abs(int j); long int labs(long int j);', 'libc.so.6' ); var_dump($ffi->abs(-2147483649)); // int(2147483647) var_dump($ffi->labs(-2147483649)); // int(2147483649)

In diesem Snippet versuchen wir zunächst, die abs()-Funktion mit einem Wert außerhalb des C-Integer-Bereichs aufzurufen. Dadurch entsteht bei dem Ergebniswert ein Overflow und die Funktion gibt das falsche Ergebnis zurück. Das PHP-Gegenstück der abs()-Funktion behandelt Float- und Integer-Werte ohne Probleme. Wenn wir eine korrekte Zahl erhalten wollen, müssen wir labs() aufrufen, das mit Werten außerhalb des C-Integer-Typs arbeitet. Datentypen in C sind viel komplexer als ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang