找回密码
 立即注册
首页 业界区 业界 PHP FFI 完整指南

PHP FFI 完整指南

俏挺喳 2025-11-20 09:20:00
PHP FFI 完整指南

什么是 FFI,能用它做什么?

FFI(Foreign Function Interface,外部函数接口)是一种允许程序使用不同语言编写的库的技术。它比 RPC 或 API 快得多,因为不需要通过网络接口,程序将直接与二进制定义进行交互。
换句话说,通过使用 FFI,PHP 程序将能够使用 C、Rust、Golang 或任何其他能够生成 ABI 的语言编写的库。
FFI 允许使用来自编译语言(如 C、Rust 和 Golang)的库。但它不是一个神奇的工具,不能让两个不同的运行时在没有网络的情况下相互通信。
通过在 PHP 中采用 FFI,能够为项目使用任何需要的共享对象:Windows 的 .dll、Linux 的 .so 或 MacOS 的 .dylib。
这提供了一个跳出 PHP 虚拟机(Zend VM)的机会,几乎可以使用 PHP 编写任何想要的东西。使用像 raylib 或 libui 这样的 C 库不再需要依赖任何 C 扩展。
原文链接 PHP FFI 完整指南
FFI 会让代码运行得更快吗?

可能会认为,由于将使用 C 编写的外部代码,它可能比 PHP 更快。这种思路不一定错,但需要记住,语言不会施展魔法:它们只是按照指令去做。
当涉及到 CPU 时间时,使用 FFI 从 PHP 调用外部函数可能会花费在纯 PHP 中执行相同操作所需时间的两倍。这是因为 PHP 的虚拟机已经非常优化,与外部代码交互需要一个翻译过程,这会增加处理成本。
这是正常的,目前所有支持 FFI 的语言在使用 FFI 时性能都会下降。
可以优化内存消耗! 正如《精通 PHP 中的位运算操作》一文中所述,每个 PHP 变量都有一个内部类型 zval,它做了很多事情来让 PHP 的生活更轻松,比如用 INT64 类型表示每个 PHP 整数。所以即使 0x10 也会在 PHP 中存储为 0x0000000000000010(并且 zval 的所有其他成员都分配了它们的指针)。
所以一个好的实践是在使用 PHP 处理事物和使用 FFI 处理内存中的对象之间找到平衡。这样可以优化内存消耗,这可能会也可能不会影响整体 CPU 时间。
FFI 还是 C 扩展,应该使用哪个?

FFI 通常被作为原型设计工具:用它迈出第一步,然后迁移到原生扩展代码。
如果代码不太关心性能(不太可能,但可能发生),使用 FFI 来扩展 PHP 的能力是可以的。不要忘记,PHP 中的 FFI 仍然是实验性的,可能会不时遇到错误或其核心的 API 更改。
C 扩展通常应该用 C 代码编写,这对许多 PHP 工程师来说是一个可怕的障碍。但它们集成到 PHP 的虚拟机中,所以扩展会比 FFI 快得多,因为它们直接从 C 调用 C 代码(不需要翻译),并且只映射将与最终用户交互的代码。
扩展是针对特定 PHP 版本编译的,这会产生一个恼人的依赖关系,可能会拖慢你升级 PHP 版本的速度。如果你准备自己升级扩展并遵循其社区提出的集成过程,那就更好了,但仍然会花费你几天时间。
FFI 将始终开箱即用,不会阻止你升级 PHP 版本,因为 FFI 扩展是 PHP 核心的一部分。
FFI 入门:构建一个 raylib 窗口

PHP 本身绝对不能做的一件事是操作操作系统上的原生窗口。有一些扩展可以做到这一点,比如之前介绍的 PHP-GTK 和 raylib 扩展,另一个选择是使用 FFI。
这里选择 Raylib 作为示例,因为它的接口非常简化且易于使用。
安装 raylib 的共享对象

对于 Mac 用户来说,这将像通过 HomeBrew 安装 raylib 一样简单:
  1. $ brew install raylib
复制代码
其他系统有完整的安装指南。这里你可以找到在 Windows 上安装和在 Linux 上安装的指南。
安装完所有内容后,你的系统中应该有一个可用的共享对象。在 MacOS 上,你可以在 /usr/local/Cellar/raylib//lib 下看到 libraylib.dylib 文件:
  1. $ ls -la /usr/local/Cellar/raylib/3.5.0/lib
  2. cmake           libraylib.351.dylib libraylib.dylib
  3. libraylib.3.5.0.dylib   libraylib.a     pkgconfig
复制代码
在 Windows 上你关心的是 .dll 文件,在 GNU Linux 上你关心的是 .so 文件。
首先在 C 中原型设计

了解它在 PHP 中使用 FFI 是否能良好工作的最简单方法是首先了解它在 C 中应该如何表现,对吧?
所以我们要做的第一件事是使用 raylib 在 C 中构建一个简单的程序,该程序将构建我们的窗口。让我们创建一个 hello_raylib.c 文件,内容如下:
  1. #include "raylib.h"
  2. int main(void)
  3. {
  4.   Color white = { 255, 255, 255, 255 };
  5.   Color red = { 255, 0, 0, 255 };
  6.   InitWindow(
  7.     800,
  8.     600,
  9.     "Hello raylib from C"
  10.   );
  11.   while (
  12.     !WindowShouldClose()
  13.   ) {
  14.     ClearBackground(white);
  15.     BeginDrawing();
  16.       DrawText(
  17.         "Hello raylib!",
  18.         400,
  19.         300,
  20.         20,
  21.         red
  22.       );
  23.     EndDrawing();
  24.   }
  25.   CloseWindow();
  26. }
复制代码
上面的代码应该创建一个大小为 800x600 的窗口,标题栏中有"Hello raylib from C"文本。在这个窗口内,应该出现一个红色的文本"Hello raylib!",其原点在屏幕中间。
让我们编译并运行上面的代码:
  1. $ gcc -o hello_raylib \
  2.   hello_raylib.c -lraylib
  3. $ ./hello_raylib
复制代码
注意:使用适用于你平台的 C 编译器。在我的情况下,我使用了 clang,但它应该或多或少以相同的方式工作。
下面你会看到预期的结果。
一个尺寸为 800x600 的原生窗口,标题为"Hello raylib from C",显示红色文本"Hello raylib!"
现在用 PHP!构建一个头文件

为了让 PHP 与 C(或其他语言)通信,我们必须首先创建一个接口。在 C 中,这样的接口由头文件表示。这正是为什么大多数 .c 文件在代码库中都有相应的 .h 文件:它概述了链接到它的文件可能会发现有用的常见对象和函数签名。
由于我们想引用 libraylib.dylib,头文件的第一行将包含以下 define,专门用于 FFI。所以让我们开始编写将与 PHP 代码交互的 raylib.h 文件:
  1. #define FFI_LIB "libraylib.dylib"
复制代码
注意:引用的文件可能会根据你的操作系统而改变。
Raylib 有很多很多函数,你可以在他们的速查表中查看。但我们不需要导入所有这些。事实上,我建议你只导入程序所需的那些。在我们的例子中,我们只需要 7 个:
  1. #define FFI_LIB "libraylib.dylib"
  2. void InitWindow(
  3.   int width,
  4.   int height,
  5.   const char *title
  6. );
  7. bool WindowShouldClose(void);
  8. void ClearBackground(
  9.   Color color
  10. );
  11. void BeginDrawing(void);
  12. void DrawText(
  13.   const char *text,
  14.   int x,
  15.   int y,
  16.   int size,
  17.   Color color
  18. );
  19. void EndDrawing(void);
  20. void CloseWindow(void);
复制代码
注意,一些函数签名需要由 raylib 构建的非常特定的类型。函数 ClearBackground 和 DrawText 需要一个 Color 类型的参数,我们也需要导入它。所以让我们把它添加到我们的头文件中:
  1. #define FFI_LIB "libraylib.dylib"
  2. typedef struct Color {
  3.   unsigned char r;
  4.   unsigned char g;
  5.   unsigned char b;
  6.   unsigned char a;
  7. } Color;
  8. void InitWindow(int width, int height, const char *title);
  9. // ...
复制代码
我们的 raylib.h 文件现在可以被 PHP 使用了。
将此头文件加载到 PHP 中

由于我们有一个头文件,我们可以使用 FFI::load() 函数像这样导入它:
[code]

相关推荐

您需要登录后才可以回帖 登录 | 立即注册