譯:我在江湖丟了
原文地址: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倉庫
「這些函數都在擴充裡,那還有什麼?」你會問,「它們擴充了什麼?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
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中的持續性與未持續性記憶體分配。 * safe_emalloc()
首先,你需要PHP本身,因為這一系列開發工具都離不開PHP。如果你還對使用源碼搭建PHP不熟悉,推薦你看下這篇文章先:http://www.php.net/install.unix --enable-debug。 這個選項令PHP在編譯時加入可執行的符號訊息,以便當發生一個段錯誤時,你能從內核儲存中得到它並且使用gdb追蹤段錯誤發生的地方和原因。另一個選項取決於你開發使用的PHP的版本。在PHP4.3中這選項名稱為 因此,下面將製作一個只有一個返回字串"Hello World"的函數的擴充。在PHP程式碼裡你可能這樣寫: hello並且進到該目錄下。事實上該目錄在不在PHP目錄樹下都可以,但是我讓你把它放在這裡以便後面將展示一個不相關的概念。目錄下需要建立三個文件:一個包含 方法的源文件,一個包含用於讓PHP加載你的擴展的引用的頭文件,和一個用於讓phpize 看得出來,上述範例擴充裡的大部分程式碼是膠-協定語言,用於介紹擴展給PHP並且建立一個對話讓它們溝通的。只有最後4行程式碼可以稱之後"real code",用來執行使用者空間層腳本能互動的任務。確實這一層的程式碼看起來很像我們之前看的PHP程式碼也容易看懂: 1、声明一个方法 2、让这个方法返回一个字符串:"Hello World" 3、。。呃。。。1?这个1几个意思? 回忆一下,ZE包含了一套复杂的内存管理层,能确保分配的资源在脚本退出时被释放。然而在内存管理的掌控下,释放同样的块两次是非常大的禁忌。这种行为通常称为"double freeing",是一个常见的段错误的原因,涉及到一个正在调用的程序访问一个不再属于它的内存块。同样的,你不希望允许ZE去释放一个存活于程序空间并且其数据块被其他进程占用的静态字符串buffer(例如我们示例中的"Hello World")。 在此版本中,我们手动地分配内存给"Hello World"这个串,并且最终传回调用脚本,然后把内存传给 编译你的扩展 本练习的最后一步就是将你的扩展编译为一个动态可加载模块。如果你把上面的示例原封不动的抄下来,那么只需要在ext/hello/目录下运行下面三步命令就行: 运行完上述三个命令之后,你应该在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重启。让我们试下运行下面命令: 如果一切顺利,你现在应该能看到这段代码输出"Hello World"了,因为你的扩展里的"hello_world()"返回了一个字符串"Hello World",而echo命令会原封不动地显示传递给他的参数(此处即为该函数的返回值)。 其他标量也可用类似的方式返回,使用 你同样需要在头文件php_hello.h里 因为你没有修改config.m4文件,所以技术上这次跳过phpize和./configure这两个步骤直接make是安全的。然后,在本游戏的这个阶段,我还是要求你从头把三个步骤都执行一遍以确认活干得漂亮。另外,最后一步的时候,你应该执行make clean all而不是简单的执行make,来确保所有源文件重建。再次声明,现在的改动上述这些步骤是非必须的,但是会更安全更清晰。模块建好后,再拷贝到你的扩展目录下,替换旧版本。 这个时候你可以再次调用你的PHP解析器,运行一段简单的脚本来试试你刚刚添加的方法。事实上,为何不现在不试呢?我等着呢。。。 试完了?很好。如果你使用 注意这里没有使用括号哦。 你也许注意到上面这些代码样品我们都没有传0和1什来表示这些值是否需要被拷贝。这是因为没有额外的内存(变量容器之外——我们将在第2部分深入)需要被分配或释放,因为这些标量都很简单很小。 还有另外三种返回类型: INI Settings Zend 引擎提供了两个方式处理 现在我们想在php.ini中定义一个变量,"hello.greeting", 用来处理你在"hello_function()"函数里用来打招呼的变量。你需要在hello.c和php_hello.h中添另一些东西,并且 现在到hello.c中用下面这串代码覆盖当前版本的 现在,你只需要在hello.c顶部剩下的 最后,我们修改 注意,你复制了来自 首次修改的部分包含了两个你需要熟悉的方法: 在你的方法hellow_world()中使用"INI_STR()"来取回当前"hello.greeting"项的值作为一个字符串。那些其他的已有方法取值作为长整型,浮点型和布尔型,如下表所示,"ORIG"补充了其他方法,能提供从 傳入 第二個參數是初始值,且通常是作為char*串不管是不是數字。這主要是因為實際上.ini檔案裡的值預設是文字——那是一個文字檔。你可以在你的腳本中使用 INI ini_set()ini_set() 通常,扩展需要在特定的请求里跟踪变量的值,使之独立于并发请求。在无线程的SAPI中这可能比较简单:只需要在源文件中声明一个全局变量,在需要时调用。然而麻烦在于PHP被设计运行于线程级的web服务器(如Apache 2 和 IIS),这就需要保证一个线程中的全局变量与其他线程中的分离。PHP通过使用TSRM(Thread
Safe Resource Management)抽象层,大大地简化了这一操作,有时被称为ZTS(Zend Thread Safety)。实际 上,到现在你已经使用了一部分TSRM了,虽然你对其不甚了解。(不要急着去搜索,通过这一系列的进展,你会发现它无处不在。) 创建线程安全全局变量的第一步,跟创建其他全局变量一样,声明它。为了实现这个例子,你将声明一个以 这次你还将用到 现在 到hello.c中添加下面这段代码到include 模块后: 修改 修改你的 最后,修改你的 在添加到php_hello.h的代码里,使用了一对宏 INI设置 vs 全局变量 如果你回观前文,一个在 然后,通过修改 现在在 最后,在hello_long()中使用这个配置的值来决定是自增还是自减: 就是这样,我们在 完整性检查 到现在,我们的三个文件里的内容应该如下所列的(为了可阅读性,一些条目被移到了一起)。 config.m4 php_hello.h hello.c 接下来做什么? 在这教程中我们开发了一个简单的PHP扩展,导出方法,返回值,声明了 下一章我们研究PHP变量的内核结构,以及变量如何存储、跟踪和在脚本环境中修改。当一个函数被调用时我们使用
傳統
非持久
持久
strdup(str)o
free(ptr)
1)erealloc(ptr,
newsize)
perealloc(ptr,
newsize, 1)
safe_pemalloc(計數,
數字,額外)
.
(使用Windows開發PHP擴充的文章稍後給出)。 <?php
function hello_word(){
return 'Hello World';
}
?>
現在我們使用PHP擴充來實作這段程式碼,首先我們在PHP原始碼的ext/目錄下建立目錄
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_FUNCTION(hello_world)
{
char *str;
str = estrup("hello World");
RETURN_STRING(str, 0);
}
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
$ php -r 'echo hello_world();'
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_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
PHP_FUNCTION(hello_bool){
RETURN_TRUE;
}
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);
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;
}
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h
"#include "php_hello.h"
PHP_FUNCTION(hello_world)
{
RETURN_STRING(INI_STR("hello.greeting"), 1);
}
Current Value
Original Value
Type
signed long
signed double
#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
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
ZEND_DECLARE_MODULE_GLOBALS(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
};
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;
}
PHP_FUNCTION(hello_long)
{
HELLO_G(counter)++;
RETURN_LONG(HELLO_G(counter));
}
ZEND_BEGIN_MODULE_GLOBAL(hello)
login counter;
zend_bool direction;
ZEND_ENG_MODULE_GLOBALS(hello)
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_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}
RETURN_LONG(HELLO_G(counter));
}
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
#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
#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();
}