翻訳: 世界中でそれを失いました
元のアドレス:http://devzone.zend.com/303/extension-writing-part- i -introduction-to-php-and-zend/
ブログアドレス: http://lg.uuhonghe.com/index/view?id=3
はじめに
拡張機能とは
ライフサイクル
Hello World建 独自の拡張機能を作成する
ini 設定
グローバル変数
グローバル変数に ini を設定する
統合チェック
そしてマイル?
はじめに
このチュートリアルを読んでいる方は、PHP 言語で拡張機能を作成することに興味があるかもしれません。そうでなければ。 。 。読んでみると、今まで知らなかったことに興味が持てるかもしれません。
この記事は、読者が PHP 言語と C で書かれた PHP インタプリタについての基本を理解していることを前提としています。
まず、PHP 拡張機能を作成する理由を確認しましょう:
1. 言語コアの抽象化の深さにより、PHP を使用して直接完了できないライブラリとシステム コールがいくつかあります。
2. PHP にいくつかの珍しい方法で独自の動作を実装したいとします。
3. 大量の PHP コードを作成しましたが、それよりも高速に実行できることはわかっています。
4. 販売したい特に賢いアイデアを実装したコードがありますが、さらに重要なのは、販売したいコードは実行可能である必要がありますが、ソース コードには表示されないことです。
これらはすべて非常に正当な理由ですが、拡張機能を作成するには、まず拡張機能とは何かを理解する必要があります。
拡張機能とは何ですか?
PHP を書いたことがあるなら、拡張機能を使用したことがあるはずです。いくつかの拡張機能を使用するだけで、PHP のすべてのユーザー空間機能は、その拡張機能の関数グループに含まれます。これらの関数の多くは標準拡張機能の一部であり、合計で 400 を超えます。 PHP ソース コードには 86 個の拡張機能がバンドルされており、それぞれに平均約 30 個の関数があります。数えてみると、関数は全部で約 2500 個あります。それでも十分でない場合は、PECL リポジトリ
で 100 を超える追加の拡張機能が利用可能であり、さらに多くの拡張機能がオンラインの他の場所で見つけることができます。「これらの関数はすべて拡張機能に含まれているのですが、他に何が拡張されているのでしょうか? PHP のコアは何ですか?」と疑問に思うかもしれません。
PHP のコアは 2 つの部分で構成されます。一番下に Zend Engine (略して ZE) があります。 ZE は、人間が認識できるスクリプトを機械が認識するシンボルに解析し、これらのシンボルをプロセス空間で実行します。 ZE は、メモリ管理、変数フィールド、および関数呼び出しを同時に処理します。この違いのもう 1 つの部分は、PHP コアです。 PHP カーネルは、通信、接続、および SAPI 層 (サーバー アプリケーション プログラミング インターフェイス。Apache、IIS、CLI、CGI などのホスト環境を指すこともよくあります) を処理します。
など)、制御層でのsafe_modeとopen_basedirの統合検出、およびファイルとネットワークI/Oのユーザー空間関数fopen()、fread()、およびfwrite()に関連するストリーミング層も提供します。
ライフサイクル
/usr/local/apache/bin/apachectl start
の応答などでSAPIが開始されると、PHPはカーネルサブシステムを初期化して開始します。このブートプロセスの最後に、各拡張機能のカーネルがロードされ、モジュール初期化ルーチン (MINIT) が呼び出されます。 これにより、各拡張機能に内部変数の初期化、リソースの割り当て、リソース ハンドルの登録、およびその関数を ZE に登録する機会が与えられるため、スクリプトがこれらの関数を呼び出すときに、ZE はどのコードを実行するかを認識できます。
次に、PHP は、SAPI レイヤーが処理するページを要求するのを待ちます。 CGI または CLI SAPI の場合、これは直接かつ 1 回だけ発生します。 Apache、IIS、またはその他の成熟した Web サーバー SAPI では、これはリモート ユーザーが要求したときに発生し、場合によっては同時実行により複数回発生する可能性があります。リクエストがどのように到着したかに関係なく、PHP はまず ZE にスクリプトを実行するための環境をセットアップするように指示し、次に各拡張機能のリクエスト初期化 (RINI
RINI 拡張機能に、独自の特定の環境変数を設定したり、特定のリソースに対するリクエストを割り当てたり、監査などの他のタスクを実行したりする機会を与えます。 RINI関数の動作の主な例はセッションです。 拡張機能では、session.auto_start 項目が有効になっている場合、RINI はユーザー空間の session_start() 関数を自動的にトリガーし、$_SESSION 変数をプリセットします。 リクエストが初期化されると、ZE が PHP スクリプトをトークンに変換し、最終的にオペコードに変換して引き継ぎます。オペコードはシングルステップでデバッグおよび実行できます。
オペコードの 1 つに含まれる拡張メソッドが呼び出されると、ZE はメソッドのパラメーターをバンドルし、一時的に制御をダイレクト メソッド完了に渡します。
スクリプトが完了すると、PHP は各拡張リクエスト シャットダウン (RSHUTDOWN) 関数を呼び出して、最終的なクリーンアップ作業 (セッション変数がディスクに保存されるようにするなど) を実行します。次に、ZE はクリーンアップ プロセス (ガベージ コレクションと呼ばれる) を実行します。これにより、リクエストの前半で使用されるすべての変数に対して unset() 操作が効果的に実行されます。最後に、PHP は、SAPI が別のドキュメントをリクエストするかシグナルを待ちます。閉める。 CGI および CLI SAPI の場合、「次のリクエスト」がないため、SAPI はシャットダウン プロセスを直接開始します。シャットダウン プロセス中に、PHP は各拡張機能を再度実行し、モジュール シャットダウン (MSHUTDOWN) 関数を呼び出し、最終的に独自のカーネル サブシステムをシャットダウンします。
上記の内容は怖く聞こえるかもしれませんが、実用的な拡張機能の開発を始めると、その一部が徐々に明らかになるでしょう。
メモリ管理
不適切に書き込まれた拡張メモリの損失を防ぐために、ZE は永続性を示す追加のフラグを備えた内部メモリ マネージャーを実装しています。継続的な割り当ては、メモリ割り当てがページ要求よりも長く続くようにするために重要です。対照的に、非永続割り当ては、解放関数が呼び出されるかどうかに関係なく、割り当てられたリクエストの終了時に解放されます。たとえば、ユーザー空間変数は、リクエストの終了後に使用されなくなった場合、非永続的に割り当てられます。
ただし、おそらく拡張機能は理論的には ZE に依存して、ページリクエストの終了時に非永続メモリを自動的に解放することになりますが、これはお勧めできません。メモリ割り当てにより非リサイクル期間が長くなり、メモリ関連のリソースが適切なタイミングで閉じられる可能性が低く、クリーンアップ作業が行われないとこの作業が混乱します。後で説明するように、割り当てられたデータが時間内に確実にクリーンアップされるようにするのは簡単です。従来のメモリ割り当て (外部ライブラリを使用する場合に使用する必要があります) と、PHP/ZE での永続性と非永続的メモリ割り当てを簡単に比較してみましょう。 * ファミリには、
「persistent」フラグを使用して、それらを非永続部分に対応させます。例: safe_pemalloc(1234,
0) ** safe_pemalloc() (で
PHP5) では、整数のオーバーフローを回避するためのチェックが追加されています。 開発環境の構築
**🎜🎜🎜🎜🎜safe_emalloc(count,
num, extr)🎜🎜🎜🎜🎜🎜safe_pemalloc(count,
番号、追加)🎜🎜🎜🎜🎜🎜 🎜🎜🎜🎜
従来型
非永続
永続
calloc(count,
num)
malloc(count)
pecalloc(カウント、
num, 1)
strdup(str)
malloc(カウント
* num + extr)
および
Hello World
任何没有完成Hello World应用的程序介绍都是不完整的。因此,下面将制作一个只有一个返回字符串"Hello World"的函数的扩展。在PHP代码里你可能这样写:
<?php function hello_word(){ return 'Hello World'; } ?>
现在我们使用PHP扩展来实现这段代码,首先我们在PHP源码的ext/目录下创建目录hello并且进到该目录下。事实上该目录在不在PHP目录树下都可以,但是我让你把它放在这里以便后面将展示一个不相关的概念。该目录下需要创建三个文件:一个包含
まず、この一連の開発ツールは PHP から切り離せないものであるため、PHP 自体が必要です。ソース コードを使用して PHP を構築することに慣れていない場合は、まずこの記事を読むことをお勧めします: http://www.php.net/install.unix
。
(Windows を使用した PHP 拡張機能の開発に関する記事は後で説明します) 。
です。
--enable-experimental-zts
、PHP 5 以降では
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 #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
#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_FUNCTION(hello_world) { char *str; str = estrup("hello World"); RETURN_STRING(str, 0); }
1、声明一个方法
2、让这个方法返回一个字符串:"Hello World"
3、。。呃。。。1?这个1几个意思?
回忆一下,ZE包含了一套复杂的内存管理层,能确保分配的资源在脚本退出时被释放。然而在内存管理的掌控下,释放同样的块两次是非常大的禁忌。这种行为通常称为"double freeing",是一个常见的段错误的原因,涉及到一个正在调用的程序访问一个不再属于它的内存块。同样的,你不希望允许ZE去释放一个存活于程序空间并且其数据块被其他进程占用的静态字符串buffer(例如我们示例中的"Hello World")。
PHP_FUNCTION(hello_world) { char *str; str = estrup("hello World"); RETURN_STRING(str, 0); }
在此版本中,我们手动地分配内存给"Hello World"这个串,并且最终传回调用脚本,然后把内存传给
编译你的扩展
本练习的最后一步就是将你的扩展编译为一个动态可加载模块。如果你把上面的示例原封不动的抄下来,那么只需要在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命令会原封不动地显示传递给他的参数(此处即为该函数的返回值)。
其他标量也可用类似的方式返回,使用
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里
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解析器,运行一段简单的脚本来试试你刚刚添加的方法。事实上,为何不现在不试呢?我等着呢。。。
试完了?很好。如果你使用
PHP_FUNCTION(hello_bool){ RETURN_TRUE; }
注意这里没有使用括号哦。
你也许注意到上面这些代码样品我们都没有传0和1什来表示这些值是否需要被拷贝。这是因为没有额外的内存(变量容器之外——我们将在第2部分深入)需要被分配或释放,因为这些标量都很简单很小。
还有另外三种返回类型:
INI Settings
Zend 引擎提供了两个方式处理
现在我们想在php.ini中定义一个变量,"hello.greeting", 用来处理你在"hello_function()"函数里用来打招呼的变量。你需要在hello.c和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中用下面这串代码覆盖当前版本的
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顶部剩下的
#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); }
注意,你复制了来自
首次修改的部分包含了两个你需要熟悉的方法:
在你的方法hellow_world()中使用"INI_STR()"来取回当前"hello.greeting"项的值作为一个字符串。那些其他的已有方法取值作为长整型,浮点型和布尔型,如下表所示,"ORIG"补充了其他方法,能提供从
Current Value | Original Value | Type |
signed long | ||
signed double | ||
第二个参数是初始值,并且通常是作为char*串不管是不是数字。这主要是因为实际上.ini文件里的值默认是文本——那是一个文本文件。你可以在你的脚本中使用
传入的第三个参数是一个访问模式修饰符。这是一个用于决定
我们就此跳过第四个参数,只点出该值用来传入一个回调方法便以ini配置无论在何时被修改时调用,比如当使用
2つ目。パラメータは初期値であり、通常はcharとして取られます* 数値であるかどうかに関係なく文字列。これは主に、.ini ファイルの値がテキストであるためです。つまり、テキスト ファイルです。 height:2;background-color:rgb(242,242,242)">INI_INT()
スクリプト内、
通常,扩展需要在特定的请求里跟踪变量的值,使之独立于并发请求。在无线程的SAPI中这可能比较简单:只需要在源文件中声明一个全局变量,在需要时调用。然而麻烦在于PHP被设计运行于线程级的web服务器(如Apache 2 和 IIS),这就需要保证一个线程中的全局变量与其他线程中的分离。PHP通过使用TSRM(Thread Safe Resource Management)抽象层,大大地简化了这一操作,有时被称为ZTS(Zend Thread Safety)。实际 上,到现在你已经使用了一部分TSRM了,虽然你对其不甚了解。(不要急着去搜索,通过这一系列的进展,你会发现它无处不在。)
创建线程安全全局变量的第一步,跟创建其他全局变量一样,声明它。为了实现这个例子,你将声明一个以
#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);
现在 到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)
修改
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)); }
在添加到php_hello.h的代码里,使用了一对宏
INI设置 vs 全局变量
如果你回观前文,一个在
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; }
最后,在hello_long()中使用这个配置的值来决定是自增还是自减:
PHP_FUNCTION(hello_long) { if (HELLO_G(direction)) { HELLO_G(counter)++; } else { HELLO_G(counter)--; } RETURN_LONG(HELLO_G(counter)); }
就是这样,我们在
完整性检查
到现在,我们的三个文件里的内容应该如下所列的(为了可阅读性,一些条目被移到了一起)。
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扩展,导出方法,返回值,声明了
下一章我们研究PHP变量的内核结构,以及变量如何存储、跟踪和在脚本环境中修改。当一个函数被调用时我们使用