PHP 扩展开发入门

PHP 扩展的头文件

PHP 扩展至少有一个头文件,它的名字为 php_extname.h,其中 extname 为 PHP 扩展的名称。例如:在 xxtea 扩展中,这个文件就是 php_xxtea.h。

因为 xxtea 是一个小扩展,所以这个头文件本身就很小。如果我们要创建一个比较大的扩展,比如其中包含了近百个函数或多个类,那么我们应该把这些函数或类的声明分散到其它的头文件中,让这个头文件保持尽可能的小,只包含有限的一组声明。库或系统的头文件不应该被包含在其中。它一般也就包含扩展模块函数的声明(例如 PHP_MINIT_FUNCTION),模块的入口指针以及版本号的定义。至于原因嘛,简单来说就是为了加快同 PHP 一起静态编译时的速度,减少可能发生冲突的机会。

xxtea 扩展的头文件很简单,全部代码如下:

/**********************************************************\
|                                                          |
| php_xxtea.h                                              |
|                                                          |
| XXTEA for pecl include file.                             |
|                                                          |
| Encryption Algorithm Authors:                            |
|      David J. Wheeler                                    |
|      Roger M. Needham                                    |
|                                                          |
| Code Author:  Ma Bingyao <mabingyao@gmail.com>           |
| LastModified: Apr 06, 2015                               |
|                                                          |
\**********************************************************/

#ifndef PHP_XXTEA_H
#define PHP_XXTEA_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"

extern zend_module_entry xxtea_module_entry;
#define phpext_xxtea_ptr &xxtea_module_entry

#define PHP_XXTEA_MODULE_NAME   "xxtea"
#define PHP_XXTEA_BUILD_DATE    __DATE__ " " __TIME__
#define PHP_XXTEA_VERSION       "1.0.11"
#define PHP_XXTEA_AUTHOR        "Ma Bingyao"
#define PHP_XXTEA_HOMEPAGE      "https://github.com/xxtea/xxtea-pecl"

ZEND_MINIT_FUNCTION(xxtea);
ZEND_MSHUTDOWN_FUNCTION(xxtea);
ZEND_MINFO_FUNCTION(xxtea);

/* declaration of functions to be exported */
ZEND_FUNCTION(xxtea_encrypt);
ZEND_FUNCTION(xxtea_decrypt);
ZEND_FUNCTION(xxtea_info);

#endif /* ifndef PHP_XXTEA_H */

其中开头的注释是版权声明,文件信息说明等等,你喜欢写什么就写什么,什么都不写也没关系。只有一点需要注意,那就是如果你要写注释,你需要用C语言风格的注释:

/* 我是 C 语言风格的注释 */

不要用C++风格的注释:

// 我是 C++ 风格的注释

不仅仅是开头的这段注释,整个扩展中所有的注释都需要写成C语言风格的注释。

原因参见 PECL/PHP 编码标准

  1. Never use C++ style comments (i.e. // comment). Always use C-style comments instead. PHP is written in C, and is aimed at compiling under any ANSI-C compliant compiler. Even though many compilers accept C++-style comments in C code, you have to ensure that your code would compile with other compilers as well. The only exception to this rule is code that is Win32-specific, because the Win32 port is MS-Visual C++ specific, and this compiler is known to accept C++-style comments in C code.

翻译如下:

  1. 绝不要使用 C++ 风格的注释(即 // 注释)。全用 C 风格的注释代替就对了。PHP 是用 C 写的,旨在任何 ANSI-C 兼容的编译器下都可编译。尽管许多编译器在 C 代码中接受 C++ 风格的注释,但是你要确保你的代码在其它的编译器上也能正常工作。该规则的唯一例外是为 Win32 编写的特定代码,因为 Win32 的移植版本是特定于 MS-Visual C++ 编译器的,而该编译器是明确可以在 C 代码中接受 C++ 风格注释的。

接下来的

#ifndef PHP_XXTEA_H
#define PHP_XXTEA_H
...
#endif /* ifndef PHP_XXTEA_H */

这几行是 C 语言头文件的基本写法,本来不想多做解释的,但考虑到有些同学原来只是做 PHP 的,对 C 语言基础也未必掌握,这里就啰嗦两句。

这几行的目的是为了保证该 .h 文件被多次包含的时候,防止它中间的代码被重复执行。

如果你还是不明白是什么意思,你还是先去看看 C 语言基础的书吧。虽然本书是一本 PHP 扩展开发入门的书,但是如果你一点 C 语言基础都没有,后面的内容看了也白看。

如果你觉得你的 C 语言基础没有问题,那可以继续往下看:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"

上面这几行其实也可以不放在这个头文件中,放到扩展的主文件开头是一样的1。不过那样的话,它后面的几行所使用的 zend_module_entry 这些类型,ZEND_MINIT_FUNCTION 这些宏,在这个头文件中就找不到定义了,在某些编辑器中打开可能会有错误提示。所以我把它放在了这个头文件中。反正这几行代码对静态编译不会有什么影响。

extern zend_module_entry xxtea_module_entry;
#define phpext_xxtea_ptr &xxtea_module_entry

这两行是重点,这两行声明的是模块的入口,照着写就行了。如果你用 ext_skel,它也会帮你生成这两行。

接下来这几行:

#define PHP_XXTEA_MODULE_NAME   "xxtea"
#define PHP_XXTEA_BUILD_DATE    __DATE__ " " __TIME__
#define PHP_XXTEA_VERSION       "1.0.11"
#define PHP_XXTEA_AUTHOR        "Ma Bingyao"
#define PHP_XXTEA_HOMEPAGE      "https://github.com/xxtea/xxtea-pecl"

只有

#define PHP_XXTEA_VERSION       "1.0.11"

是重点,其它的可有可无。

把跟扩展有关的常量宏都这样定义的好处是,后面的主文件写起来会比较清楚,也比较好改。所以建议这样做。

这里还有一点要注意,这些常量宏的格式必须是 PHP_EXTNAME_XXX 这样的风格。尤其是 PHP_EXTNAME_VERSION

pecl 网站提供了一种版本检查的机制,如果你上传的扩展代码中的实际版本号和 package.xml 写的版本号不一致(比如你忘了改 php_extname.h 中的版本号,或者忘了改 package.xml 中的版本号),pecl 网站都会检查出来,给你一次改正的机会。

但如果你没有采用 PHP_EXTNAME_VERSION 这样风格的宏来定义版本号,那 pecl 网站的在你上传扩展时就检查不出来了。

我曾经就犯过这个错误,导致 pecl 网站上的 xxtea 扩展有几个版本的版本号实际上是不对的。但我也没机会再去修正那些错误的版本号了。这都是血和泪的教训啊,切记,切记!

ZEND_MINIT_FUNCTION(xxtea);
ZEND_MSHUTDOWN_FUNCTION(xxtea);
ZEND_MINFO_FUNCTION(xxtea);

这几行是关于模块的几个函数的声明。它们是可有可无的。你只要保证在后面的主文件中,引用这些函数名符号时,它们已经被声明,或已经被定义就行了。

/* declaration of functions to be exported */
ZEND_FUNCTION(xxtea_encrypt);
ZEND_FUNCTION(xxtea_decrypt);
ZEND_FUNCTION(xxtea_info);

这几行是关于导出到 PHP 中的函数的声明。同样,它们是可有可无的。原则跟上面说的一样。

你可以把它们从这里移动到扩展的主文件开头,或者在主文件中把它们的定义移到它们被引用之前。

简单来说,可以用一句话总结:

你程序中引用的每个符号,要么提前声明,要么提前定义,否则在编译或连接的时候,会出现找不到符号的错误。

而关于本节内容的总结呢,也是一句话:

在 php_extname.h 中,只有模块入口声明和模块版本号定义是必须的,其它的都是可有可无的。但只要不影响扩展正常工作,就让它保持尽可能的小。


1 ext_skel 生成的头文件中就不包含这几行,而是放在了扩展的主文件中。