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 编码标准
- 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.
翻译如下:
- 绝不要使用 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 生成的头文件中就不包含这几行,而是放在了扩展的主文件中。