ホームページ  >  記事  >  バックエンド開発  >  PHP 拡張機能の作成 1: PHP と Zend の概要

PHP 拡張機能の作成 1: PHP と Zend の概要

WBOY
WBOYオリジナル
2016-07-30 13:29:341040ブラウズ
拡張機能の作成パート I: PHP と Zend の紹介

翻訳: 世界中でそれを失いました

元のアドレス: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 での永続性と非永続的メモリ割り当てを簡単に比較してみましょう。

calloc(count, num)malloc(カウント * num + extr)
従来型 非永続 永続
malloc(count)
malloc(count)
emalloc(count)
emalloc(カウント)
ecalloc(カウント, num)
pemalloc(カウント, 1)*
pecalloc(カウント、 num, 1)strdup(str)
strdup(str)estrdup(str)
strndup(str, len)
estrdup(str)
estrdup(str, len)
free(ptr) efree(ptr)pestrdup(str, 1) pemalloc() & memcpy()
free(ptr) efree(ptr) pefree(ptr, 1)
realloc(ptr, newsize) errealloc(ptr, newsize) perrealloc(ptr, newsize、1)
**🎜🎜🎜🎜🎜safe_emalloc(count, num, extr)🎜🎜🎜🎜🎜🎜safe_pemalloc(count, 番号、追加)🎜🎜🎜🎜🎜🎜 🎜🎜🎜🎜

* pemalloc()pemalloc()

ファミリには、 「persistent」フラグを使用して、それらを非永続部分に対応させます。例: safe_emalloc(1234) safe_emalloc(1234) および

safe_pemalloc(1234, 0)safe_emalloc() 和  safe_pemalloc()同じ。

**

safe_emalloc()


および

safe_pemalloc() (で PHP5) では、整数のオーバーフローを回避するためのチェックが追加されています。 開発環境の構築--enable-debug

 这个选项令PHP在编译时加入可执行的符号信息,以便当发生一个段错误时,你能从内核存储中得到它并且使用gdb跟踪段错误发生的地方和原因。另外一个选项取决于你开发使用的PHP的版本。在PHP4.3中这选项名为--enable-experimental-zts,在PHP5或更高的版本中为--enable-maintainer-zts该先项让PHP在多线程环境中思考自己的操作,让你捕获一些常见在非线程环境下是无害而导致您的扩展在多线程环境下用不了的异常。只要你在编译PHP时使用了这些额外的选项安装在你的开发服务器(或者工作站)上,你可以开始编写你的第一个扩展了。

        Hello World

        任何没有完成Hello World应用的程序介绍都是不完整的。因此,下面将制作一个只有一个返回字符串"Hello World"的函数的扩展。在PHP代码里你可能这样写:

<?php
function hello_word(){
    return 'Hello World';
}
?>

    现在我们使用PHP扩展来实现这段代码,首先我们在PHP源码的ext/目录下创建目录hello并且进到该目录下。事实上该目录在不在PHP目录树下都可以,但是我让你把它放在这里以便后面将展示一个不相关的概念。该目录下需要创建三个文件:一个包含 hello_world PHP と Zend Engine の動作の背後にある理論をいくつか学習したので、実際に開発を開始したいと思われるでしょう。次に、開始する前に、ニーズを満たす開発環境をセットアップするために必要な作業をいくつか収集する必要があります。

まず、この一連の開発ツールは PHP から切り離せないものであるため、PHP 自体が必要です。ソース コードを使用して PHP を構築することに慣れていない場合は、まずこの記事を読むことをお勧めします: http://www.php.net/install.unix
。 (Windows を使用した PHP 拡張機能の開発に関する記事は後で説明します)

安全のために Linux ディストリビューションに含まれるバイナリ パッケージを使用することは非常に誘惑的ですが、これらのパッケージでは、開発中に非常に便利な 2 つの ./configure オプションが欠落します。 1 つ目は


--enable-debug


です。

このオプションにより、PHP はコンパイル時に実行可能シンボル情報を追加します。これにより、セグメンテーション違反が発生したときにカーネル ストレージから情報を取得し、gdb を使用してセグメンテーション違反が発生した場所と理由を追跡できます。別のオプションは、開発に使用している PHP のバージョンによって異なります。このオプションの名前は、PHP 4.3 では

--enable-experimental-zts
、PHP 5 以降では

--enable-maintainer-zts🎜🎜 です。 🎜この優先順位により、PHP はマルチスレッド環境での操作を考慮できるようになり、非スレッド環境では無害であり、マルチスレッド環境では拡張機能が使用できなくなる原因となるいくつかの一般的な例外をキャッチできるようになります。開発サーバー (またはワークステーション) にインストールされたこれらの追加オプションを使用して PHP をコンパイルしていれば、最初の拡張機能の作成を開始できます。 T Hello World🎜🎜🎜🎜🎜 Hello World アプリケーションが完了していないプログラムの導入は不完全です。したがって、以下では、文字列「Hello World」を返す関数を 1 つだけ拡張します。 PHP コードでは次のように記述できます: 🎜🎜🎜
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 拡張機能を使用してこのコードを実装します。 まず、PHP ソース コードの ext/ ディレクトリにディレクトリ 🎜hello🎜 を作成し、そのディレクトリに入ります。実際、このディレクトリは PHP ディレクトリ ツリー内または外に置くことができますが、後で無関係な概念を説明するために、ここに置くことにしました。このディレクトリに 3 つのファイルを作成する必要があります: 🎜 🎜hello_world🎜 🎜 メソッドのソース ファイル、拡張機能をロードするための PHP への参照を含むヘッダー ファイル、および 🎜phpize🎜 が拡張機能をコンパイルできるようにする設定ファイル config.m4🎜🎜🎜
#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
🎜 php_hello.h🎜🎜。 🎜🎜
#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);
}
🎜 Hello.c🎜🎜
PHP_FUNCTION(hello_world)
{
    char *str;
    str = estrup("hello World");
    RETURN_STRING(str, 0);
}
🎜 上記の拡張機能のコードの大部分は、PHP に拡張機能を導入し、通信するためのダイアログを確立するために使用されるグルー プロトコル言語であることがわかります。コードの最後の 4 行は「実際のコード」と呼ばれ、ユーザー空間層のスクリプトが対話できるタスクを実行するために使用されます。実際、この層のコードは前に説明した 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.ini」を付けます。先ほどの「挨拶」を共通の規約として、拡張機能名とini設定名のよりわかりやすい部分を一旦分離しましたPHP_INI_ENTRY() 的第一个参数是一个包含了php.ini的选项的名称的字符串。为避免命名空间碰撞,你必须使用和你方法相同的约定;那就是在所有值前加上你扩展的名字为前缀,比如刚才的"hello.greeting",作一个惯例的约定,一度曾将扩展名和ini设置名中更具描述性的部分和分开。

    第二个参数是初始值,并且通常是作为char*串不管是不是数字。这主要是因为实际上.ini文件里的值默认是文本——那是一个文本文件。你可以在你的脚本中使用 INI_INT()INI_FLT()或者  INI_BOOL() 来作类型转换。

    传入的第三个参数是一个访问模式修饰符。这是一个用于决定 INI 在什么时候什么地方可以修改的位掩码。对于某些,比如 register_globals是很简单的不允许在脚本中使用ini_set() 来修改的,因为这个配置只能在请求启动脚本前有机会运行。另外如allow_url_fopen,是一些管理项,你不希望在共享主机环境中允许用户修改,不管是ini_set() 还是.htaccess指令。这个参数的另一个典型的值应该是 PHP_INI_ALL,表示这个值在哪里都能修改。另有PHP_INI_SYSTEM|PHP_INI_PERDIR,,表示这个配置能在php.ini文件中设置,也能通过Apache指令在.htaccess文件中修改,但是不允许通过 ini_set()修改。再者有PHP_INI_SYSTEM,意思是该值只能在php.ini文件中修改,其他地方都不行。

    我们就此跳过第四个参数,只点出该值用来传入一个回调方法便以ini配置无论在何时被修改时调用,比如当使用 ini_set()

2つ目。パラメータは初期値であり、通常はcharとして取られます* 数値であるかどうかに関係なく文字列。これは主に、.ini ファイルの値がテキストであるためです。つまり、テキスト ファイルです。 height:2;background-color:rgb(242,242,242)">INI_INT()
スクリプト内、INI_FLT ()

または 🎜 🎜 🎜INI_BOOL ()🎜 🎜 を使用して型変換を実行します。 🎜🎜🎜🎜🎜🎜🎜🎜 渡される 3 番目のパラメーターは、アクセス モード修飾子です。これは、🎜 🎜INI🎜🎜 をいつ、どこで変更できるかを決定するために使用されるビットマスクです。 🎜 🎜register_globals🎜 のように、🎜 はスクリプト内で ini_set()🎜 🎜 を変更するには、この設定は起動スクリプトをリクエストする前にのみ実行できるためです。 ',monospace; line-height:2; background-color:rgb(242,242,242)">allow_url_fopen は、ini_set()🎜 または.htaccess ディレクティブ。このパラメータのもう 1 つの一般的な値は次のとおりです。これは、この値がどこでも変更できることを示します。 PHP_INI_SYSTEM|PHP_INI_PERDIR🎜,🎜 もあります。これは、この設定が php.ini に存在する可能性があることを示しています。 ファイル内で設定され、Apache 命令を通じて .htaccess ファイル内で変更することもできますが、 🎜 🎜ini_set()を変更します。さらに、PHP_INI_SYSTEM があります。これは、この値が php.ini でのみ設定できることを意味します。 ファイル 🎜🎜🎜🎜🎜🎜 4 番目のパラメータをスキップし、🎜 🎜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 までご連絡ください。