Your first extension
The construction of each PHP extension requires at least two files: a configuration file, which tells the compile time which files to build and what external libraries are required, and at least one source file, which does the actual work.
Profiling Extension
In practice, there will usually be a second or third configuration file, and one or more header files. For your For an extension, you need to add one file of each type and work with them.
Configuration files
To get started, first in the ext/ directory of your php source code tree Create a directory named sample. Actually this new directory can be placed anywhere, but in order to demonstrate win32 and static build options later in this chapter, we will create it in the source code directory first.
Next step , enter this directory, create a file named config.m4, type the following content:
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
This is the minimum requirement to be able to call the enable-sample option when ./configure. The second parameter of PHP_ARG_ENABLE will be in Displayed when the extended configuration file is reached during ./configure processing. The third parameter will be displayed as a help message when the terminal user executes ./configure --help.
Have you ever wondered why some extensions Configuration uses enable-extname, while some extensions use with-extname? There is no functional difference between the two. In fact, enable means that enabling this feature does not require any other third-party libraries, in contrast, with means that this should be used Features have other prerequisites
Now, your sample extension does not need to be linked with other libraries, so you only need to use the enable version. In Chapter 17 "External Libraries", we will introduce using with and indicate The compiler uses additional CFLAGS and LDFLAGS settings.
If the end user calls ./configure with the enable-sample option, then the local environment variable $PHP_SAMPLE, will be set to yes. PHP_SUBST() is the standard autoconf PHP modified version of the AC_SUBST() macro, which is required when building an extension as a shared module.
Last but not least, PHP_NEW_EXTENSION() defines the module and enumerates all that must be used as an extension Part of the compiled source files. If multiple files are needed, it can be listed using spaces in the second parameter, for example:
PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)
The last parameter corresponds to the PHP_SUBST(SAMPLE_SHARED_LIBADD) command, in the build share It is also needed for modules.
Header file
When developing in C, it is common to isolate the data type definition in an external header file and include it in the source file. Practice. Although PHP does not require this, it can be simplified when the module grows to the point where it cannot be placed in a single source file.
In your php_sample.h header file, start with the following content
#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 */
This header file completes two main tasks: If the extension is built using the phpize tool (this method is usually used in this book), then HAVE_CONFG_H is defined, so config.h will be used normally Included. No matter how the extension is compiled, php.h will be included from the php source tree. This header file contains other header files used in the php source code to access most of the PHPAPI.
Next, your The zend_module_entry structure used by the extension is defined as external, so that when this module is loaded using extension=xxx, it can be fetched by Zend using dlopen and dlsym().
Translation Note: For the module loading process, please refer to A blog by the translator
The header file will also contain some preprocessing to define the information that will be used in the original file.
Source code
Finally, the most important thing you need to create is a simple source code in sample.c Skeleton:
#include "php_sample.h" zend_module_entry sample_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_SAMPLE_EXTNAME, NULL, /* Functions */ NULL, /* MINIT */ NULL, /* MSHUTDOWN */ NULL, /* RINIT */ NULL, /* RSHUTDOWN */ NULL, /* MINFO */ #if ZEND_MODULE_API_NO >= 20010901 PHP_SAMPLE_EXTVER, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_SAMPLE ZEND_GET_MODULE(sample) #endif
It's that simple. These three files are everything you need to create a module skeleton. However, it doesn't have any functionality, but it's a good choice as a template for you to fill in the functionality later in this section. But first let's see what's going on. The first line is very simple. It contains the header file you just created, and by extension, other kernel header files in the php source tree.
Next, create the zend_module_entry structure you defined in the header file. You should notice that the first element of zend_module_entry is a conditional expression, given the current ZEND_MODULE_API_NO definition. This API number is probably php4.2.0, if If you are sure that your extension will not be installed on a version older than this, you can cut off the #ifdef part and include the STANDARD_MODULE_HEADER element directly.
Consider that it will take a little time during compilation anyway, It will not affect the resulting binary or the time required for processing, so in most cases it is best to cut off this condition directly. The same applies to the version attribute below.
The other 6 elements are now initialized Set to NULL; you can see its purpose in the comments after it.
Finally, at the bottom you can see a common element that every PHP extension that can be compiled as a shared module has. This short conditional is added by Zend when you dynamically load a reference. Don't worry about its details, you just need to ensure that it exists, otherwise the next section may not work.
Build your first An extension
Now that you have all the files, it’s time to compile and install. Compared to compiling the main PHP binary, the steps are slightly different.
在*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('sample.so'); 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 '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)!