PHP教程

PHP FFI

FFI允许在纯 PHP 中加载共享库(.DLL 或 .so)、调用 C 函数、访问 C 数据结构,而无需深入了解 Zend 扩展 API,也无需学习第三方“中间”语言。该扩展的公共 API 由 FFI 类实现,其中包含几个静态方法和对象重载方法,用于执行与 C 数据的实际交互。

简单来说,这个扩展使得 PHP 开发者可以使用 C 语言编写的库,并在 PHP 中轻松地调用其中的函数和访问 C 语言中定义的数据结构,无需编写 C 扩展或了解底层的 C 函数调用方式。这个扩展的核心是 FFI 类,它提供了一系列静态方法和对象重载方法,用于在 PHP 中调用 C 函数和访问 C 数据结构。

一、基础FFI用法

在深入了解 FFI API 细节之前,先看几个示例,展示 FFI API 在常规任务中的简单使用。

注意:其中一些示例需要 libc.so.6,因此在没有该库的系统上无法运行。

从共享库中调用函数:

<?php
// 创建 FFI 对象,加载 libc 和输出函数 printf()
$ffi = FFI::cdef(
"int printf(const char *format, ...);", // 这是普遍的 C 声明
"libc.so.6");
// call C's printf()
$ffi->printf("Hello %s!\n", "world");
?>

以上示例会输出:

Hello world!

注意:一些 C 函数需要特定的调用规则,例如 __fastcall、__stdcall 或 __vectorcall。

调用函数,通过参数返回结构体:

<?php
// 创建 gettimeofday() 绑定
$ffi = FFI::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); 
", "libc.so.6");
// 创建 C 数据结构
$tv = $ffi->new("struct timeval");
$tz = $ffi->new("struct timezone");
// 调用 C 的 gettimeofday()
var_dump($ffi->gettimeofday(FFI::addr($tv), FFI::addr($tz)));
// 访问 C 数据结构的字段
var_dump($tv->tv_sec);
// 打印完整数据结构
var_dump($tz);
?>

以上示例的输出类似于:

int(0)
int(1555946835)
object(FFI\CData:struct timezone)#3 (2) {
["tz_minuteswest"]=>
int(0)
["tz_dsttime"]=>
int(0)
}

访问已存在的 C 变量:

<?php
// 创建 FFI 对象,加载 libc 和输出 errno 变量
$ffi = FFI::cdef(
"int errno;", // 这是普遍的 C 声明
"libc.so.6");
// print C's errno
var_dump($ffi->errno);
?>

以上示例会输出:

int(0)

创建和修改 C 变量:

<?php
// 创建新的 C int 变量
$x = FFI::new("int");
var_dump($x->cdata);
// 简单赋值
$x->cdata = 5;
var_dump($x->cdata);
// 复合赋值
$x->cdata += 2;
var_dump($x->cdata);
?>

以上示例会输出:

int(0)
int(5)
int(7)

使用 C 数组:

<?php
// 创建 C 数据结构
$a = FFI::new("long[1024]");
// 使用它就像使用常规数组
for ($i = 0; $i < count($a); $i++) {
$a[$i] = $i;
}
var_dump($a[25]);
$sum = 0;
foreach ($a as $n) {
$sum += $n;
}
var_dump($sum);
var_dump(count($a));
var_dump(FFI::sizeof($a));
?>

以上示例会输出:

int(25)
int(523776)
int(1024)
int(8192)

使用 C 枚举:

<?php
$a = FFI::cdef('typedef enum _zend_ffi_symbol_kind {
ZEND_FFI_SYM_TYPE,
ZEND_FFI_SYM_CONST = 2,
ZEND_FFI_SYM_VAR,
ZEND_FFI_SYM_FUNC
} zend_ffi_symbol_kind;
');
var_dump($a->ZEND_FFI_SYM_TYPE);
var_dump($a->ZEND_FFI_SYM_CONST);
var_dump($a->ZEND_FFI_SYM_VAR);
?>

以上示例会输出:

int(0)
int(2)
int(3)

二、PHP回调

可以将 PHP 闭包分配给函数指针类型的原生变量,或将其作为函数参数传递。

<?php
$zend = FFI::cdef("
typedef int (*zend_write_func_t)(const char *str, size_t str_length);
extern zend_write_func_t zend_write;
");
echo "Hello World 1!\n";
$orig_zend_write = clone $zend->zend_write;
$zend->zend_write = function($str, $len) {
global $orig_zend_write;
$orig_zend_write("{\n\t", 3);
$ret = $orig_zend_write($str, $len);
$orig_zend_write("}\n", 2);
return $ret;
};
echo "Hello World 2!\n";
$zend->zend_write = $orig_zend_write;
echo "Hello World 3!\n";
?>

以上示例会输出:

Hello World 1!
{
Hello World 2!
}
Hello World 3!

三、完整PHP/FFI/preloading

以下是完整的PHP/FFI/preloading示例:

1、php.ini

ffi.enable=preload
opcache.preload=preload.php

2、preload.php

<?php
FFI::load(__DIR__ . "/dummy.h");
opcache_compile_file(__DIR__ . "/dummy.php");
?>

3、dummy.h

#define FFI_SCOPE "DUMMY"
#define FFI_LIB "libc.so.6"
int printf(const char *format, ...);

4、dummy.php

<?php
final class Dummy {
private static $ffi = null;
function __construct() {
if (is_null(self::$ffi)) {
self::$ffi = FFI::scope("DUMMY");
}
}
function printf($format, ...$args) {
return (int)self::$ffi->printf($format, ...$args);
}
}
?>

5、test.php

<?php
$d = new Dummy();
$d->printf("Hello %s!\n", "world");
?>

四、C代码和数据主接口

通过工厂方法 FFI::cdef()、FFI::load() 或 FFI::scope() 创建该类的对象。定义的 C 变量作为有效的 FFI 实例属性,定义的 C 函数作为有效的 FFI 实例方法。声明的 C 类型可以用于 FFI::new() 和 FFI::type() 创建新的 C 数据结构。

FFI 定义解析和共享库加载可能需要较长时间。在 Web 环境中,每个 HTTP 请求都进行这些操作是没有意义的。然而,在 PHP 启动时预加载 FFI 定义和库,并在需要时实例化 FFI 对象是可能的。header 文件可以使用特殊的 FFI_SCOPE 定义进行扩展(例如 #define FFI_SCOPE "foo"),然后在预加载期间由 FFI::load() 加载。这将创建持久绑定,将通过 FFI::scope() 在所有后续请求中可用。

类摘要:

final class FFI {
/* 常量 */
public const int __BIGGEST_ALIGNMENT__;
/* 方法 */
public static addr(FFI\CData &$ptr): FFI\CData
public static alignof(FFI\CData|FFI\CType &$ptr): int
public static arrayType(FFI\CType $type, array $dimensions): FFI\CType
public static cast(FFI\CType|string $type, FFI\CData|int|float|bool|null &$ptr): ?FFI\CData
public cast(FFI\CType|string $type, FFI\CData|int|float|bool|null &$ptr): ?FFI\CData
public static cdef(string $code = "", ?string $lib = null): FFI
public static free(FFI\CData &$ptr): void
public static isNull(FFI\CData &$ptr): bool
public static load(string $filename): ?FFI
public static memcmp(string|FFI\CData &$ptr1, string|FFI\CData &$ptr2, int $size): int
public static memcpy(FFI\CData &$to, FFI\CData|string &$from, int $size): void
public static memset(FFI\CData &$ptr, int $value, int $size): void
public static new(FFI\CType|string $type, bool $owned = true, bool $persistent = false): ?FFI\CData
public new(FFI\CType|string $type, bool $owned = true, bool $persistent = false): ?FFI\CData
public static scope(string $name): FFI
public static sizeof(FFI\CData|FFI\CType &$ptr): int
public static string(FFI\CData &$ptr, ?int $size = null): string
public static type(string $type): ?FFI\CType
public type(string $type): ?FFI\CType
public static typeof(FFI\CData &$ptr): FFI\CType
}

五、C Data Handles

FFI\CData 对象可以像普通 PHP 数据一样以多种方式使用:

1、标量类型的 C 数据可以通过 $cdata 属性读取和分配,例如:$x = FFI::new('int');$x->cdata = 42。

2、C 结构和联合字段可作为常规 PHP 对象属性访问,例如:$cdata->field。

3、C 数组元素可以像普通 PHP 数组元素一样访问,例如:$cdata[$offset]。

4、可以使用 foreach 语句遍历 C 数组。

5、C 数组可用作 count() 的参数。

6、C 指针可以作为数组被取消引用,例如 $cdata[0]。

7、C 指针可以使用常规比较运算符(<, <=, ==, !=, >=, >)进行比较。

8、C 指针可以使用常规的 +/-/ ++/-- 操作进行递增和递减,例如:$cdata += 5。

9、C 指针可以通过常规的 - 运算从另一个指针减去。

10、指向函数的 C 指针可以作为常规的 PHP 闭包调用,例如:$cdata()。

11、可以使用克隆运算符复制任何 C 数据,例如:$cdata2 = clone $cdata。

12、任何 C 数据都可以使用 var_dump()、print_r() 等可视化。

六、C Type Handles

1、类摘要

final class FFI\CType {
/* 常量 */
public const int TYPE_VOID;
public const int TYPE_FLOAT;
public const int TYPE_DOUBLE;
public const int TYPE_LONGDOUBLE;
public const int TYPE_UINT8;
public const int TYPE_SINT8;
public const int TYPE_UINT16;
public const int TYPE_SINT16;
public const int TYPE_UINT32;
public const int TYPE_SINT32;
public const int TYPE_UINT64;
public const int TYPE_SINT64;
public const int TYPE_ENUM;
public const int TYPE_BOOL;
public const int TYPE_CHAR;
public const int TYPE_POINTER;
public const int TYPE_FUNC;
public const int TYPE_ARRAY;
public const int TYPE_STRUCT;
public const int ATTR_CONST;
public const int ATTR_INCOMPLETE_TAG;
public const int ATTR_VARIADIC;
public const int ATTR_INCOMPLETE_ARRAY;
public const int ATTR_VLA;
public const int ATTR_UNION;
public const int ATTR_PACKED;
public const int ATTR_MS_STRUCT;
public const int ATTR_GCC_STRUCT;
public const int ABI_DEFAULT;
public const int ABI_CDECL;
public const int ABI_FASTCALL;
public const int ABI_THISCALL;
public const int ABI_STDCALL;
public const int ABI_PASCAL;
public const int ABI_REGISTER;
public const int ABI_MS;
public const int ABI_SYSV;
public const int ABI_VECTORCALL;
/* 方法 */
public getAlignment(): int
public getArrayElementType(): FFI\CType
public getArrayLength(): int
public getAttributes(): int
public getEnumKind(): int
public getFuncABI(): int
public getFuncParameterCount(): int
public getFuncParameterType(int $index): FFI\CType
public getFuncReturnType(): FFI\CType
public getKind(): int
public getName(): string
public getPointerType(): FFI\CType
public getSize(): int
public getStructFieldNames(): array
public getStructFieldOffset(string $name): int
public getStructFieldType(string $name): FFI\CType
}

2、预定义常量

  • FFI\CType::TYPE_VOID
  • FFI\CType::TYPE_FLOAT
  • FFI\CType::TYPE_DOUBLE
  • FFI\CType::TYPE_LONGDOUBLE
  • FFI\CType::TYPE_UINT8
  • FFI\CType::TYPE_SINT8
  • FFI\CType::TYPE_UINT16
  • FFI\CType::TYPE_SINT16
  • FFI\CType::TYPE_UINT32
  • FFI\CType::TYPE_SINT32
  • FFI\CType::TYPE_UINT64
  • FFI\CType::TYPE_SINT64
  • FFI\CType::TYPE_ENUM
  • FFI\CType::TYPE_BOOL
  • FFI\CType::TYPE_CHAR
  • FFI\CType::TYPE_POINTER
  • FFI\CType::TYPE_FUNC
  • FFI\CType::TYPE_ARRAY
  • FFI\CType::TYPE_STRUCT
  • FFI\CType::ATTR_CONST
  • FFI\CType::ATTR_INCOMPLETE_TAG
  • FFI\CType::ATTR_VARIADIC
  • FFI\CType::ATTR_INCOMPLETE_ARRAY
  • FFI\CType::ATTR_VLA
  • FFI\CType::ATTR_UNION
  • FFI\CType::ATTR_PACKED
  • FFI\CType::ATTR_MS_STRUCT
  • FFI\CType::ATTR_GCC_STRUCT
  • FFI\CType::ABI_DEFAULT
  • FFI\CType::ABI_CDECL
  • FFI\CType::ABI_FASTCALL
  • FFI\CType::ABI_THISCALL
  • FFI\CType::ABI_STDCALL
  • FFI\CType::ABI_PASCAL
  • FFI\CType::ABI_REGISTER
  • FFI\CType::ABI_MS
  • FFI\CType::ABI_SYSV
  • FFI\CType::ABI_VECTORCALL

七、FFI Exceptions

类摘要:

class FFI\Exception extends Error {
/* 继承的属性 */
protected string $message = "";
private string $string = "";
protected int $code;
protected string $file = "";
protected int $line;
private array $trace = [];
private ?Throwable $previous = null;
/* 继承的方法 */
public Error::__construct(string $message = "", int $code = 0, ?Throwable $previous = null)
final public Error::getMessage(): string
final public Error::getPrevious(): ?Throwable
final public Error::getCode(): int
final public Error::getFile(): string
final public Error::getLine(): int
final public Error::getTrace(): array
final public Error::getTraceAsString(): string
public Error::__toString(): string
private Error::__clone(): void
}

八、FFI Parser Exceptions

类摘要:

final class FFI\ParserException extends FFI\Exception {
/* 继承的属性 */
protected string $message = "";
private string $string = "";
protected int $code;
protected string $file = "";
protected int $line;
private array $trace = [];
private ?Throwable $previous = null;
/* 继承的方法 */
public Error::__construct(string $message = "", int $code = 0, ?Throwable $previous = null)
final public Error::getMessage(): string
final public Error::getPrevious(): ?Throwable
final public Error::getCode(): int
final public Error::getFile(): string
final public Error::getLine(): int
final public Error::getTrace(): array
final public Error::getTraceAsString(): string
public Error::__toString(): string
private Error::__clone(): void
}
广告合作
QQ群号:707632017

温馨提示:

1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com。(#改为@)

2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。

目录