ホームページ  >  記事  >  バックエンド開発  >  [翻訳][php 拡張機能の開発と埋め込み] 第 5 章 - 最初の拡張機能

[翻訳][php 拡張機能の開発と埋め込み] 第 5 章 - 最初の拡張機能

黄舟
黄舟オリジナル
2017-02-09 11:31:371202ブラウズ

最初の拡張機能

すべての PHP 拡張機能を構築するには、少なくとも 2 つのファイルが必要です。1 つは、構築するファイルとコンパイル中にどの外部ライブラリが必要かを示す構成ファイル、もう 1 つは実際のジョブを実行する少なくとも 1 つのソース ファイルです。

拡張機能の分析

実際には、通常、2 つ目または 3 つ目の設定ファイルと 1 つ以上のヘッダー ファイルがあり、各タイプのファイルを 1 つ追加して作業する必要があります。設定ファイル

まず、php ソース コード ツリーの ext/ ディレクトリに、sample という名前のディレクトリを作成します。実際、この新しいディレクトリはどこにでも配置できますが、この章の後半で win32 と static について説明します。ビルド オプション。まずソース コード ディレクトリにビルドしましょう。

次のステップでは、このディレクトリに入り、config.m4 という名前のファイルを作成し、次の内容を入力します。設定時に enable-sample オプションを呼び出します。 PHP_ARG_ENABLE の 2 番目のパラメータは、./configure 処理中にこの拡張設定ファイルに到達したときに表示されます。 3 番目のパラメータは、端末ユーザーが ./configure --help を実行したときに表示されます。一部の拡張機能構成では、enable-extname が使用され、他の拡張機能では with-extname が使用されるのはなぜだろうかと疑問に思ったことはありますか。実際、enable は、この機能を有効にするために他に何も必要ないことを意味します。対照的に、 with は、こ​​の機能を使用するための他の前提条件があることを意味します

これで、サンプル拡張機能を他のライブラリにリンクする必要がないため、第 17 章「外部ライブラリ」で使用する必要があるのは、enable バージョンのみです。 with の使用を導入し、追加の CFLAGS および LDFLAGS 設定を使用するようにコンパイラーに指示します。

エンドユーザーが enable-sample オプションを指定して ./configure を呼び出すと、ローカル環境変数 $PHP_SAMPLE が yes に設定されます。標準 autoconf の AC_SUBST() マクロの PHP 修正バージョン。共有モジュールとして拡張機能を構築するときに必要です。

最後に重要なことですが、PHP_NEW_EXTENSION() はモジュールを定義し、拡張機能の一部としてコンパイルする必要があるすべてのソース ファイルを列挙します。複数のファイルが必要な場合は、2 番目のパラメーターにスペースを使用して列挙できます。例:

PHP_ARG_ENABLE(sample,  
  [Whether to enable the "sample" extension],  
  [  enable-sample        Enable "sample" extension support])  
  
if test $PHP_SAMPLE != "no"; then  
  PHP_SUBST(SAMPLE_SHARED_LIBADD)  
  PHP_NEW_EXTENSION(sample, sample.c, $ext_shared)  
fi

最後のパラメーターは、ビルド共有の PHP_SUBST(SAMPLE_SHARED_LIBADD) コマンドに対応します。これはモジュールにも必要です。ヘッダー ファイル

C で開発する場合、データ型定義を外部ヘッダー ファイルに分離し、それをソース ファイルに含めるのが一般的ですが、PHP ではこれを必要としませんが、これによりモジュールが大きくなったときに作業が簡素化されます。単一のソース ファイルです。

php_sample.h ヘッダー ファイルで、次の内容から始めます

PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)

このヘッダー ファイルは、2 つの主要なタスクを完了します: 拡張機能が phpize ツール (通常、この本で使用される方法) を使用してビルドされている場合) の場合、HAVE_CONFG_H が定義されるため、拡張子のコンパイル方法に関係なく、config.h が通常どおりインクルードされます。このヘッダー ファイルには、PHP ソース コードで使用される他のヘッダー ファイルが含まれます。ほとんどの PHPAPI にアクセスします

次に、拡張機能で使用される zend_module_entry 構造体が外部として定義されるため、このモジュールが extension=xxx を使用してロードされると、dlopen と dlsym() を使用して Zend によって取得できます。モジュールの読み込みプロセスについては、翻訳者のブログ「dl('xxx.so') 関数からの PHP モジュール開発の分析」(http://blog.csdn.Net/lgg201/article/details/6584095) を参照してください。

ヘッダー ファイルには、元のファイルで使用される情報を定義するための前処理も含まれます。

ソース コード

最後に、最も重要なのは、sample.c で単純なソース コードのスケルトンを作成することが重要です:

#ifndef PHP_SAMPLE_H  
/* 防止重复包含 */  
#define PHP_SAMPLE_H  
  
/* 定义扩展的属性 */  
#define PHP_SAMPLE_EXTNAME    "sample"  
#define PHP_SAMPLE_EXTVER    "1.0"  
  
/* 在php源码树外面构建时引入配置选项 */  
#ifdef HAVE_CONFIG_H  
#include "config.h"  
#endif  
  
/* 包含php的标准头文件 */  
#include "php.h"  
  
/* 定义入口点符号, Zend在加载这个模块的时候使用 */  
extern zend_module_entry sample_module_entry;  
#define phpext_sample_ptr &sample_module_entry  
  
#endif /* PHP_SAMPLE_H */

これら 3 つのファイルは、モジュールのスケルトンを作成するために必要なすべてです。ただし、このセクションで説明するように、機能を備えたテンプレートが適切な選択肢であることを見てみましょう。最初の行は非常に単純で、先ほど作成したヘッダー ファイルが含まれており、拡張子として PHP ソース ツリー内の他のカーネル ヘッダーが取得されます。次に、ヘッダー ファイルに定義した zend_module_entry 構造体を作成します。現在の ZEND_MODULE_API_NO 定義を考えると、 zend_module_entry の最初の要素は条件式であることに注意してください。拡張機能がこれより古いバージョンにインストールされないことが確実な場合は、次のようにすることができます。 #ifdef 部分を切り取り、STANDARD_MODULE_HEADER 要素を直接組み込みます。

とにかくコンパイル中に少し時間がかかることを考慮してください。結果のバイナリや処理に必要な時間に影響を与えるため、ほとんどの場合、この条件を直接削除することをお勧めします。同じことが以下の version 属性にも当てはまります

他の 6 つの要素は、その後のコメントでその目的を確認できます。この短い条件は、Zend を動的にロードするときに使用され、参照を追加することを確認するだけで済みます。存在しない場合、次のセクションは機能しない可能性があります。

最初の拡張機能を構築する

すべてのファイルを用意したら、コンパイルしてインストールします。メインの PHP バイナリのコンパイルと比較すると、手順が少し異なります。

在*nix上构建

第一步是使用config.m4中的信息作为末班生成./configure脚本. 这可以运行在你安装主php二进制时附带安装的phpize程序来完成:

$ phpize  
PHP Api Version: 20041225  
Zend Module Api No: 20050617  
Zend Extension Api No: 220050617

Zend Extension Api No前面多出来的2并不是印刷错误; 它对应于Zend引擎2这个版本号, 一般认为要保持这个API编号大于它对应的ZE1版本.

如果你此时查看当前目录 你会注意到比刚才的3个文件多了不少文件. phpize程序结合你扩展的config.m4文件以及从你的php构建中收集的信息和所有让编译发生所需的一切. 这意味着你不用纠缠在makefile和定位php头上面. php已经帮你做了这个工作.

下一步就简单了, 执行./configure. 这里你只要配置你的扩展, 因此你需要做的如下:

$ ./configure --enable-sample

注意这里没有使用enable-debug和enable-maintainer-zts. 这是因为phpize已经将它们的值从主php构建中拿过来并应用到你的扩展的./configure脚本中了.

现在, 构建它! 和其他任何的包一样, 你只需要键入make, 生成的脚本文件就会处理剩下的事情.

构建处理完成后, 你会得到一个消息指出sample.so已经编译并放在了当前构建目录下一个名为"modules"的目录中.

在windows上构建

译者不熟悉windows平台, 因此略过.

将构建的扩展作为共享模块加载

在请求的时候为了让php找到这个模块, 需要将它放到php.ini中extension_dir设置的目录下. 默认的php.ini放在/usr/local/lib/php.ini; 不过这个默认值可能会因为包管理系统而不同. 检查php -i的输出可以看到你这个配置文件在哪里.

如果php.ini中的这个设置没有修改过, 它的值默认是"PHP_HOME/lib/php/extensions/debug-zts-20100525", 后面的debug-zts-20100525分别是是否启用调试, 是否启用zts, PHPAPI编号. 如果你还没有已加载的扩展, 或者说除了sample.so没有其他扩展, 可以将这个值修改到你make产生模块的路径. 否则, 直接将产生的sample.so拷贝到这个设置的目录下.(译注: php -i | grep extension_dir查找你的extension_dir设置)

在extension_dir指向正确的位置后, 有两种方式告诉php去加载你的模块. 第一种是在脚本中使用dl()函数:

<?php  
    dl(&#39;sample.so&#39;);  
    var_dump(get_loaded_modules());  
?>

如果脚本没有显示sample已加载, 则表示有哪里出了问题. 查看输出上面的错误消息作为线索, 或者如果在php.ini中进行了相应的设置就参考error_log.

第二种方式, 也是更常用的方式, 在php.ini中使用extension指令指定要加载的模块. 这个指令在php.ini的设置中是比较特殊的, 可以多次以不同的值使用它. 因此如果你已经在php.ini中设置了一个扩展, 不要在同一行使用分隔符的方式列举, 而是插入新的一行: extension=sample.so. 此时你的php.ini看起来是这样的:

extension_dir=/usr/local/lib/php/modules/  
extension=sample.so

现在你可以不使用dl()运行相同的脚本, 或者直接执行php -m命令, 就可以在已加载模块列表中看到sample了.

所有本章剩余以及以后章节的代码, 都假设你已经按照这里描述的方法加载了当前扩展. 如果你计划使用dl(), 请确认在测试脚本中加入加载的代码(dl()).

静态构建

在已加载模块列表中, 你可能注意到了一些在php.ini中并没有使用extension指令包含的模块. 这些模块是直接构建到php中的, 它们作为主php程序的一部分被编译进php中.

在*nix下静态构建

现在, 如果你现在进入到php源码树的根目录, 运行./configure --help, 你会看到虽然你的扩展和所有其他模块都在ext/目录下, 但它并没有作为一个选项列出. 这是因为, 此刻, ./configure脚本已经生成了, 而你的扩展并不知道. 要重新生成./configure并让它找到你的新扩展, 你需要做的只是执行一条命令:

$ ./buildconf

如果你使用产品发布版的php做开发, 你会发现./buildconf自己不能工作. 这种情况下, 你需要执行: ./buildconf --force来绕过对./configure命令的一些保护.

现在你执行./configure --help就可以看到--enable-sample是一个可用选项了. 此时, 你就可以重新执行./configure, 使用你原来构建主php时使用的所有选项, 外加--enable-sample, 这样构建出来的php二进制文件就是完整的, 包含你自己扩展的程序.

当然, 这样做还有点早. 你的扩展除了占用空间还应该做一些事情.

windows下静态构建

译者不熟悉windows环境, 因此略过.

功能函数

在用户空间和扩展代码之间最快捷的链接就是PHP_FUNCTION(). 首先在你的sample.c文件顶部, #include "php_sample.h"之后增加下面代码:

PHP_FUNCTION(sample_hello_world)  
{  
    php_printf("Hello World!\n");  
}

PHP_FUNCTION()宏函数就像一个普通的C函数定义, 因为它按照下面方式展开:

#define PHP_FUNCTION(name) \  
    void zif_##name(INTERNAL_FUNCTION_PARAMETERS)

这种情况下, 它就等价于:

void zif_sample_hello_world(zval *return_value,  
    char return_value_used, zval *this_ptr TSRMLS_DC)

当然, 只定义函数还不够. 引擎需要知道函数的地址以及应该暴露给用户空间的函数名. 这通过下一个代码块完成, 你需要在PHP_FUNCTION()块后面增加:

static function_entry php_sample_functions[] = {  
    PHP_FE(sample_hello_world,        NULL)  
    { NULL, NULL, NULL }  
};

php_sample_functions向量是一个简单的NULL结束向量, 它会随着你向扩展中增加功能而变大. 每个你要暴露出去的函数都需要在这个向量中给出说明. 展开PHP_FE()宏如下:

{ "sample_hello_world", zif_sample_hello_world, NULL},

这样提供了新函数的名字, 以及指向它的实现函数的指针. 这里第三个参数用于提供暗示信息, 比如某些参数需要引用传值. 在第7章"接受参数"你将看到这个特性.

现在, 你有了一个要暴露的函数列表, 但是仍然没有连接到引擎. 这通过对sample.c的最后一个修改完成, 将你的sample_module_entry结构体中的NULL, /* Functions */一行用php_sample_functions替换(请确保留下那个逗号).

现在, 通过前面介绍的方式重新构建, 使用php命令行的-r选项进行测试, -r允许你不用创建文件直接在命令行运行简单的代码片段:

$ php -r &#39;sample_hello_world();

如果一切OK的话, 你将会看到输出"Hello World!". 恭喜!!!

Zend内部函数

内部函数名前缀"zif_"是"Zend内部函数"的命名标准, 它用来避免可能的符号冲突. 比如, 用户空间的strlen()函数并没有实现为void strlen(INTERNAL_FUNCTION_PARAMETERS), 因为它会和C库的strlen冲突.

zif_的前缀也并不能完全避免名字冲突的问题. 因此, php提供了可以使用任意名字定义内部函数的宏: PHP_NAMED_FUNCTION(); 例如PHP_NAMED_FUNCTION(zif_sample_hello_world)等同于前面使用的PHP_FUNCTION(sample_hello_world)

当使用PHP_NAMED_FUNCTION定义实现时, 在function_entry向量中, 可以对应使用PHP_NAMED_FE()宏. 因此, 如果你定义了自己的函数PHP_NAMED_FUNCTION(purplefunc), 就要使用PHP_NAMED_FE(sample_hello_world, purplefunc, NULL), 而不是使用PHP_FE(sample_hello_world, NULL).

我们可以在ext/standard/file.c中查看fopen()函数的实现, 它实际上使用PHP_NAMED_FUNCTION(php_if_fopen)定义.从用户空间角度来看, 它并不关心函数是什么东西, 只是简单的调用fopen(). 

函数别名

有时一个函数可能会有不止一个名字. 回想一下, 普通的函数内部定义是用户空间函数名加上zif_前缀, 我们可以看到用PHP_NAMED_FE()宏可以很容易的创建这个可选映射.

PHP_FE(sample_hello_world,    NULL)  
PHP_NAMED_FE(sample_hi,    zif_sample_hello_world,     NULL)

PHP_FE()宏将用户空间函数名sample_hello_world和PHP_FUNCTION(sample_hello_world)展开而来的zif_sample_hello_world关联起来. PHP_NAMED_FE()宏则将用户空间函数名sample_hi和同一个内部实现关联起来.

现在, 假设Zend引擎发生了一个大的变更, 内部函数的前缀从zif_修改为pif_了. 这个时候, 你的扩展就不能工作了, 因为当到达PHP_NAMED_FE()时, 发现zif_sample_hello_world没有定义.

这种情况并不常见, 但非常麻烦, 可以使用PHP_FNAME()宏展开sample_hello_world避免这个问题:

PHP_NAMED_FE(sample_hi, PHP_FNAME(sample_hello_world), NULL)

这种情况下, 即便函数前缀被修改, 扩展的zend_function_entry也会使用宏扩展自动的更新.

现在, 你这样做已经可以工作了, 但是我们已经不需要这样做了. php暴露了另外一个宏, 专门设计用于创建函数别名. 前面的例子可以如下重写:

PHP_FALIAS(sample_hi, sample_hello_world, NULL)

实际上这是官方的创建函数别名的方法, 在php源码树中你时常会看到它.

小结

本章你创建了一个简单的可工作的php扩展, 并学习了在主要平台上构建它需要的步骤. 在以后的章节中, 你将会继续丰满这个扩展, 最终让它包含php的所有特性.

php源码树和它编译/构建在各个平台上依赖的工具经常会发生变化, 如果本章介绍的某些地方不能正常工作, 请参考php.net在线手册的Installation一掌, 查看你使用的版本的特殊需求.

以上就是 [翻译][php扩展开发和嵌入式]第5章-您的第一个扩展的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。