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
相关代码及编译步骤如下:
- 封装脚本
wordnet.pyx:
from nltk.corpus import wordnet def synonyms(word: str) -> list[str]: return wordnet.synonyms(word)
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);
- 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
相关产品推荐
相关产品推荐

