You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP通过FFI调用Cython编译的WordNet函数时PyObject未定义问题解决

问题描述

我需要在PHP应用中使用Python NLTK包的wordnet工具,但出于性能考虑无法调用Python进程,也无法将代码移植到PHP且无替代库。因此尝试用Cython将wordnet封装为共享C库,再通过PHP的FFI接口调用。代码编译正常,但调用时报错:

PHP Fatal error: Uncaught FFI\ParserException: Undefined C type "PyObject" at line 2 in /build/test.php:5
Stack trace:
#0 /build/test.php(5): FFI::cdef()
#1 {main}
thrown in /build/test.php on line 5

相关代码及编译步骤如下:

  1. 封装脚本wordnet.pyx:
from nltk.corpus import wordnet

def synonyms(word: str) -> list[str]:
    return wordnet.synonyms(word)
  1. setup.py文件及wordnet.h头文件:
from distutils.core import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize(
        'wordnet.pyx',
        compiler_directives={'language_level': '3'}
    )
)
#include <Python.h>
PyObject* wordnet(PyObject* self, PyObject* args);
  1. PHP测试脚本:
<?php
$ffi = FFI::cdef(
     file_get_contents("/build/wordnet.h"),
     "/build/wordnet_wrapper.so"
);

$result = $ffi->wordnet("dog");

编译步骤:

python setup.py build_ext --inplace
gcc -Wall \
    -pthread \
    -fwrapv \
    -O2 \
    -fPIC \
    -fno-strict-aliasing \
    -shared -o wordnet_wrapper.so \
    wordnet.c \
    -I /usr/local/include/python3.11 \
    -L /usr/local/lib/python3.11 \
    -L /usr/local/lib \
    -lpython3.11
解决方法

核心问题是PHP的FFI不识别Python的PyObject类型,且直接暴露Python对象给PHP无法直接处理。需要从接口设计上做调整,让封装的库提供纯C兼容的调用接口:

1. 修改Cython封装脚本

重新编写wordnet.pyx,导出接收C字符串、返回JSON格式C字符串的函数,同时增加内存释放函数避免泄漏:

from nltk.corpus import wordnet
import json
from cpython.mem cimport PyMem_Malloc, PyMem_Free

cdef public char* get_synonyms(const char* word_c):
    # 将C字符串转Python字符串
    word = word_c.decode('utf-8')
    # 获取同义词列表
    syns = wordnet.synonyms(word)
    # 转成JSON字符串方便PHP解析
    json_str = json.dumps(syns).encode('utf-8')
    # 分配内存存储结果(含终止符)
    cdef char* result = <char*>PyMem_Malloc(len(json_str) + 1)
    if result is NULL:
        return NULL
    result[:len(json_str)] = json_str
    result[len(json_str)] = b'\x00'
    return result

# 提供释放内存的接口
cdef public void free_result(char* ptr):
    if ptr is not NULL:
        PyMem_Free(ptr)

2. 更新头文件wordnet.h

去掉Python依赖,只保留纯C类型的函数声明:

#ifndef WORDNET_WRAPPER_H
#define WORDNET_WRAPPER_H

char* get_synonyms(const char* word);
void free_result(char* ptr);

#endif

3. 重新编译共享库

保持原编译流程不变,确保链接正确的Python版本库:

python setup.py build_ext --inplace
gcc -Wall \
    -pthread \
    -fwrapv \
    -O2 \
    -fPIC \
    -fno-strict-aliasing \
    -shared -o wordnet_wrapper.so \
    wordnet.c \
    -I /usr/local/include/python3.11 \
    -L /usr/local/lib/python3.11 \
    -L /usr/local/lib \
    -lpython3.11 \
    -lm

4. 修改PHP测试脚本

调用纯C接口,将返回的JSON字符串解析为PHP数组,并记得释放内存:

<?php
$ffi = FFI::cdef(
    file_get_contents("/build/wordnet.h"),
    "/build/wordnet_wrapper.so"
);

// 调用获取同义词JSON字符串
$json_ptr = $ffi->get_synonyms("dog");
if ($json_ptr === null) {
    die("调用失败");
}

// 转成PHP字符串并解析为数组
$result = json_decode(FFI::string($json_ptr), true);

// 释放内存避免泄漏
$ffi->free_result($json_ptr);

print_r($result);
?>

额外注意事项

  • 确保Python环境已安装NLTK并下载wordnet语料库(执行nltk.download('wordnet'))
  • 编译时使用的Python版本必须和运行时一致,避免版本兼容问题
  • 生产环境需增加输入校验、异常捕获等错误处理逻辑

内容的提问来源于stack exchange,提问作者Moritz Mazetti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 23:02:56