首頁  >  文章  >  後端開發  >  編寫PHP擴充一:PHP與Zend介紹

編寫PHP擴充一:PHP與Zend介紹

WBOY
WBOY原創
2016-07-30 13:29:341039瀏覽
Extension Writing Part I: Introduction to PHP and Zend

譯:我在江湖丟了

原文地址:http://devzone.zend

原文地址:http://devzone.zend.com/303/ext。 -introduction-to-php-and-zend/

部落格網址:http://lg.uuhonghe.com/index/view?id=3

簡介

何為擴充

生命週期

World

建立自己的擴充

ini 設定

全域變數

ini 為全域變數

簡介


    如果你正在閱讀本教程,那你可能對PHP語言的擴展編寫頗感興趣。如果不是。 。 。也許你讀完之後會發現你對這個之前不知道的東西產生了興趣。

    本文假設讀者基本上了解PHP語言和C寫的PHP解釋者。


    讓我們先確認你為何想寫一個PHP擴充:


    1、由於語言內核的抽象深度使你有一些函式庫和深度完成。


    2、你希望PHP以一些不尋常的方法實現自身行為。


    3、你已經寫了一堆PHP代碼,但你知道它可以跑得更快。


    4、你有個實現了特別機智點子的代碼想賣,但更重要的是你要賣的代碼要能跑但不能在源碼裡看到。


    這些都是非常正當的理由了,但要創建一個擴展,首先你得理解什麼是擴展。


何為擴充?


    如果你寫過PHP,那你一定用過擴充了。只需要小小的幾個擴展,一切PHP裡的用戶空間功能都在這個或那個擴展的函數組裡了。大量這些函數都在標準擴充的一部分——標準擴充總共的400多個。 PHP原始碼裡捆綁了86個擴展,平均每個大概有30個函數。掐指一算,總約2500個函數。如果這還不夠,
PECL倉庫

裡還提供了100個以上的附加擴展,更多的可以再網上別的地方找到。

    「這些函數都在擴充裡,那還有什麼?」你會問,「它們擴充了什麼?PHP的核心是什麼?」。

    PHP的內核由兩個部分組成。在最底層你能找到Zend引擎(簡稱ZE)。 ZE把人能辨識的腳本解析為機器辨識的符號,並且在行程空間裡運行這些符號。 ZE同時處理記憶體管理,變數域和函數呼叫。這種區分方式的另一部門是PHP核心。 PHP核心處理通訊、連線和SAPI層(Server Application Programming Interface, 通常也用來指主機環境,如Apache, IIS, CLI, CGI 等等),也提供了控制層對 safe_mode 和 open_basedir 的統一偵測,還有和用於檔案和網路I/O的使用者空間函數fopen(), fread()和fwrite()相關的串流層。


生命週期

    當一個SAPI啟動,例如在 /usr/local/apache/bin/apachectl start 

/usr/local/apache/bin/apachectl start 

的回應中,PHPPH在這個啟動全程將結束之際,會載入每個擴充的核心並呼叫他們的模組初始化例程(MINIT)。

    這給每個擴充一個機會去初始化內部變量,分配資源,註冊資源句柄,並且使用ZE註冊它的函數,以便當腳本調這些函數時,ZE知道要運行哪段程式碼。     接下來,PHP等待SAPI層來請一個頁面處理。在CGI或CLI SAPI的情況下,這會直接發生並且只會發生一次。在Apache, IIS 或其他一些成熟的 web 伺服器SAPI中,這是在遠端使用者請求時發生,並且或發生多次,可能伴隨著並發。無論請求如何到達,PHP以通知ZE建立一個供腳本運行的環境為開始,然後調用每個擴展的請求初始化(

RINI

)函數。 RINI
給擴展一個機會去建立自己特定的環境變量,分配請求特定的資源,或執行其他任務如審計。
RINI

函數行為的一個主要例子是在sessions 擴充裡,如果

session.auto_start項是開啟的, RINI將會自動觸發使用者空間session_start()函數並且預先設定$_SESSION變數。     一旦請求被初始化了,ZE透過翻譯PHP腳本為tokens,最後轉為opcodes來接管,opcodes能夠單步調試和執行。

    當其中一個opcodes包含的一個擴展方法被調用,ZE會捆綁上該方法的參數,並且臨時性地交出控制直接方法完成。

    在腳本運行完成之後,PHP調用每個擴展的請求關閉(RSHUTDOWN)函數來做最後的清理工作(例如確保會話變數到磁碟)。接著,ZE運行一個清理程序(稱為垃圾回收),該程序能有效在請求前期中使用的每個變量上執行unset()操作.

    

   後,PHP等待SAPI請求另一個文件或訊號關閉。在CGI和CLI SAPI情況下,是沒有「下一個請求」的,因此SAPI直接啟動關閉程序。在關閉進程中,PHP再次遍歷每個擴展,呼叫模組關閉(MSHUTDOWN)函數,最後關閉自己的核心子系統。

    以上或許聽來使人生畏,然後當你開始鑽石一個工作中的擴展時,一些將逐漸清晰。

記憶體管理

    為了防止寫得很爛的擴展內存丟失,ZE用一個表明持續性的附加標識來執行它內部的內存管理器。持續性分配是很重要的,能保證記憶體分配持續到比一個頁面請求還長。相對而言,一個不持續性分配在它分配的請求結束時被釋放,無論釋放函數是否被呼叫。例如,使用者空間變數會在請求結束之後不再使用時候時分配為不持續性的。

    然而也許一個擴展理論上會依賴ZE在頁面請求結束時自動釋放不持續性內存,這是不推薦的。記憶體分配會給未回收的一個更長的周期,與記憶體有關的資源則不太可能會被在恰當的時候關閉,沒有清理工作會讓這次工作亂作一團。稍後你會看到,確保分配的資料適時清理其時是一件很簡單的事情,下面我們簡單的對比一下傳統記憶體分配(必須在使用外部類別庫時使用)和PHP/ZE中的持續性與未持續性記憶體分配。

strdup(str)free(ptr) 1)erealloc(ptr, newsize)perealloc(ptr, newsize, 1)safe_pemalloc(計數, 數字,額外) 
傳統 非持久 持久
o持久() num)
emalloc(count)
ecalloc(count, num)
pemalloc(計數, 1)
*pecalloc(計數, num, 1)
strndup(str, len)
estrdup(str)
estrdup(str, len)
pestrdup(str, 1)
pemalloc() & memcpy()
efree(ptr) realloc(ptr, newsize)
malloc(計數 * num + extr)**
safe_emalloc(count, num,extr)

    * pemalloc() 家族包含一個 'persistent' 標誌來讓他們與他們的未持續性部分相對應。例如:

         safe_emalloc(1234)  0) 一樣。     **  

safe_emalloc() 和   PHP5中) 加了個檢查避免整數溢位。 建立一個開發環境

    現在我們學習了PHP和Zend引擎工作方式背後的一些理論,我猜你想運足功力開始開發了。然後在你開始之前,必須得收集一些必備的工作來建立一個滿足你需求的開發環境。

    首先,你需要PHP本身,因為這一系列開發工具都離不開PHP。如果你還對使用源碼搭建PHP不熟悉,推薦你看下這篇文章先:http://www.php.net/install.unix
. (使用Windows開發PHP擴充的文章稍後給出)

雖然用你的linux發行版裡的二進位套件安全是非常誘人的,但是這些會遺漏兩項在開發過程中十分方便的./configure選項。第一個就是

 --enable-debug 這個選項令PHP在編譯時加入可執行的符號訊息,以便當發生一個段錯誤時,你能從內核儲存中得到它並且使用gdb追蹤段錯誤發生的地方和原因。另一個選項取決於你開發使用的PHP的版本。在PHP4.3中這選項名稱為--enable-experimental-zts,在PHP5或更高的版本中為--enable-maintainer-zts。 該先項讓PHP在多執行緒環境中思考自己的操作,讓你捕捉一些常見在非執行緒環境下是無害而導致您的擴充在多執行緒環境下用不了的異常。只要你在編譯PHP時使用了這些額外的選項安裝在你的開發伺服器(或工作站)上,你可以開始寫你的第一個擴充功能了。         Hello World

    因此,下面將製作一個只有一個返回字串"Hello World"的函數的擴充。在PHP程式碼裡你可能這樣寫:

<?php
function hello_word(){
    return 'Hello World';
}
?>
    現在我們使用PHP擴充來實作這段程式碼,首先我們在PHP原始碼的ext/目錄下建立目錄

hello並且進到該目錄下。事實上該目錄在不在PHP目錄樹下都可以,但是我讓你把它放在這裡以便後面將展示一個不相關的概念。目錄下需要建立三個文件:一個包含
 

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

看得出來,上述範例擴充裡的大部分程式碼是膠-協定語言,用於介紹擴展給PHP並且建立一個對話讓它們溝通的。只有最後4行程式碼可以稱之後"real code",用來執行使用者空間層腳本能互動的任務。確實這一層的程式碼看起來很像我們之前看的PHP程式碼也容易看懂:

        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

    傳入 PHP_INI_ENTRY() 的第一個參數是一個包含了php.ini的選項的名稱的字串。為避免命名空間碰撞,你必須使用和你方法相同的約定;那就是在所有值前加上你擴展的名字為前綴,比如剛才的"hello.greeting",作一個慣例的約定,一度曾將擴展名和ini設定名中更具描述性的部分和分開。

    第二個參數是初始值,且通常是作為char*串不管是不是數字。這主要是因為實際上.ini檔案裡的值預設是文字——那是一個文字檔。你可以在你的腳本中使用 INI_INT()INI_FLT()       傳入的第三個參數是存取模式修飾符。這是一個用來決定 

INI 在什麼時候什麼地方可以修改的位元遮罩。對於某些,例如 register_globals, 是很簡單的不允許在腳本中使用ini_set() 來修改的,因為這個配置只能在請求啟動腳本前有機會運行。另外如allow_url_fopen,是一些管理項,你不希望在共享主機環境中允許用戶修改,不管是ini_set() 還是.htaccess指令。這個參數的另一個典型的值應該是 PHP_INI_ALL,表示這個值在哪裡都能修改。另外PHP_INI_SYSTEM|PHP_INI_PERDIR,,表示這個配置能在php.ini檔案中設置,也能透過Apache指令在.htaccess檔案中修改,但不允許透過再者有PHP_INI_SYSTEM,意思是該值只能在php.ini檔案中修改,其他地方都不行。     我們就此跳過第四個參數,只點出該值用來傳入一個回調方法便以ini配置無論在何時被修改時調用,比如當使用 ini_set()

ini_set()

ini_set()ini_set()ini_set()ini_set()修改時。這能令擴充更精確的控制設定被修改,或透過修改一個新的配置來觸發對應的行為。

    全域變數


    通常,扩展需要在特定的请求里跟踪变量的值,使之独立于并发请求。在无线程的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 

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn