编写PHP扩展三步曲之一

简介
如果你正在阅读这份指南,你大概已经有兴趣为php编写扩展了。如果没有兴趣...,唔,或许当你看完本文,你会发现这是一件你未曾尝试过的非常有趣的事情。
这份指南假设你熟悉PHP语言和php解释器的编写语言:C。
让我们来说说为什么要编写PHP扩展:
1、由于抽象程度的问题,一些库或系统调用不能被PHP直接调用。
2、在一些不常见的情况,你需要控制PHP的行为。
3、你已经写好一些PHP代码,但你想让它运行得更快,体积更小,占有更少的内存。
4、你打算销售一些独特的、聪明的代码,非常重要的是:它能够执行,但不能查看源代码。
这些都是很常见的原因,但是为了创建扩展,你首先需要理解什么是扩展。

什么是扩展
如果你使用过PHP,那你就已经使用过扩展。除了很少例外,PHP里的每个用户空间(userspace)函数,都来自一个或多个扩展。这些函数中的很大部分属于“标准扩展”--共有超过400个这样的函数。PHP源码包中有86个扩展,每个扩展平均有大约30个函数。照这样计算,大约有2500个函数。如果你觉得这些还不够,在PECL库中,还提供了超过100个其他扩展,而且在互联网上还能找到更多。
“所有的函数都来自扩展,那还剩下什么?”。我听见你这样问。“扩展到底是什么?PHP的核心又是什么呢?”
PHP的核心由两大部分组成。在低层次是the Zend引擎(ZE)。ZE的任务是:将人们可读的脚本转化成机器可读的符号,并在进程空间中执行这些符号。ZE还处理内存管理、变量作用域、分发函数调用。另外一大部分是PHP。PHP处理通讯,和跟SAPI层(服务器端应用编程接口,通常指以下主机环境--Apache,IIS,CLI,CGI等)的绑定。它也为safe_mode和open_basedir检查提供一个统一的控制层,比如,通过fopen、fread、fwrite进行文件和网络操作的字节流层。

生命周期
当SAPI启动时,例如响应操作 /usr/local/apache/bin/apachectl start,PHP开始初始化它的核心子系统。在启动过程的后期,它装载每个扩展,并调用扩展的模块初始化函数(MINIT)。这就给了每个扩展初始化内部变量、分配资源、注册资源处理器、向ZE注册函数的机会,这样,如果脚本调用这些函数,ZE就知道怎么去执行。
接下来,PHP等待SAPI层的页面处理请求。在用作CGI或CLI时,这会立即发生并只执行一次。当运行在Apache、IIS或其他完整的web服务器SAPI下时,它在用户请求页面时触发,重复执行,并有可能并发执行。不用关心请求是怎样进来的,PHP向ZE请求设置一个脚本运行的环境,然后调用每个扩展的请求初始化函数(RINIT)。RINIT给扩展一个设置特定环境变量、分配特定资源、执行其他任务例如审核的机会。一个RINIT的简单例子是sessions扩展,如果session.auto_start选项被打开,RINIT会自动触发用户空间函数session_start(),构造$_SESSION变量。
一旦请求被初始化了,ZE将PHP脚本转换成符号,进而转换成可执行的操作码去执行。一旦操作码调用一个扩展函数,ZE就将参数打包传递给扩展函数,临时交出控制权,直至执行完成。
当脚本执行完毕,PHP调用每个扩展的请求结束函数(RSHUTDOWN),以执行一些清除任务(例如将session变量保存到磁盘上)。接下来,ZE运行一个清除进程(垃圾收集),高效地对前一个请求中用到的每个变量执行unset()操作。
一旦完成,PHP等待SAPI请求另一个文档或发送一个shutdown信号。在CGI和CLI的SAPI中,没有下一次请求,因此SAPI立即执行shutdown。在shutdown时,PHP会调用扩展的模块Shutdown函数(MSHUTDOWN),最终关闭核心子系统。
整个过程在刚开始时听起来让人退却,但是一旦你深入一个正常运行的扩展中,这些过程就会变得显而易见。

内存分配
在编写扩展时,为了避免内存泄漏,ZE通过一个表明持久的附加标志提供了自身内部的内存管理。“持久分配”的内存可以在单个页面请求结束后继续存在。与此相对的“非持久分配”,内存被分配后,在请求结束时,无论是否调用释放函数都将被释放。例如,用户空间的变量,都应该使用非持久分配,因为在请求结束后,它们就没有意义了。
在理论上,扩展可以依靠ZE在每个页面请求结束时自动释放非持久分配的内存,但这样做是不推荐的。内存分配将保持未回收状态比较长一段时间,内存关联的资源很少能正确释放,弄得一团糟却不清除它,这是不好的习惯。你逐渐会发现,实际上很容易保证所有分配得数据都被正确释放。
让我们简要比较一下传统的内存分配函数和PHP/ZE的持久、非持久内存分配:
传统
malloc(count) calloc(count, num)
strdup(str) strndup(str, len)
free(ptr)
realloc(ptr, newsize)
malloc(count *num + extr)
非持久
emalloc(count) ecalloc(count, num)
estrdup(str) estrndup(str, len)
efree(ptr)
erealloc(ptr, newsize)
safe_emalloc(count, num, extr)
持久
pemalloc(count, 1) pecalloc(count, num, 1)
pestrdup(str, 1) pemalloc() & memcpy()
pefree(ptr, 1)
perealloc(ptr, newsize, 1)
safe_pemalloc(count, num, extr)

配置编辑环境
现在你已经接触到了一些隐藏在PHP和Zend引擎下的理论,我打赌你已经想要开始构建它们了。然而,在你能构建之前,你需要收集一些必要的工具,并配置环境。
首先,你需要PHP,设置构建工具需要PHP。如果你对build PHP源代码还不熟悉,你建议你看看http://www.php.net/install.unix。(为Windows开发PHP扩展将在后面的文档中讲述)。跟使用PHP的二进制分发包相比,源代码版本有两点非常重要。/configure选项在开发过程中非常顺手。首先是--enable-debug。这个选项将使用附加的符号信息编译PHP,这样,如果一个错误发生,你能收集到core dump,这样就可以使用gdb来进行跟踪分析错误为什么会发生。另一个选项根据版本的不同有不同的名称。在PHP 4.3中,名为--enabled-experimental-zts,在PHP5和后续版本中,叫做--enable-maintainer-zts。这个选项可以让PHP知道它运行在多线程的环境中,这样你能捕获到在单线程的环境中没有问题的编程错误。这样你的扩展就能在多线程的环境中安全使用。一旦你用这些特定的选项编译完PHP并安装好它,你就能开始你的第一个扩展了。

Hello World
没有必不可少的“Hello World”应用,哪一个入门教程是完整的?在这个实例中,你将编写一个扩展,导出一个返回“Hello World”字符串的函数。在PHP代码中,你可能会这样写:
<?php
function hello_world(){
 return 'Hello World';
}
?>
现在你将它转换成一个扩展。首先,让我们在PHP源代码目录树下的ext目录中创建一个名为hello的目录,并进入这个目录。事实上,这个目录也可以在PHP目录树外存在,但是我希望你把它放在这里,在后面的文档中会介绍一些不相关的概念。你需要创建3个文件:一个源文件,包含hello_world函数;一个头文件,包含PHP装载这个扩展的引用;一个配置文件,它会被phpize用来准备扩展的编译环境。
config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])

if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

php_hello.h
#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1

#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"

PHP_FUNCTION(hello_world);

extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry

#endif

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

#include "php.h"
#include "php_hello.h"

static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif

PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}

你在上面的扩展示例中看到的大多数代码都是胶水代码-将扩展声明给PHP,并建立他们之间的通信环境。只有最后4行代码很象之前我们写的PHP代码:
1、声明函数名为hello_world
2、函数将字符串“Hello World”作为返回值
3、...嗯?关于1,那到底是什么?
回忆一下,ZE包含了一个内存管理层,以保证分配的资源在脚本退出的时候被释放。在内存管理中,一个大问题是两次释放同一个内存块。这种行为叫做double freeing,就象访问不再属于自己的内存,这是一个引起错误的常见原因。同样的,你不想ZE释放一个静态的字符串缓冲(例如我们扩展示例里的Hello World),因为它存在在代码空间,而并不是属于任务进程的数据块。RETURN_STRING()能假定任何字符串都需要拷贝传递,这样就能被安全释放;但是因为在函数内部为字符串分配内存,动态填充,然后返回它,这并不罕见,RETURN_STRING()允许我们指定是否有必要生成一个字符串的拷贝。为了演示这种概念,下面的代码片断等同于在刚才你看到的代码:
PHP_FUNCTINO(hello_world){
 char *str;
 str = estrdup("Hello World");
 RETURN_STRING(str, 0);
}
在这个版本中,你为字符串“Hello World”手动分配了内存,最终会被传回调用脚本,然后把内存交给RETURN_STRING。使用第二个参数值0表明它不需要创建自己的拷贝。

构建你的扩展
练习的最后一步将把扩展构建成动态加载的模块。如果你正确拷贝了上面的代码,只需要在ext/hello/目录下执行3个命令:
phpize
./configure --enable-hello
make
在运行了这些命令后,ext/hello/modules/目录下会生成hello.so文件。现在你就可以象其他扩展一样,把它拷贝到你的扩展目录(缺省为/usr/local/lib/php/extensions,检查php.ini进行确认),并在php.ini中增加一行extension=hello.so,以在PHP启动的时候载入它。对于CGI或CLI的SAPI,这意味着下次运行PHP的时候会生效;对于Web服务器SAPI,例如Apache,在下次Web服务器重启的时候生效。现在让我们通过命令行来试试:
php -r 'echo hello_world();'
如果所有的步骤都正确,你应该能看到脚本输出的Hello World了,因为扩展中的hello_world函数返回了这个字符串,echo命令就将它显示出来。
返回其它类型的值也类似这种形式,使用RETURN_LONG()返回整型,RETURN_DOUBLE()返回浮点型,RETURN_BOOL()返回布尔型,RETURN_NULL()返回空值。让我们来看看hello.c中在function_entry结构加入的PHP_PE行,以及在文件尾部加入的PHP_FUNCTION()行。
static function_entry hello_functions[] = {
 PHP_PE(hello_world, NULL)
 PHP_PE(hello_long, NULL)
 PHP_PE(hello_double, NULL)
 PHP_PE(hello_bool, NULL)
 PHP_PE(hello_null, NULL)
 {NULL, NULL, NULL}
};
PHP_FUNCTION(hello_long)

PHP_FUNCTION(hello_double)

PHP_FUNCTION(hell_bool)

PHP_FUNCTION(hello_null)
你也需要在头文件php_hello.h加入函数的原型声明,这样才能正确编译:
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
因为你不需要修改config.m4文件,因此在技术上跳过phpize和./configure过程,直接make是安全的。然而,为了得到正确的build,我建议你还是执行整个三个步骤。另外,你应该调用make clean all而不是简单的make,来保证所有的源文件都被重新build。一旦模块被构建出来,你要再次把它拷贝到你的扩展目录,替换掉老版本。
到这一步,你可以再次调用PHP解释器,用简单的脚本测试你新增的函数。实际上,你为什么不现在就这么做呢?我等着你...
完成了?非常好。如果你曾用var_dump代替echo查看每个函数的输出,你可能已经注意到hello_bool()返回true。那就是传递给RETURN_BOOL()的值1得到的。就像在PHP脚本中,整数0等同FALSE,其它整数等同TRUE。扩展的编写者按照惯例通常使用1来返回TRUE,我也建议你这么做,只要不拘泥与此就行了。为了获得更好的可读性,可以使用RETURN_TRUE和RETURN_FALSE宏。我们使用RETURN_TRUE重新实现hello_bool():
PHP_FUNCTION(hello_bool)

注意,使用RETURN_TRUE和RETURN_FALSE的时候,没有圆括号,跟其它的RETURN_*()宏不一样。因此,注意不要犯这样的错误!
你可能已经注意到,上面的每个代码我们都没有传递0或1来指出值是否需要拷贝。这是因为对于这些简单的标量来说,没有附加的内存需要分配和释放。
还有3个附加的返回类型:RESOURCE(用来返回mysql_connect(), fsockopen(), ftp_connect()),ARRAY(返回HASH),OBJECT(通过关键字new来返回)。当我们深入了解这些变量时,我们将在第二章中进行讨论。

INI设置
Zend引擎提供了两种管理INI值的方式。现在我们先看看简单一些的方式,完整阐释它;对于更复杂一些的方式,在我们有机会获取全局变量时再讨论它。
假定你想为你的扩展在php.ini中定义一个值hello.greeting,这样在你的hello_world()函数中可以获取到这个值用来输出。你需要在hello.c和php_hello.h中对hello_module_entry结构做出一些改动。在php_hello.h靠近函数原型声明的地方加入新的原型:
PHP_MINIT_FUNCTION(hell);
PHP_MSHUTDONW_FUNCTION(hello);

PHP_FUNCTION(hello_world);
...
现在,用下面的代码替换hello.c中的hello_module_entry
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
 STANDARD_MODULE_HEADER,
#endif
 PHP_HELLO_WORLD_EXTNAME,
 hello_functions,
 PHP_MINIT(hello),
 PHP_MSHUTDOWN(hello),
 NULL,
 NULL,
 NULL,
#if ZEND_MODULE_API_NO >= 20010901
 PHP_HELLO_WORLD_VERSION,
#endif
 STANDARD_MODULE_PROPERTIES
};

PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()

PHP_MINIT_FUNCTION(hello)
{
 REGISTER_INI_ENTRIES();
 return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
 UNREGISTER_INI_ENTRIES();
 return SUCCESS;
}
现在,你需要在hello.c的include块后加入一个include项,以获取INI文件支持:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
最后,需要修改hello_world函数,以使用INI值:
PHP_FUNCTION(hello_world)
{
 RETURN_STRING(INI_STR("hello.greeting"), 1);
}
注意,你拷贝了INI_STR()的返回值。这是因为它一个静态变量。事实上,如果你尝试修改这个返回值,PHP扩展环境将变得不稳定,甚至崩溃。
在本节里,第一个变化是引入了两个方法MINIT和MSHUTDOWN,你将对他们非常熟悉。正如以前提及的,它们会由SAPI在启动初始化和关闭时分别调用。它们不会在请求间和请求中被调用。在这个例子中,你用它来注册在扩展中定义的php.ini入口(entries),在后续的讨论中,你将看到如何使用MINIT和MSHUTDOWN函数注册resource,object和stream handlers。
在你的hello_world()函数中,你使用INI_STR()获取hello.greeting的当前字符串值。获取long、double、boolean型值的函数,连同获取默认值的函数,如下表所示。
当前值:INI_STR(name) INI_INT(name) INI_FLT(name) INI_BOOL(name)
默认值:INI_ORIG_STR(name) INI_ORIG_INT(name) INI_ORIG_FLT(name) INI_ORIG_BOOL(name)
类  型:char *(NULL terminated)  signed long  signed double  zend_bool
传递给PHP_INI_ENTRY()的第一个参数是在php.ini中定义的entry的名称字符串。为了避免命名冲突,你应使用与函数相同的习惯:对所有值加上以扩展名命名的前缀,例如hello.greeting。作为一个习惯,使用句点将后面的ini设置名称的描述部分分隔开。
第二个参数是初始值,无论它是不是一个数字型值,都传递一个char *的字符串。这样做主要的原因是ini文件本身是文本化的。你可以使用INI_INT(),INI_FLT(),INI_BOOL()在自己的脚本里进行类型转换。
第三个参数是访问模式修饰符。这是一个掩码字段,用来决定这个INI值何时、何处可以被修改。例如register_globals,并不能简单的在脚本中使用ini_set()来进行修改,因为这些设置只在请求startup时--脚本运行之前被使用。其它的设置,例如allow_url_fopen,在共享的主机环境中,即使是通过ini_set()或是通过.htaccess,你也并不想让用户随意改变它。这个参数的典型值是PHP_INI_ALL,表明它的值可以在任何地方修改。PHP_INI_SYSTEM|PHP_INI_PERDIR表示可以在php.ini文件中修改,也可以通过.htaccess文件修改,但不能通过ini_set()修改。PHP_INI_SYSTEM表示只能在php.ini中进行修改。
第四个参数允许在ini设置改变时(例如ini_set()),触发一个回调函数,我们现在略过不讲。当设置发生变化时,这允许扩展执行更精确的控制,或者根据新值触发相关动作。

全局数值
扩展经常需要跟踪一个值,保持该值与其它的请求无关,即使是这些请求是发生在同一时刻。在非多线程的SAPI中可能很简单:只需要声明一个全局变量,在需要是访问它就行了。可能存在的问题是,PHP被设计成可以运行在多线程的Web服务器中(如Apache2和IIS),这需要保持全局数值的线程安全。PHP采用TSRM(Thread Safe Resource Management)抽象层进行了极大地简化,有时也被称为ZTS(Zend Thread Safety)。实际上,你已经使用过TSRM,只是不知道而已。(不要艰难地试图找出它,很快你就会发现,它隐藏在每个地方。)
创建一个线程安全地全局变量的第一步是,用global声明一个变量。在下面的例子中,声明了一个long型的全局变量。每次调用hello_long()函数地时候,将它加1。在php_hello.h的#define PHP_HELLO_H语句后加入下面的代码:
#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
 long counter;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif
这次该用到RINIT方法了,因此你需要在头部声明它的原型:
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);
让我们到hello.c中增加如下的代码:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

ZEND_DECLARE_MODULE_GLOBALS(hello)
修改hello_module_entry,加入PHP_RINIT(hello);
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
 STANDARD_MODULE_HEADER,
#endif
 PHP_HELLO_WORLD_EXTNAME,
 hello_functions,
 PHP_MINIT(hello),
 PHP_MSHUTDOWN(hello),
 PHP_RINIT(hello),
 NULL,
 NULL,
#if ZEND_MODULE_API_NO >= 20010901
 STANDARD_MODULE_PROPERTIES
#endif
};
同时修改MINIT函数,以及另一对函数,在请求startup时处理初始化:
static void php_hello_init_globals(zend_hello_globals *hello_globals)

PHP_RINIT_FUNCTION(hello)
{
 HELLO_G(counter) = 0;
 return SUCCESS;
}
PHP_MINIT_FUNCTIOIN(hello)
{
 ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
 REGISTER_INI_ENTRIES();
 return SUCCESS;
}
最后,你可以这样修改hello_long()函数:
PHP_FUNCTION(hello_long)

在php_hello.h中,你使用一对宏ZEND_BEGIN_MODULE_GLOBALS()和ZEND_END_MODULE_GLOBALS()来创建名为zend_hello_globals的结构,它包含一个类型为long的变量。然后,你有条件的定义了HELLO_G()从线程池或从全局作用域来获取变量的值--如果你在不支持线程的环境编译。
在hello.c中,你使用ZEND_DECLARE_MODULE_GLOBALS()宏来实例化zend_hello_globals结构,使它成为一个真正的全局变量(如果是非线程安全构建)或线程资源池的一员。作为一个扩展编写者,这种区别是我们不需要关心的,因为Zend引擎会帮我们处理好。最后,在MINIT,你使用ZEND_INIT_MODULE_GLOBALS()分配线程安全的资源id--现在并不需要担心它是什么。
你可能已经注意到php_hello_init_globals()没有做什么实际的事情,然而我们却在RINIT时将counter初始化为0,这是为什么呢?
关键在于这两个函数被调用的时机。php_hello_init_globals()只在新的进程或线程启动时被调用;然而,每个进程可以为不只一个请求服务,因此在这个函数里将counter初始化为0将只会在第一个页面请求时工作。同一个进程的后续页面请求仍将使用旧的counter,因此counter并不是从0开始。为了将每个页面请求的counter初始化为0,我们实RINIT函数,就像你在前面学到的一样,在每个页面请求时它都会被调用。我们包含php_hello_init_globals()函数的目的是你即将会用到它,也是由于我们为init函数传递NULL给ZEND_INIT_MODULE_GLOBALS()在非线程平台将会导致一个错误。

INI设置作为全局值
如果你回忆一下前面所说的,一个php.ini的值被称为PHP_INI_ENTRY(),是一个字符串值,在需要时使用INI_INT(),INI_FLT(),INI_BOOL()转换成其它的格式。对于一些设置,在脚本的执行过程中,存在大量的毫无必要的复制过程。幸运的是,我们可以通知ZE把这个INI值存储为一个特殊的数据类型,只在它的值被使用时才执行转换。让我们声明另一个INI变量,这次是一个Boolean,指明counter是需要增加还是减少。让我们从修改php_hello.h的MODULE_GLOBALS块开始:
ZEND_BEGIN_MODULE_GLOBALS(hello)
 long counter;
 zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)
接下来,修改PHP_INI_BEGIN块来声明INI变量的值:
PHP_INI_BEGIN()
 PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
 STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END
现在在init_globals方法初始化设置:
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
 hello_globals->direction = 1;
}
最后,在hello_long()中使用它的值,决定是增加还是减少:
PHP_FUNCTION(hello_long)
else
 RETURN_LONG(HELLO_G(counter));
}
都搞定了。在INI_ENTRY里指定的OnUpdateBool方法会将在php.ini,.htaccess或在脚本里通过ini_set()设置的任何值自动转换成你在脚本里可以直接访问的TRUE或FALSE。STD_PHP_INI_ENTRY的后三个参数分别告诉PHP要转换哪个全局变量,扩展的全局结构是什么样的,被保存到的全局容器名。

完整的参考代码
现在,我们的三个文件类似下面的样子了(一些小的移动和分组是为了得到更好的可读性)
config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])

if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

php_hello.h
#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1

#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
    zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif

#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry

#endif

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

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

ZEND_DECLARE_MODULE_GLOBALS(hello)

static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    PHP_FE(hello_long, NULL)
    PHP_FE(hello_double, NULL)
    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    PHP_RINIT(hello),
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif

PHP_INI_BEGIN()
    PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
    STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
    hello_globals->direction = 1;
}

PHP_RINIT_FUNCTION(hello)
{
    HELLO_G(counter) = 0;

    return SUCCESS;
}

PHP_MINIT_FUNCTION(hello)
{
    ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);

    REGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(hello)
{
    UNREGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}

PHP_FUNCTION(hello_long)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }

    RETURN_LONG(HELLO_G(counter));
}

PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}

PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}

PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

下一步?
在本指南中,我们阐释了一个简单的PHP扩展的结构:导出函数,返回值,声明INI设置,在请求的处理过程中记录内部状态。
在下一章中,我们将阐释PHP变量的内部结构,他们是怎么被存储,被追踪,在脚本环境中被操纵的。我们将使用zend_parse_parameters来接收调用参数。也将提及返回复杂结果的办法,包括数组,对象和资源的办法 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值