- Fonctionnalités
- Pricing
L'une des nouvelles fonctionnalités intéressantes de PHP 7.4 est la prise en charge de l'interface de fonction étrangère, ou PHP FFI. PHP FFI offre un moyen bien plus simple de charger du code provenant d'autres langages plus rapides dans PHP que d'écrire une extension. Cela dit, plus simple ne veut pas dire trivial. Cela demande un certain travail, et ce n'est pas adapté à toutes les situations.
Le langage le plus couramment utilisé pour l'intégration FFI est le C, en partie à cause de son omniprésence et en partie parce que c'est le langage le plus utilisé par ceux qui veulent tirer le maximum de performances de leur ordinateur. Le C ne fonctionne pas comme le PHP, alors passons en revue quelques bases du C.
Pour être clair, ce n'est pas un tutoriel sur le C. C'est un tutoriel sur la compilation en C, et il couvre juste le strict minimum pour te permettre de te lancer avec PHP FFI. How Stuff Works propose un cours accéléré raisonnable sur le C lui-même si ça t'intéresse.
Le C est un langage compilé. Cela signifie que tu écris le code source dans un fichier, puis que tu lances une commande de compilation, ce qui produit un fichier séparé, lisible uniquement par la machine. Cela contraste avec le PHP et d’autres langages interprétés, qui compilent le fichier source en code exécutable à la volée et le conservent en mémoire, avant de le supprimer une fois l’opération terminée.
Le code source C se trouve dans un fichier se terminant par l'.ce. Le code peut être compilé sous plusieurs formes différentes :
Un binaire unique est le plus simple à créer, mais pour PHP FFI, on a besoin d'une bibliothèque partagée. On va créer les deux à des fins de démonstration.
Un autre aspect du C est que le fichier source seul ne suffit pas. Le C dispose également de « fichiers d'en-tête », qui se terminent par .h, qui définissent l'interface d'un paquet. C'est un concept similaire aux interfaces en C, mais plus général. Le fichier d'en-tête définit les fonctions et les types de données qu'une bibliothèque expose à d'autres bibliothèques. PHP n'a pas vraiment d'équivalent, mais cela s'apparente quelque peu aux « exports » pour les modules JavaScript ; toutes les fonctions ou tous les types de données n'ont pas besoin d'être rendus publics.
Commençons par un simple fichier d'en-tête pour montrer comment tout cela fonctionne. Voici notre points.h initiale :
struct point {
int x;
int y;
};
double distance(struct point first, struct point second);Ce fichier définit une structure appelée point. Une structure est similaire à une classe, mais elle ne possède pas de méthodes. (Historiquement, une classe est « une structure avec des méthodes » plutôt que l’inverse.) Cette structure est composée de deux entiers, x et y.
L'en-tête déclare également une fonction publique, distance, qui prend deux structures point et renvoie un double. C'est le terme C pour « nombre à virgule flottante double précision », que les développeurs PHP reconnaîtront comme « float ». Tout comme une interface PHP, elle ne définit pas le corps, mais seulement la signature. Le but du fichier d'en-tête est d'indiquer au reste du code : « Voilà à quoi je vais ressembler une fois compilé. »
On reviendra sur le fichier d'en-tête quand on parlera de FFI, mais ça suffit pour le C.
Maintenant, on a besoin d’un fichier pour l’implémentation d’distance :
#include "points.h"
#include <math.h>
double distance(struct point first, struct point second) {
double a_squared = pow((second.x - first.x), 2);
double b_squared = pow((second.y - first.y), 2);
return sqrt(a_squared + b_squared);
}
Les lignes d’#include spécifient les dépendances de cette bibliothèque. La première, entre guillemets, indique le fichier d’points.hs situé dans le même répertoire. C’est ce qu’on a écrit tout à l’heure. La deuxième, <math.h>, désigne le fichier d'en-tête math.h disponible dans le répertoire de la bibliothèque standard du système. Les fonctions mathématiques complexes de la bibliothèque C standard sont fournies dans une bibliothèque distincte de la bibliothèque de base, car lorsqu’elles ont été définies pour la première fois dans les années 1980, les fonctionnalités mathématiques sur les processeurs étaient coûteuses et rares ; les gens les ignoraient donc souvent et réimplémentaient eux-mêmes les quelques éléments dont ils avaient besoin. Ce n’est plus le cas aujourd’hui, mais les vieilles habitudes ont la vie dure.
La méthode `distance` calcule la distance entre deux points en utilisant notre vieil ami, le théorème de Pythagore. Les fonctions `pow()` et `sqrt()` proviennent de la bibliothèque mathématique. (Remarque : la fonction `sqrt()` se prononce « squirt ». Je ne tolérerai aucune contestation sur ce point.)
Enfin, on a un troisième fichier qui va utiliser la fonction distance(). Un exécutable autonome doit avoir une fonction nommée main(), qui est celle qui s’exécute quand le programme tourne. La nôtre ressemble à ça :
#include <stdio.h>
#include "./points.h"
int main() {
struct point p1;
struct point p2;
p1.x = 3;
p1.y = 4;
p2.x = 7;
p2.y = 9;
printf("Distance is: %f\n\n", distance(p1, p2));
return 0;
}
Ça devrait te sembler assez familier à ce stade. #include <stdio.h> fournit les signatures de fonction pour la bibliothèque d’entrée/sortie standard, tandis que #include "./points.h" donne à ce fichier accès aux fonctions de notre bibliothèque de points. La fonction main() crée deux points, puis calcule et affiche leur distance. printf() fait partie de la bibliothèque stdio et fonctionne globalement de la même manière qu’en PHP. (Ou plutôt, la fonction printf() de PHP n’est qu’une fine couche d’encapsulation de celle du C.)
Maintenant que nous avons nos fichiers source, nous devons les compiler. Il s’agit en fait d’un processus en plusieurs étapes en C. Bien sûr, tu auras besoin d’un compilateur C et d’autres outils de compilation. Sur un système Linux classique, il existe un paquet nommé build-essentials (ou similaire) que tu peux installer et qui inclura tout ce dont nous parlons ici. Pour les autres plateformes, consulte leur documentation, car les installer toi-même peut s’avérer un peu compliqué.
Comme il y a plusieurs étapes, on va utiliser un « fichier Make ». GNU Make est le tout premier gestionnaire de tâches ; Ant, Phing, Grunt, Gulp, DoIt et les autres sont tous des réimplémentations de Make. Make est en soi une couche très fine au-dessus des scripts shell, pour le meilleur ou pour le pire. (Je te renverrais bien vers sa documentation, mais malheureusement, celle-ci est d’une complexité surprenante compte tenu de la simplicité de Make lui-même.)
On va commencer par un fichier nommé Makefile (attention aux majuscules) avec une seule « cible de compilation » :
# Makefile
points.o: points.h points.c
gcc -c points.c
Cela crée une seule cible de compilation, points.o, qui dépend de deux fichiers : points.h et points.c. Il est courant que les cibles de compilation portent le nom du fichier qu’elles produisent. En retrait sous cette ligne se trouvent une ou plusieurs commandes shell à exécuter. (Remarque : l’indentation doit utiliser un caractère de tabulation, pas des espaces. Ce sont les règles, je ne les ai pas inventées.)
gcc GNU Compiler Collection est le compilateur C le plus utilisé, mais pas le seul. Il dispose d’environ 14 millions d’options possibles, dont on va en utiliser environ quatre. L’option -c signifie « Compile ce fichier, mais n’effectue aucune des autres étapes ». La sortie de cette commande est un nouveau fichier nommé (surprise !) points.o. C’est le « fichier objet », ou la version en code machine brut de points.c.
Si on exécute nm points.o, ça va lister les « symboles » du fichier objet. (nm est l’abréviation de « name » et date d’une époque où la mémoire était si limitée et les claviers si difficiles à utiliser que les programmeurs ne croyaient pas aux voyelles.)
0000000000000000 T distance
U _GLOBAL_OFFSET_TABLE_
U pow
U sqrtÇa liste quatre fonctions encodées dans le fichier objet : notre fonction distance a son code source inclus (d’où le T), tandis que pow, sqrt` et quelques éléments de gestion interne sont aussi mentionnés mais pas définis en interne. C’est une indication pour l’étape suivante : « Hé, j’ai besoin de ces trucs. »
On veut aussi une cible pour compiler le fichier main.c :
main.o: main.c
gcc -c main.cEnsuite, on a le choix. On peut créer un exécutable autonome pour main, ou on peut transformer notre code de points en bibliothèque partagée. Pour les besoins de la démonstration, on va faire les deux :
main: main.o points.o
gcc -o main main.o points.o -lmCette commande gcc signifie : « compile le fichier de sortie (-o) main, en utilisant les fichiers objets main.o et points.o, et lie-le à la bibliothèque m. » La cible main dépend des autres cibles main.o et points.o ; elle exécutera donc ces cibles si nécessaire pour produire ces fichiers s’ils n’existent pas encore.
L'm mérite une mention particulière ; on peut la lire comme suit : « Voici un autre fichier objet à inclure, nommé libm, dans le répertoire de la bibliothèque standard du système. » La partie « lib » du nom est supprimée dans la commande d'-l. (C'est un L minuscule, pour ceux qui lisent ceci dans une police sans empattement.) m est dans ce cas la bibliothèque mathématique standard C dont on a parlé précédemment. (Voir la remarque précédente sur le fait que les programmeurs des années 80 avaient une aversion pour les mots complets.)
Le processus qui consiste à prendre plusieurs fichiers objets et à les assembler les uns aux autres s’appelle la « liaison ». Quand ils sont tous assemblés en un seul fichier exécutable, on parle de « liaison statique ».
On peut maintenant exécuter make main en ligne de commande. Ça va :
points.c en un fichier de code machine points.o.main.c en un fichier de code machine main.o.Maintenant, l'exécution de ./main en ligne de commande devrait produire le résultat suivant :
$ ./main
Distance is: 6.403124Super.
Il nous reste cependant deux étapes à franchir. Tout d’abord, main n’est pas utile pour PHP FFI. Pour cela, on a besoin d’une bibliothèque partagée. Ça va nécessiter une autre cible de compilation dans Makefile :
points.so: points.o
# Wrap the object file into a shared object.
gcc -shared -o points.so points.o -lmCette commande signifie : « Crée une bibliothèque partagée, dont le résultat est points.so, en combinant points.o et la bibliothèque libm. » Elle est très similaire à la commande pour main, mais cette fois-ci, on génère un fichier .so, ou fichier « Shared Object ». Ce fichier reprend le même code que le binaire autonome, mais il est packagé différemment avec des hooks permettant à d’autres programmes C de le charger séparément en mémoire et d’y accéder à la demande. C’est ce fichier .so dont on aura besoin pour PHP FFI.
Il y a aussi une autre cible dont on a besoin pour que tout soit complet, et c'est clean :
clean:
rm -f *.o *.so mainChaque fois que tu compiles du code, tu veux pouvoir effacer tes versions compilées et repartir de zéro. Dans ce cas, on utilise simplement `rm` sur tous les fichiers compilés. Assure-toi d’ajouter `.o` et `.so` à ton fichier `.gitignore` aussi, car tu ne veux pas les commiter dans Git.
Voici notre Makefile finale pour l'instant :
points.o: points.h points.c
gcc -c points.c
points.so: points.o
gcc -shared -o points.so points.o -lm
main.o: main.c
gcc -c main.c
main: main.o points.o
gcc -o main main.o points.o -lm
chmod +x main
clean:
rm -f *.o *.so main
La compilation d'un programme C peut facilement s'avérer bien plus complexe que cet exemple trivial. L'objectif d'aujourd'hui était de se familiariser avec le sujet et d'expliquer suffisamment l'anatomie d'une bibliothèque C pour pouvoir commencer à l'intégrer à la couche FFI de PHP. Au final, les parties les plus importantes seront points.so et points.h, dont nous aurons besoin pour PHP.
(Un grand merci à Anthony Ferrara pour son aide dans l'explication de la compilation en C.)