YDWE官方博客

坑爹的php

周末研究了一下从 c++调用 php,看起来是一件很简单的事却折腾了我两天,总之是被坑惨了。虽然 php 是用 c 写的,不过看起来开发组只管自己用得溜就算了,完全没有考虑一般使用者的感受。

以下是我在 msvc2010 下编译 php5.4.27 的记录。

坑之 ZTS

php 默认启用 ZTS(php 线程安全),但 ZTS 宏却不是默认开启.如果你编译 php 时没有关掉 ZTS,那么使用 php api 时会得到以下错误

1
2
error LNK2001: 无法解析的外部符号 __imp__compiler_globals
error LNK2001: 无法解析的外部符号 __imp__executor_globals

你需要自己定义 ZTS

1
#define ZTS

坑之 PHP_WIN32、ZEND_WIN32

完全想不出不通过_ WIN32 宏来生成 PHP_WIN32、ZEND_WIN32 宏的理由,总之你是需要自己来定义了。还有为什么会有 PHP_WIN32 和 ZEND_WIN32 两个宏呢。

1
2
#define ZEND_WIN32
#define PHP_WIN32

另外,虽然 php 不会理会 WIN32 宏,但却会理会 WIN64 宏。

坑之 ZEND_WIN32_FORCE_INLINE

如果说之前的坑只是懒,那这个坑只能说是逗了。让我们来看看 ZEND_WIN32_FORCE_INLINE 都干了些什么。

1
2
3
4
5
6
#undef inline
#ifdef ZEND_WIN32_FORCE_INLINE
# define inline __forceinline
#else
# define inline
#endif

注意,默认是没有定义 ZEND_WIN32_FORCE_INLINE 的,所以当你引用了 php 的头文件,你代码中 inline 就会全部失效。c++标准库的头文件中也使用了 inline,所以就算你没用 inline,一样会让你的代码无法编译。错误通常为

1
2
error C2491: “std::flush”: 不允许 dllimport 函数 的定义
error C2491: “std::ws”: 不允许 dllimport 函数 的定义

所以 ZEND_WIN32_FORCE_INLINE 宏必须被定义,或者删掉这段略逗的代码。

坑之_ USE_32BIT_TIME_T

如果你没定义_ WIN64 宏,那么 php 会帮你定义

1
2
3
#ifndef _WIN64
# define _USE_32BIT_TIME_T 1
#endif

但 msvc 默认是不定义这个宏的,所以当你在未引用 php 头文件时,使用 c++的头文件,使用的是 64 位 time_t,引用 php 头文件后就变 32 位 time_t 了。错误通常为

1
stat.inl(42): error C2466: 不能分配常量大小为 0 的数组

或者

1
time.inl(36): error C2664: “_ctime32”: 不能将参数 1 从“const time_t *”转换为“const __time32_t *”

解决方法,删掉这段代码。

坑之 ZEND_DEBUG 和 UNICODE

_zend_executor_globals 是 php 中一个很重要的结构体(也就是 EG(xxx)),这个结构体中包含了一个 OSVERSIONINFOEX 成员,而 OSVERSIONINFOEX 的长度会根据 UNICODE 宏的定义与否有所不同,php 默认的编译选项是没有定义 UNICODE 宏的,如果你的工程定义了 UNICODE 宏,那么你就会获得很多迷のbug。ZEND_DEBUG 也会有同样的问题,如果你编译时有—enable-debug,就必须定义 ZEND_DEBUG,反之就不能定义。

php 完全可以增加编译时的检查,以保证不会有类似的 abi 错误,但它没有这么做。

坑之导出的全局变量

你可能无法想象,php 不但很随意地使用了全局变量,并且还很随意地把它直接导出了。这种莫名其妙的设定遍地都是,不过我今天要说的是编译的坑。

表面上看,你在 c++里直接使用 php 的头文件并没有什么问题,因为 php 在每个导出函数前都加了 extern “C”,以保证 c++编译器能找到 php 导出的函数。但这个并没有包括它导出的全局变量,所以当你在 c++中使用它导出的全局变量时,就会得到一个链接错误。所以你必须给每个你要引用的 php 头文件加上 extern “C”,就像这样

1
2
3
extern "C" {
#include <zend.h>
}

显然它原来自己加的 extern “C”就没用了,倒不如你就像 lua 那样宣称我就是要用 c,你要用 c++就自己加 extern “C”得了,做事做一半就不管了,算是个什么意思。

坑之 debug 模式

当你编译为 debug 模式的 php,php 会为你做很多额外的检查,这本来是一件好事,但它最通常的做法就是直接崩掉,没有任何有用的提示;关掉 debug 模式的话,是 ok 的(至少表面看起来 ok)。对于这种暴力的提示错误方式,我真不知道该如何招架,或许就 php 开发组的人玩得溜了。




最后吐槽下 php 的代码到处都充斥着丑陋的 TSRM 宏,无论是否开启 ZTS,tsrm_ls 都应该作为一个外部参数传入,php 不应该也没必要在内部保留自己的状态,无论是否开启 ZTS。

Comments