当前位置: 首页> 最新文章列表> 在 FFI::cdef 中忘记定义 struct 会发生什么?

在 FFI::cdef 中忘记定义 struct 会发生什么?

gitbox 2025-05-28

PHP 的 FFI(Foreign Function Interface)扩展让我们可以直接在 PHP 中调用 C 语言的函数和数据结构,在处理性能敏感或者底层交互的场景非常有用。但是一旦你深入使用它,就会发现一些隐藏的“地雷”,比如——在 FFI::cdef() 里声明结构体时,如果你忘了写 struct 会发生什么?

先看一个最基本的例子

假设我们有一个简单的 C 结构体:

typedef struct {
    int x;
    int y;
} Point;

我们用 PHP 的 FFI::cdef() 声明:

$ffi = FFI::cdef("
    typedef struct {
        int x;
        int y;
    } Point;
");

然后可以这样使用:

$point = $ffi->new("Point");
$point->x = 10;
$point->y = 20;
echo "Point: ({$point->x}, {$point->y})\n";

一切运行正常。但现在问题来了:如果你忘了写 struct,会发生什么?

忘写 struct 的陷阱

C 语言中,struct PointPoint 是两个不同的名字空间。除非你用了 typedef,否则 Point 并不是一个有效的类型名。而在 PHP 的 FFI::cdef() 中,情况略有不同——它的解析规则和 C 编译器略有差异,容错性低。

比如你写了这样一段声明:

$ffi = FFI::cdef("
    typedef struct Point {
        int x;
        int y;
    } Point;
");

$pt = $ffi->new("Point");

这段代码是没问题的。但如果你粗心把 typedef 部分写漏了,比如这样:

$ffi = FFI::cdef("
    struct Point {
        int x;
        int y;
    };
");

$pt = $ffi->new("Point"); // 出错

这里 $ffi->new("Point") 会抛出异常,提示找不到类型“Point”。因为此时“Point”是 struct Point,而不是一个单独的类型别名。

正确的调用应该是:

$pt = $ffi->new("struct Point");

这就是很多人掉进去的坑:以为定义了结构体名字“Point”,其实你只定义了“struct Point”,没做 typedef 的话不能直接用 Point。

如何排查这个问题?

如果你看到类似下面的错误:

FFI\Exception: undefined C type 'Point'

那你首先要检查你在 cdef() 中到底是如何声明的。是不是只写了 struct Point 而没有 typedef?如果是这样,就只能用 struct Point 来引用。

一个调试技巧是,把你的 cdef() 声明打印出来检查结构体有没有 typedef,例如:

echo $ffi->cdef;

虽然这个方法不总是适用(尤其在某些版本的 PHP 中),但能帮助你确认类型是否正确注册。

建议做法:一律 typedef

为了避免混乱,建议你在所有结构体声明中都带上 typedef。例如:

typedef struct {
    int x;
    int y;
} Point;

这样你在 PHP 中就能愉快地写 $ffi->new("Point"),不用再纠结是否该加 struct 前缀。

举个更复杂的例子:结构体嵌套

假设你定义了一个结构体里嵌套另一个结构体:

typedef struct {
    int x;
    int y;
} Point;

typedef struct {
    Point start;
    Point end;
} Line;

PHP 中的 FFI::cdef() 应该这样写:

$ffi = FFI::cdef("
    typedef struct {
        int x;
        int y;
    } Point;

    typedef struct {
        Point start;
        Point end;
    } Line;
");

然后你可以像这样使用:

$line = $ffi->new("Line");
$line->start->x = 1;
$line->start->y = 2;
$line->end->x = 10;
$line->end->y = 20;

echo "Start: ({$line->start->x}, {$line->start->y})\n";
echo "End: ({$line->end->x}, {$line->end->y})\n";

如果你在声明中漏了 typedef,比如 Point 没 typedef,那么 Line 的定义就会报错,因为它找不到 Point 类型。

小结

在使用 PHP FFI 调用 C 的结构体时,以下几点务必注意:

  1. 结构体未 typedef 的话,类型名必须加 struct 前缀

  2. 推荐所有结构体都使用 typedef 声明,避免命名混淆

  3. 调试时遇到 undefined type 错误,先查 cdef 中的声明是否一致

通过这些规范使用方式,可以减少大量莫名其妙的类型错误,让你在用 PHP 玩 C 接口的时候少踩坑、少掉发。

更多关于 PHP FFI 的示例可以参考 https://gitbox.net/docs/php-ffi-examples