[ PHP 内核与扩展开发系列] 第一个 PHP 扩展:扩展的基本结构

每一个 PHP 扩展都至少需要两个文件:一个配置文件和一个源文件。配置文件用来告诉编译器应该编译哪几个文件,以及编译本扩展是否需要的其它库。

配置文件

才开始,我们先用最快的(不是最标准的)的方式来建立一个代码最少的扩展。在 PHP 源码文件夹的 ext 目录下创建一个新的文件夹,这里我取的名字叫做 academy,它往往就是我们扩展的名字。其实这个文件夹可以放在任何一个位置,但是为了我们在后面介绍 win32 的编译与静态编译,我们还是把它放在 PHP 源码的 ext 目录下。现在,我们在这个目录下创建一个 config.m4 文件,并输入以下内容:

PHP_ARG_ENABLE(academy,
    [Whether to enable the "academy" extension],
    [  enable-academy        Enable "academy" extension support])

if test $PHP_ACADEMY != "no"; then
    PHP_SUBST(ACADEMY_SHARED_LIBADD)
    PHP_NEW_EXTENSION(academy, academy.c, $ext_shared)
fi

上面 PHP_ARG_ENABLE 函数有三个参数,第一个参数是我们的扩展名(注意不用加引号),第二个参数是当我们运行 ./configure 脚本时显示的内容,最后一个参数则是我们在调用 ./configure --help 时显示的帮助信息。

也许有人会问,为什么有的扩展的开启方式是 --enable-extname 的形式,有的则是 --with-extname 的形式呢?其实两者并没有什么本质的不同,只不过 enable 多代表不依赖外部库便可以直接编译,而 with 大多需要依赖于第三方的库。现在,我们的扩展并不需要依赖其它的库文件,所以我们直接使用 --enable-academy 便可以了。在后续章节我们将接触通过 CFLAGSLDFLAGS 来配置自己的扩展,使其依赖第三方库文件才能被编译成 PHP 扩展。

如果我们显式运行 ./configure --enable-academy,那么终端环境便会自动将 $PHP_ACADEMY 变量设置为 yes,而 PHP_SUBST 函数只不过是 PHP 官方对 autoconf 里的 AC_SUBST 函数的一层封装。最后重要的一点是,PHP_NEW_EXTENSION 函数声明了这个扩展的名称、需要的源文件名、此扩展的编译形式。如果我们的扩展使用了多个文件,便可以将这多个文件名罗列在函数的参数里,如:

PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)

最后的 $ext_shared 参数用来声明这个扩展不是一个静态模块,而是在 PHP 运行时动态加载的。

下面,我们来编写实现扩展主逻辑的源文件 academy.c:

//加载config.h,如果配置了的话
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

//加载php头文件
#include "php.h"

#define phpext_academy_ptr &academy_module_entry

//module entry
zend_module_entry academy_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
     STANDARD_MODULE_HEADER,
#endif
    "academy", //这个地方是扩展名称,往往我们会在这个地方使用一个宏。
    NULL, /* Functions */
    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    "2.1", //这个地方是我们扩展的版本
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_ACADEMY
ZEND_GET_MODULE(academy)
#endif

这就是所有的代码了,不过鉴于我们平时的开发习惯,往往会把这一份代码分成两份,一个.h文件,一个.c文件。上面的代码只是生成了一基本的框架,而没有任何实际的用处。紧接着,创建一个zend_module_entry 结构体,你肯定已经发现了,依据 ZEND_MODULE_API_NO 是否大于等于 20010901,这个结构体需要不同的定义格式。20010901 大约代表 PHP 4.2.0 版本,所以我们现在的扩展几乎都要包含 STANDARD_MODULE_HEADER 这个元素了。其余六个成员我们可以先赋值为 NULL,其实看看它们各自后面的注释你就应该大体上了解它们各自是负责哪一方面的工作了。最后,最底下的代码用来标志我们的这个扩展是一个 shared module,它是干么的呢?它是用于在动态加载扩展名时添加 Zend 使用的引用。

标准一些

根据我们平时的开发习惯,应该不会把所有代码都写在这一个文件里的,我们需要把上述代码放在两个文件里,一个头文件,一个源文件。

//php_academy.h
#ifndef ACADEMY_H
#define ACADEMY_H

//加载config.h,如果配置了的话
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

//加载php头文件
#include "php.h"
#define phpext_academy_ptr &academy_module_entry
extern zend_module_entry academy_module_entry;

#endif

下面的是源文件

//academy.c
#include "php_academy.h"
//module entry
zend_module_entry academy_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
     STANDARD_MODULE_HEADER,
#endif
    "academy", //这个地方是扩展名称,往往我们会在这个地方使用一个宏。
    NULL, /* Functions */
    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    "2.1", //这个地方是我们扩展的版本
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_ACADEMY
ZEND_GET_MODULE(academy)
#endif

学院君 has written 716 articles

资深PHP工程师,Laravel学院院长

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>