첫 번째 확장
각 PHP 확장을 구성하려면 최소한 두 개의 파일이 필요합니다. 즉, 컴파일 시간에 빌드할 파일과 필요한 외부 라이브러리를 알려주는 구성 파일, 그리고 최소한 하나의 소스 파일입니다.
프로파일링 확장
사실 일반적으로 두 번째 또는 세 번째 구성 파일과 하나 이상의 헤더 파일이 있습니다. 각 유형의 파일 하나를 추가하고 작업합니다.
구성 파일
시작하려면 먼저 PHP 소스 트리의 ext/ 디렉터리에서 실제로는 이 새 디렉터리를 만듭니다. 디렉토리는 어디에나 배치할 수 있지만 이 장의 뒷부분에서 win32 및 정적 빌드 옵션을 보여주기 위해 먼저 소스 코드 디렉토리에 생성하겠습니다.
다음 단계에서 이 디렉토리를 입력하고 config라는 파일을 생성합니다. .m4에 다음 내용을 입력하세요:
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
이는 ./configure 시 활성화 샘플 옵션을 호출할 수 있는 최소 요구 사항입니다. PHP_ARG_ENABLE의 두 번째 매개 변수는 확장 구성 파일이 다음과 같을 때 표시됩니다. ./configure 처리 중에 도달했습니다. 터미널 사용자가 ./configure --help를 실행할 때 세 번째 매개변수가 도움말 메시지로 표시됩니다.
왜 일부 확장에서는 활성화-extname을 사용하는지 궁금한 적이 있습니까? 확장 프로그램은 with-extname을 사용합니까? 실제로 활성화는 이 기능을 활성화하면 다른 타사 라이브러리가 필요하지 않음을 의미하며, 기능에는 다른 전제 조건이 있습니다
이제 샘플 확장은 다른 라이브러리와 링크할 필요가 없으므로 활성화 버전만 사용하면 됩니다. 17장 "외부 라이브러리"에서는 with 및 표시 사용을 소개합니다. 컴파일러는 추가 CFLAGS 및 LDFLAGS 설정.
최종 사용자가 활성화-샘플 옵션을 사용하여 ./configure를 호출하면 로컬 환경 변수 $PHP_SAMPLE이 yes로 설정됩니다. PHP_SUBST()는 표준 autoconf PHP 수정 버전입니다. 공유 모듈로 확장을 구축할 때 필요한 AC_SUBST() 매크로.
마지막으로, PHP_NEW_EXTENSION()은 모듈을 정의하고 확장으로 사용해야 하는 모든 모듈을 열거합니다. 컴파일된 소스 파일의 일부입니다. 여러 파일이 필요한 경우 두 번째 매개변수에 공백을 사용하여 나열할 수 있습니다. 예:
PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)
마지막 매개변수는 빌드 공유의 PHP_SUBST(SAMPLE_SHARED_LIBADD) 명령에 해당합니다. 모듈에도 필요합니다.
헤더 파일
C로 개발할 때 데이터 유형 정의를 외부 헤더 파일로 분리하여 소스 파일에 포함시키는 것이 일반적입니다. 그러나 PHP에서는 이를 요구하지 않습니다. 모듈이 단일 소스 파일에 배치할 수 없을 정도로 커지면 작업이 단순화됩니다.
php_sample.h 헤더 파일에서 다음 내용으로 시작하세요.
#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 */
이 헤더 파일은 두 가지 주요 작업을 완료합니다. 확장이 phpize 도구(이 방법은 이 책에서 일반적으로 사용됨)를 사용하여 빌드된 경우 HAVE_CONFG_H가 정의되므로 확장이 어떻게 컴파일되든 상관없이 config.h가 정상적으로 사용됩니다. .h는 PHP 소스 트리에서 포함됩니다. 이 헤더 파일에는 대부분의 PHP API에 액세스하기 위해 PHP 소스 코드에 사용되는 다른 헤더 파일이 포함되어 있습니다.
다음으로 확장에 사용되는 zend_module_entry 구조가 정의됩니다. 확장자=xxx를 사용하여 이 모듈을 로드하면 dlopen 및 dlsym()을 사용하여 Zend에서 가져올 수 있습니다.
번역 참고: 모듈 로딩 프로세스는 다음 블로그를 참조하세요. 번역기
헤더 파일에는 다음도 포함됩니다. 원본 파일에 사용될 정보를 정의하는 전처리 과정을 거칩니다.
소스 코드
마지막으로 가장 중요한 것은 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
이렇게 간단합니다. 이 세 파일은 모듈 뼈대를 만드는 데 필요한 모든 것입니다. 그러나 기능은 없지만 기능을 채우기 위한 템플릿으로 좋은 선택입니다. 하지만 먼저 무슨 일이 일어나는지 살펴보겠습니다.
으로 시작하는 줄은 방금 생성한 헤더 파일과 확장하여 PHP 소스 트리의 다른 커널 헤더 파일을 포함합니다. 🎜>
다음으로, 헤더 파일에 정의한 zend_module_entry 구조를 생성합니다. 현재 ZEND_MODULE_API_NO 정의를 고려하면 이 API 번호는 아마도 php4.2.0일 것입니다. 이보다 이전 버전에 확장 프로그램이 설치되지 않을 것이라고 확신한다면 #ifdef 부분을 잘라내고 STANDARD_MODULE_HEADER 요소를 직접 포함할 수 있습니다. 어쨌든 컴파일하는 동안 약간의 시간이 걸릴 것이라는 점을 고려하세요. 결과 바이너리나 처리에 필요한 시간에 영향을 미치지 않으므로 대부분의 경우 이 조건을 직접 차단하는 것이 가장 좋습니다. 나머지 6개 요소는 이제 초기화됩니다. 마지막으로 공유 모듈로 컴파일할 수 있는 모든 PHP 확장에 포함된 공통 요소를 볼 수 있습니다. 참조를 동적으로 로드할 때 참조가 존재하는지 확인하기만 하면 됩니다. 그렇지 않으면 다음 섹션이 작동하지 않을 수 있습니다. 첫 번째 확장 프로그램을 빌드하세요 이제 모든 파일이 준비되었으므로 컴파일하고 설치할 차례입니다. 기본 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('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中文网(m.sbmmt.com)!