Home >Backend Development >PHP Tutorial >Writing PHP Extension 1: Introduction to PHP and Zend

Writing PHP Extension 1: Introduction to PHP and Zend

WBOY
WBOYOriginal
2016-07-30 13:29:341067browse
Extension Writing Part I: Introduction to PHP and Zend

Translation: I lost it in the world

Original address:http://devzone.zend.com/303/extension-writing-part-i -introduction-to-php-and-zend/

Blog address: http://lg.uuhonghe.com/index/view?id=3

Introduction

What is extension

Life cycle

Hello World建 Create its own extension

Ini configuration


global variables

Set ini for global variables

Integral check

and then miles?


Introduction

If you are reading this tutorial, you may be interested in writing extensions in the PHP language. if not. . . Maybe after reading it you will find that you are interested in something you didn't know before.


This article assumes that readers have a basic understanding of the PHP language and the PHP interpreter written in C.


Let’s first confirm why you want to write a PHP extension:


1. Due to the depth of abstraction of the language core, you have some libraries and system calls that cannot be completed directly using PHP.


2. You want PHP to implement its own behavior in some unusual ways.


3. You’ve written a bunch of PHP code, but you know it can run faster.


4. You have code that implements a particularly clever idea that you want to sell, but more importantly, the code you want to sell must be able to run but cannot be seen in the source code.


These are all very valid reasons, but to create an extension, you first have to understand what an extension is.


What is extension?

If you have ever written PHP, then you must have used extensions. With just a few extensions, all user-space functionality in PHP is in the function group of this or that extension. A large number of these functions are part of the standard extensions - more than 400 in total. There are 86 extensions bundled in the PHP source code, each with an average of about 30 functions. By counting, there are about 2500 functions in total. If that's not enough, there are over 100 additional extensions available in the PECL repository

, and more can be found elsewhere online.

“These functions are all in extensions, so what else?” You may ask, “What do they extend? What is the core of PHP?”.


The core of PHP consists of two parts. At the bottom you can find the Zend Engine (ZE for short). ZE parses human-recognizable scripts into machine-recognized symbols and runs these symbols in the process space. ZE handles memory management, variable fields and function calls simultaneously. Another part of this distinction is the PHP core. The PHP kernel handles communications, connections, and the SAPI layer (Server Application Programming Interface, often also used to refer to host environments such as Apache, IIS, CLI, CGI etc.), and also provides unified detection of safe_mode and open_basedir in the control layer, as well as a streaming layer related to the user space functions fopen(), fread() and fwrite() for file and network I/O.

Life Cycle

When a SAPI is started, such as in the response of /usr/local/apache/bin/apachectl start

, PHP starts by initializing its kernel subsystem. At the end of this boot process, each extension's kernel is loaded and their module initialization routine (

MINIT) is called. This gives each extension a chance to initialize internal variables, allocate resources, register resource handles, and register its functions with ZE so that when the script calls these functions, ZE knows which piece of code to run.


Next, PHP waits for the SAPI layer to request a page to process. In the case of CGI or CLI SAPI, this happens directly and only once. In Apache, IIS or some other mature web server SAPI, this happens when the remote user requests it, and may happen multiple times, possibly with concurrency. Regardless of how the request arrives, PHP starts by telling ZE to set up an environment for the script to run, and then calls each extension's request initialization (
RINI

) function.

RINIGives extensions a chance to set up their own specific environment variables, allocate requests for specific resources, or perform other tasks such as auditing. RINIA prime example of functional behavior is in sessions In the extension, if the session.auto_start item is enabled, RINI will automatically trigger the user space session_start() function and preset the $_SESSION variable. Once the request is initialized, ZE takes over by translating the PHP script into tokens and finally into opcodes, which can be debugged and executed in a single step.

When an extension method contained in one of the opcodes is called, ZE will bundle the parameters of the method and temporarily hand over control to the direct method completion.

After the script is completed, PHP calls each extended request shutdown (RSHUTDOWN) function to do the final cleanup work (such as ensuring that session variables are saved to disk). Next, ZE runs a cleanup process (known as garbage collection), which effectively performs the unset() operation on every variable used in the previous request.


Operation complete Finally, PHP waits for SAPI to request another document or for the signal to close. In the case of CGI and CLI SAPI, there is no "next request", so the SAPI starts the shutdown process directly. During the shutdown process, PHP goes through each extension again, calls the module shutdown (MSHUTDOWN) function, and finally shuts down its own kernel subsystem.

The above may sound intimidating, but as you start to develop a working extension, some of it will gradually become clear.

Memory Management

To prevent poorly written extended memory loss, ZE implements its internal memory manager with an additional flag indicating persistence. Continuous allocation is important to ensure that memory allocations last longer than a page request. In contrast, a non-persistent allocation is released at the end of the request for which it was allocated, regardless of whether the release function is called. For example, user space variables are allocated non-persistently when they are no longer used after the request ends.

However, perhaps an extension would theoretically rely on ZE to automatically release non-persistent memory at the end of the page request, which is not recommended. Memory allocation will give a longer period of unrecycling, memory-related resources are unlikely to be closed at the right time, and no cleanup work will make this work a mess. As you will see later, it is a simple matter to ensure that the allocated data is cleaned up in time. Let's briefly compare traditional memory allocation (must be used when using external libraries) and persistence in PHP/ZE. and unpersistent memory allocation.

Traditional Non-Persistent Persistent
malloc(count)
calloc(count, num)
emalloc(count)
ecalloc(count, num)
pemalloc(count, 1)*
pecalloc(count, num, 1)
strdup(str)
strndup(str, len)
estrdup(str)
estrndup(str, len)
pestrdup(str, 1)
pemalloc() & memcpy()
free(ptr) efree(ptr) pefree(ptr, 1)
realloc(ptr, newsize) erealloc(ptr, newsize) perealloc(ptr, newsize, 1)
malloc(count * num + extr)** safe_emalloc(count, num, extr) safe_pemalloc(count, num, extr)
 

 * pemalloc() The family contains a 'persistent' flag to make them correspond to their non-persistent parts. For example:

safe_emalloc(1234) and safe_pemalloc(1234, 0) Same.

** safe_emalloc() and safe_pemalloc() (in PHP5) adds a check to avoid integer overflow.

Building a Development Environment

Now that we have learned some of the theory behind how PHP and Zend Engine work, I guess you want to get started developing. Then before you start, you must gather some necessary work to set up a development environment that meets your needs.

First, you need PHP itself, because this series of development tools are inseparable from PHP. If you are not familiar with using source code to build PHP, I recommend you read this article first: http://www.php.net/install.unix. (The article on developing PHP extensions using Windows will be given later) . Although it is very tempting to use the binary packages included in your Linux distribution to be safe, but these will miss two ./configure options that are very convenient during the development process. The first one is --enable-debug. This option causes PHP to add executable symbol information when compiling, so that when a segfault occurs, you can get it from kernel storage and use gdb to track where and why the segfault occurred. Another option depends on the version of PHP you are developing with. This option is named --enable-experimental-zts in PHP 4.3 and --enable-maintainer-zts in PHP 5 or higher. This precedence allows PHP to think about its operations in a multi-threaded environment, allowing you to catch some common exceptions that are harmless in a non-threaded environment and cause your extension to be unusable in a multi-threaded environment. As long as you compiled PHP with these extra options installed on your development server (or workstation), you can start writing your first extension. W w Hello World

Any program introduction that has not completed the Hello World application is incomplete. Therefore, below we will make an extension of only one function that returns the string "Hello World". In PHP code you may write like this:

<?php
function hello_word(){
    return 'Hello World';
}
?>
Now we use PHP extension to implement this code. First, we create the directory

hello

in the ext/ directory of the PHP source code and enter the directory. In fact, this directory can be in or out of the PHP directory tree, but I let you put it here so that I will demonstrate an unrelated concept later. Three files need to be created in this directory: a source file that contains the
hello_world

method, a header file that contains references to let PHP load your extension, and a header file that lets phpize do the work for compiling the extension. Prepare the configuration file. 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);
}
It can be seen that most of the code in the above example extension is glue-protocol language. used to introduce extensions to PHP and establish a conversation for them to communicate. Only the last 4 lines of code can be called "real code" and are used to perform tasks that user space layer scripts can interact with. Indeed, the code at this level looks very similar to the PHP code we looked at before and is easy to understand:

        1、声明一个方法  hello_world

        2、让这个方法返回一个字符串:"Hello World"

        3、。。呃。。。1?这个1几个意思?

    回忆一下,ZE包含了一套复杂的内存管理层,能确保分配的资源在脚本退出时被释放。然而在内存管理的掌控下,释放同样的块两次是非常大的禁忌。这种行为通常称为"double freeing",是一个常见的段错误的原因,涉及到一个正在调用的程序访问一个不再属于它的内存块。同样的,你不希望允许ZE去释放一个存活于程序空间并且其数据块被其他进程占用的静态字符串buffer(例如我们示例中的"Hello World")。  RETURN_STRING()能够假设任何一个传递给它的字符串都被复制过以便可以安全的释放;但是因为在内核函数里分配内存给一个字符串不太常见,所以动态的填充它,然后返回,  RETURN_STRING()允许用户指定是不是必须复制字符串。为了进一步说明这个概念,下面这个代码片段作用和上面相应部分一样:

PHP_FUNCTION(hello_world)
{
    char *str;
    str = estrup("hello World");
    RETURN_STRING(str, 0);
}

    在此版本中,我们手动地分配内存给"Hello World"这个串,并且最终传回调用脚本,然后把内存传给 RETURN_STRING()第二个参数值为0表示不需要复制一份,可以直接使用传递过来的。

    编译你的扩展

    本练习的最后一步就是将你的扩展编译为一个动态可加载模块。如果你把上面的示例原封不动的抄下来,那么只需要在ext/hello/目录下运行下面三步命令就行:

phpize
./configure --enable-hello

(译者注:如果编译PHP的时候使用了 --prefix 参数,此处要加上 --with-php-config 选项,
如笔者编译PHP时使用的是 ./configure --prefix=/use/local/phpdev/ 此处命令应使用
./configure --enable-hello --with-php-c/local/phpdev/bin/php-config)

make

    运行完上述三个命令之后,你应该在ext/hello/modules/下找到一个 hello.so 文件。(译者注:如果在make时报错: error: unknown type name 'function_entry' ,可以把 'function_entry' 改为 'zend_function_entry',参见:https://bugs.php.net/bug.php?id=61479  )。现在,就像其他PHP扩展一样,你可以把你的扩展拷贝到扩展目录(默认为,/usr/local/lib/php/extensions/,可以通过php.ini确认)然后在php.ini里加上extension=hello.so一行可以在以触发它在程序启动时被加载到了。对于CGI/CLI SAPIs 来说,启动就指下一次运行PHP;而对我web server SAPIs如Apache来说,启动指下次web server重启。让我们试下运行下面命令:

$ php -r 'echo hello_world();'

    如果一切顺利,你现在应该能看到这段代码输出"Hello World"了,因为你的扩展里的"hello_world()"返回了一个字符串"Hello World",而echo命令会原封不动地显示传递给他的参数(此处即为该函数的返回值)。

    其他标量也可用类似的方式返回,使用RETURN_LONG() 返回整型, RETURN_DOUBLE() 返回浮点数, RETURN_BOOL() 返回true/false值,RETURN_NULL() 你猜到了,返回 NULL 值。让我们来逐行分析一下在hello.cfunction_entry 结构体下通过 PHP_FE() 添加的行和文件末尾的那些PHP_FUNCTION()都做了些什么。

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}
};

PHP_FUNCTION(hello_long)
{
    RETURN_LONG(42);
}

PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}

PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}

PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

    你同样需要在头文件php_hello.h hello_world() 的旁边添加这些方法的原型,拿得创建过程能正确执行:

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是安全的。然后,在本游戏的这个阶段,我还是要求你从头把三个步骤都执行一遍以确认活干得漂亮。另外,最后一步的时候,你应该执行make clean all而不是简单的执行make,来确保所有源文件重建。再次声明,现在的改动上述这些步骤是非必须的,但是会更安全更清晰。模块建好后,再拷贝到你的扩展目录下,替换旧版本。

    这个时候你可以再次调用你的PHP解析器,运行一段简单的脚本来试试你刚刚添加的方法。事实上,为何不现在不试呢?我等着呢。。。

    试完了?很好。如果你使用 var_dump() 而不是 echo 来查看每个函数的返回值你也许会发现"hello_bool()"返回true。这就是值1在"RETURN_BOOL()"所表示的。就像在PHP脚本里,整型值0 等于 FALSE, 但是其他所有的整形值都等于 TRUE扩展作者经常使用"1"作这一系列的约定,我们希望你也这样,但是也不必拘泥于此。为了更具可读性, RETURN_TRUE 和  RETURN_FALSE 两个宏也可使用;现在再次修改"hello_bool()",这次使用"RETURN_TRUE":

PHP_FUNCTION(hello_bool){
    RETURN_TRUE;
}

    注意这里没有使用括号哦。 RETURN_TRUERETURN_FALSE 与其他宏RETURN_*() 格式的变体,所以这里注意别被捕获。

    你也许注意到上面这些代码样品我们都没有传0和1什来表示这些值是否需要被拷贝。这是因为没有额外的内存(变量容器之外——我们将在第2部分深入)需要被分配或释放,因为这些标量都很简单很小。

    还有另外三种返回类型: RESOURCE (例如"mysql_connect()","fsockopen()"和"ftp_connect()"的返回值), ARRAY (也称为HASH),还有 OBJECT (通过关键词new返回)。我们将会在第二章更深入变量学习的时候了解这一系列。

INI Settings

    Zend 引擎提供了两个方式处理 INI 变量

    我们先看一下较简单的一种,而更全面、更复杂的方式,等以后你有机会接触全局变量再说。

    现在我们想在php.ini中定义一个变量,"hello.greeting", 用来处理你在"hello_function()"函数里用来打招呼的变量。你需要在hello.cphp_hello.h中添另一些东西,并且hello_module_entry 结构也得有所变化。以加入下面这些原型到php_hello.h的用户空间方法原型旁边:

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello); 

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

    现在到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变量栈都关心的问题,这是一个静态变量。实际上,如果你试图去修改这个方法返回的值,PHP运行环境会变得不稳定甚至可能崩溃。

    首次修改的部分包含了两个你需要熟悉的方法:MINIT MSHUTDOWN。正如上面提到的,这两个方法分别是在SAPI层初始启动和最后关闭时调用的。他们不在请求之间也不在请求之中调用。在这个示例中,你用他们注册在你扩展中定义的php.ini选项。在本文的后面,你将学习如何使用"MINIT"和"MSHUTDOWN"方法来注册资源、对象和流句柄。

    在你的方法hellow_world()中使用"INI_STR()"来取回当前"hello.greeting"项的值作为一个字符串。那些其他的已有方法取值作为长整型,浮点型和布尔型,如下表所示,"ORIG"补充了其他方法,能提供从 INI设置中引用的值(在被.htaccess或者 ini_set() 修改之前的值)。

    

Current Value Original Value Type
INI_STR(name) INI_ORIG_STR(name) char * (NULL terminated)
INI_INT(name) INI_ORIG_INT(name) signed long
INI_FLT(name) INI_ORIG_FLT(name) signed double
INI_BOOL(name) INI_ORIG_BOOL(name) zend_bool

The first parameter passed into PHP_INI_ENTRY() is a string containing the name of the option in php.ini. To avoid namespace collisions, you must use the same convention as your method; that is, prefix all values ​​with the name of your extension, such as "hello.greeting" just now. This is a common convention. At one time, the extension was name and ini settings for the more descriptive part of the name and separate.

The second parameter is the initial value, and is usually taken as a char* string whether it is a number or not. This is mainly because the values ​​in the .ini file actually default to text - it is a text file. You can use INI_INT(), INI_FLT(), or INI_BOOL() to perform type conversion in your script.

The third parameter passed in is an access mode modifier. This is a bitmask used to determine when and where INI can be modified. For some, such as register_globals, is simply not allowed to be modified using ini_set() in the script, because this configuration can only have a chance to run before requesting to start the script. In addition, such as allow_url_fopen, are some management items that you do not want to allow users to modify in a shared hosting environment, whether it is ini_set() or the .htaccess command. Another typical value of this parameter should be PHP_INI_ALL, which means that this value can be modified anywhere. There is also PHP_INI_SYSTEM|PHP_INI_PERDIR,, which means that this configuration can be set in the php.ini file, and can also be modified in the .htaccess file through Apache instructions, but modification through ini_set() is not allowed. Furthermore, there is PHP_INI_SYSTEM, which means that this value can only be modified in the php.ini file, not elsewhere.

We will skip the fourth parameter and just click on this value to pass in a callback method to be called whenever the ini configuration is modified, such as when using ini_set() When modifying. This allows the extension to more precisely control settings being modified, or to trigger corresponding behavior by modifying a new configuration.

Global variables

    通常,扩展需要在特定的请求里跟踪变量的值,使之独立于并发请求。在无线程的SAPI中这可能比较简单:只需要在源文件中声明一个全局变量,在需要时调用。然而麻烦在于PHP被设计运行于线程级的web服务器(如Apache 2 和 IIS),这就需要保证一个线程中的全局变量与其他线程中的分离。PHP通过使用TSRM(Thread Safe Resource Management)抽象层,大大地简化了这一操作,有时被称为ZTS(Zend Thread Safety)。实际 上,到现在你已经使用了一部分TSRM了,虽然你对其不甚了解。(不要急着去搜索,通过这一系列的进展,你会发现它无处不在。)

    创建线程安全全局变量的第一步,跟创建其他全局变量一样,声明它。为了实现这个例子,你将声明一个以 long 型开始,值为 0 的全局变量。每次hello_long() 方法被调用时,我们增值该变量变返回。下面这段php_hello.h中的代码放在#define PHP_HELLO_H 模块后面:

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

ZEND_BEGIN_MODULE_GLOBALS(hello)
    login counter;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRM(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中添加下面这段代码到include 模块后:

#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
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

    修改你的 MINIT 方法,添加另一对方法,来处理初始化启动请求:

static void
php_hello_init_globals(zend_hello_globals *hello_globals)
{
}

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;
}

    最后,修改你的 hello_long() 方法来使用这个值:

PHP_FUNCTION(hello_long)
{
    HELLO_G(counter)++;
    
    RETURN_LONG(HELLO_G(counter));
}

    在添加到php_hello.h的代码里,使用了一对宏ZEND_BEGIN_MODULE_GLOBALS() 和 ZEND_END_MODULE_GLOBALS() 创建了一个包含一个 long变量的结构体 zend_hello_globals 。然后根据条件是否处于一个无线程环境定义了是从线程池取值还是仅从全局范围取值的宏 HELLO_G() 。在hello.c中,我们使用 ZEND_DECLARE_MODULE_GLOBALS() 来创建了一个zend_hello_globals结构的实例,作为一个全局变量(如果在非线程安全中创建)或者一个线程资源池的一员。作为扩展作者,这个区别我们是不用但心的,Zend 引擎会帮我们处理好的。最后,在 MINI ,使用 ZEND_INIT_MODULE_GLOBALS() 分配了一个线程安全资源id——现在还不用担心这个是啥。你也许注意到 php_hello_init_globals()并没有作什么,但是我们声明 RINIT 时初始化counter为 0,为何?关键在于这两个函数何时被调用。php_hello_init_globals() 仅在一个新进程或者线程启动时调用;然而一个进程可以处理多个请求,所以使用这个方法初始化我们的counter为 0 只会在第一个页面请求时调用。接下来的请求到同一个进程的页面会一直使用这个已存储的值,因为不会从 0 开始计数。为了在每个页面请求中初始化counter为 0 ,我们实现了 RINIT 方法,如前所述的在页面请求之前实现。我们在这里包含php_hello_init_globals() 方法是因为一会就要使用到它,传一个 NULL 给ZEND_INIT_MODULE_GLOBALS() 来初始化函数会在非线程平台导致一个段错误 。

    INI设置 vs 全局变量

    如果你回观前文,一个在 PHP_INI_ENTRY() 里声明的php.ini是作为字符串转换成其他需要的类型,通过 INI_INT(), INI_FLT()和 INI_BOOL()。对某些配置,这代表在脚本执行的过程中会做大量重复的工作一遍又一遍的读这个值。幸运的是,可以指定ZE使用特殊的数据类型存储 INI 值,只在其值改变时做类型转换。让我们通过声明另一个 INI 值来试一下这个功能,这次使用布尔型标明计数器应该增还是减。首先修改php_hello.h下的MODULE_GLOBALS 模块如下:

ZEND_BEGIN_MODULE_GLOBAL(hello)
    login counter;
    zend_bool direction;
ZEND_ENG_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)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }    
    
    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 从程序获取参数,接着学习返回更复杂的结果的方法,包括本章提到的 arrayobject 

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn