PHP 扩展的头文件

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

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

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

  1. /**********************************************************\
  2. | |
  3. | php_xxtea.h |
  4. | |
  5. | XXTEA for pecl include file. |
  6. | |
  7. | Encryption Algorithm Authors: |
  8. | David J. Wheeler |
  9. | Roger M. Needham |
  10. | |
  11. | Code Author: Ma Bingyao <mabingyao@gmail.com> |
  12. | LastModified: Apr 06, 2015 |
  13. | |
  14. \**********************************************************/
  15. #ifndef PHP_XXTEA_H
  16. #define PHP_XXTEA_H
  17. #ifdef HAVE_CONFIG_H
  18. #include "config.h"
  19. #endif
  20. #include "php.h"
  21. extern zend_module_entry xxtea_module_entry;
  22. #define phpext_xxtea_ptr &xxtea_module_entry
  23. #define PHP_XXTEA_MODULE_NAME "xxtea"
  24. #define PHP_XXTEA_BUILD_DATE __DATE__ " " __TIME__
  25. #define PHP_XXTEA_VERSION "1.0.11"
  26. #define PHP_XXTEA_AUTHOR "Ma Bingyao"
  27. #define PHP_XXTEA_HOMEPAGE "https://github.com/xxtea/xxtea-pecl"
  28. ZEND_MINIT_FUNCTION(xxtea);
  29. ZEND_MSHUTDOWN_FUNCTION(xxtea);
  30. ZEND_MINFO_FUNCTION(xxtea);
  31. /* declaration of functions to be exported */
  32. ZEND_FUNCTION(xxtea_encrypt);
  33. ZEND_FUNCTION(xxtea_decrypt);
  34. ZEND_FUNCTION(xxtea_info);
  35. #endif /* ifndef PHP_XXTEA_H */

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

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

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

  1. // 我是 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++ 风格注释的。

接下来的

  1. #ifndef PHP_XXTEA_H
  2. #define PHP_XXTEA_H
  3. ...
  4. #endif /* ifndef PHP_XXTEA_H */

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

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

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

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

  1. #ifdef HAVE_CONFIG_H
  2. #include "config.h"
  3. #endif
  4. #include "php.h"

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

  1. extern zend_module_entry xxtea_module_entry;
  2. #define phpext_xxtea_ptr &xxtea_module_entry

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

接下来这几行:

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

只有

  1. #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 扩展有几个版本的版本号实际上是不对的。但我也没机会再去修正那些错误的版本号了。这都是血和泪的教训啊,切记,切记!

  1. ZEND_MINIT_FUNCTION(xxtea);
  2. ZEND_MSHUTDOWN_FUNCTION(xxtea);
  3. ZEND_MINFO_FUNCTION(xxtea);

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

  1. /* declaration of functions to be exported */
  2. ZEND_FUNCTION(xxtea_encrypt);
  3. ZEND_FUNCTION(xxtea_decrypt);
  4. ZEND_FUNCTION(xxtea_info);

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

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

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

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

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

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


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