symplely/zend-ffi

提供创建扩展或使用FFI修改Zend/PHP内部核心的基API。

安装次数: 2,331

依赖项: 1

建议者: 0

安全: 0

星星: 10

关注者: 2

分支: 3

公开问题: 0

语言:C

类型:项目

0.12.8 2023-04-14 02:00 UTC

README

zend-ffi tests

提供创建扩展或修改使用FFI的Zend/PHP内部核心的基API

  • 适用于PHP 7.4, 8.0, 8.1, 8.2, Windows, Mac, Linux

允许加载共享库(.dll.so),调用C函数和访问纯PHP中的C类型数据结构。

此包将Zend扩展API分解为获取对所有PHP内部操作的直接访问的PHP类。您实际上可以更改PHP默认行为。您将能够获得componere扩展的行为,而无需安装它。

这里许多例程是对Z-Engine库包的重写,使用了不同的术语和设置结构。此包遵循/保留PHP C源代码风格,并具有骨架FFi安装过程。

Pecl上有很多尚未更新的扩展。

安装

对于正常独立使用。

    composer require symplely/zend-ffi

设置FFI集成的骨架。

    composer create-project symplely/zend-ffi .

最低php.ini设置

extension=ffi
extension=openssl
extension=sockets

zend_extension=opcache

[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1

; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1

[ffi]
; FFI API restriction. Possible values:
; "preload" - enabled in CLI scripts and preloaded files (default)
; "false"   - always disabled
; "true"    - always enabled
ffi.enable="true"

; List of headers files to preload, wildcard patterns allowed. `ffi.preload` has no effect on Windows.
; See headers directory for `your-php-version`, this feature is untested, since not enabled for Windows.
;ffi.preload=path/to/vendor/symplely/zend-ffi/headers/ze(your-php-version).h

;This feature is untested.
;opcache.preload==path/to/vendor/symplely/zend-ffi/preload.php

对于简单的FFI集成过程创建/编辑

  • ffi_extension.json每个包/库应列出要预加载的文件,将由ffi_preloader.php脚本处理。
  • .ignore_autoload.php将在composer create-project your_package .cdef/foldername事件中调用/执行。
    • 此事件仅在您的composer create-project命令安装时调用。
  • .preload.php用于通用常用FFI函数,更改tag_changeMe骨架名称。
  • .github\workflows\*.yml这些GitHub Actions旨在用于交叉编译并将二进制返回到您的repo,更改some_libsome_repo骨架名称。
    • 这个想法是将安装完全自包含,必要的第三方库二进制文件已打包在内。
    • CI构建动作仅用于手动运行。
// Skeleton for `ffi_extension.json` file
{   // The same name to be used in `composer create-project package .cdef/foldername`
    "name": "foldername",
    // Either
    "preload": {
        "files": [
            "path/to/file.php",
            "...",
            "..."
        ],
    // Or
        "directory": [
            "path/to/directory"
        ]
    }
}

文档

应使用或扩展preload.phpFunctions.php中的函数。

有关示例,请参阅测试文件夹。将代码复制/粘贴到--FILE----EXPECT--块之间在.phpt文件中。

有关一般FFI C数据处理,请参阅CStruct

函数c_int_type()c_struct_type()c_array_type()c_typedef()是任何C数据typedef的包装器,将其转换为PHP CStruct类实例,具有所有FFI函数作为方法以及附加功能。

有关AST处理

  • zend_parse_string() 将 PHP 源代码转换为保存在 ZendAst 类中的本地 C 数据 zend_ast 节点,使用 print_ast() 来显示结果。
  • 使用 zend_ast_process(function(\ZE\AstProcess $hook){}) 来在编译过程后拦截和修改 AST。

获取像 nikic/php-astsgolemon/astkit 这样的 PHP 扩展的行为,这些扩展提供了对底层 AST 结构的低级绑定,无需任何额外库。

只需扩展 StandardModule 抽象类 就可以实现整个 PHP 生命周期过程。

declare(strict_types=1);

require 'vendor/autoload.php';

final class SimpleCountersModule extends \StandardModule
{
    protected string $module_version = '0.4';

    //Represents ZEND_DECLARE_MODULE_GLOBALS macro.
    protected string $global_type = 'unsigned int[10]';

    // Do module startup?
    protected bool $m_startup = true;
    protected bool $r_startup = true;

    // Represents PHP_MINIT_FUNCTION() macro.
    public function module_startup(int $type, int $module_number): int
    {
        echo 'module_startup' . \PHP_EOL;
        return \ZE::SUCCESS;
    }

    // Represents PHP_RINIT_FUNCTION() macro.
    public function request_startup(...$args): int
    {
        echo 'request_startup' . \PHP_EOL;
        $data = $this->get_globals();
        $data[5] = 25;
        return \ZE::SUCCESS;
    }

    // Represents PHP_GINIT_FUNCTION() macro.
    public function global_startup(\FFI\CData $memory): void
    {
        if (\PHP_ZTS) {
            \tsrmls_activate();
        }

        echo 'global_startup' . \PHP_EOL;
        \FFI::memset($this->get_globals(), 0, $this->globals_size());
    }
}

$module = new SimpleCountersModule();
if (!$module->is_registered()) {
    $module->register();
    $module->startup();
}

// Represents ZEND_MODULE_GLOBALS_ACCESSOR() macro.
$data = $module->get_globals();
$module->get_globals('4', 20);
$data[0] = 5;
$data[9] = 15;
var_dump($data);

ob_start();
phpinfo(8);
$value = ob_get_clean();

preg_match('/simple_counters support => enabled/', $value, $matches);
var_dump($matches[0]);

类似于 headers_sent() 错误的修复技巧:PHP 警告:无法修改头信息 - 已由 (输出开始于 xxxxxxxx 发送

require 'vendor/autoload.php';

function headers_sent_reset()
{
    zend_sg('headers_sent', 0);
}

echo 'any non-buffered output';
var_dump(headers_sent()); // true

headers_sent_reset();
var_dump(headers_sent()); // false

// This would have otherwise produced warning/errors!
header('Location: http://www.example.com/');

从任何 C ABI 库的 *.h 文件创建适当的 FFI C 库头文件

Linux: cpp -P -D"__attribute__(ARGS)=" path/to/original/header.h -o ffi_header.h Windows: 首先下载 mcpp mcpp -P -D"__attribute__(ARGS)=" path/to/original/header.h -o ffi_header.h

可能需要 -I <directory> 选项来搜索/查找额外的包含源文件,输出文件仍需编辑,主要是 96% 正确的头文件,FFI 会抱怨,只需编辑/检查指示行之后的 2 行。

参考/致谢

可能的安全风险

开始

贡献

鼓励并欢迎贡献;我总是很高兴在 Github 上收到反馈或拉取请求 :) 为错误和新功能创建 Github 问题 并对您感兴趣的问题进行评论。

许可

MIT 许可证 (MIT)。请参阅 许可文件 获取更多信息。