設定とリンク
実行中のプロジェクトを貼り付ける必要がある場合、前の例のすべてのコードは、PHP ユーザー空間にコードを記述した C 言語の独立したバージョンです。 php 拡張機能の組み合わせを使用するには、少なくとも 1 つの外部ライブラリをリンクする必要があります。
autoconf
単純なアプリケーションでは、次の CFLAGS と LDFLAGS を Makefile に追加している可能性があります。
CFLAGS = ${CFLAGS} -I/usr/local/foobar/include LDFLAGS = ${LDFLAGS} -lfoobar -L/usr/local/foobar/lib
libfoobar を使用せずにアプリケーションを構築したい人、または libfoobar を別の場所にインストールした人は、エラーの原因を見つけるのに役立つ処理されたエラー メッセージを受け取ります
過去 10 年間のほとんどの開発ソース コード ソフトウェア (OSS) ) と PHP は、ユーティリティ ツール autoconf を使用して、いくつかの単純なマクロを通じて複雑な構成スクリプトを生成します。生成されたスクリプトは、この情報に基づいて、パッケージのビルド行をカスタマイズできます。コードを追加するか、コンパイル時間が無駄になる前に意味のあるエラー メッセージを提供してください。
autoconf メカニズムをすでによく知っている場合でも、php 拡張機能を構築するときは、この autoconf を活用する必要があります。この章を読むのに数分かかります。PHP では、一般的にインストールされている autoconf
と従来の autoconf 手順 (パッケージのすべての構成マクロが含まれています) では使用できないいくつかのカスタム マクロを紹介します。さまざまな拡張機能、SAPI、コア自体、ZendEngine を含む、多くのビットフィールド ソース ツリーの下で小さな config.m4 スクリプトの調整を管理するために、configure.in のみを使用します。
次に、拡張機能がより多くの設定時情報を収集できるように、このファイルに他の autoconf 構文を追加します。 find
config.m4 スクリプトは、主に次のことを確認するために使用されます。 mysql、ldap、gmp などの拡張機能は、依存ライブラリがインストールされていない場合、またはインストールされているバージョンが C ライブラリによって実装されている他の機能との間の接着層として設計されています。古すぎると、コンパイル エラーが発生するか、生成されたバイナリが実行できなくなります。
ヘッダー ファイル スキャン
は、依存ライブラリをスキャンするのに最も効果的です。簡単な手順は、以下のコードは、いくつかの一般的な場所で zlib.h を見つけようとします:
PHP_ARG_WITH(zlib,[for zlib Support] [ with-zlib Include ZLIB Support]) if test "$PHP_ZLIB" != "no"; then for i in /usr /usr/local /opt; do if test -f $i/include/zlib/zlib.h; then ZLIB_DIR=$i fi done if test -z "$ZLIB_DIR"; then AC_MSG_ERROR([zlib not installed (http://www.php.cn/)]) fi PHP_ADD_LIBRARY_WITH_PATH(z,$ZLIB_DIR/lib, ZLIB_SHARED_LIBADD) PHP_ADD_INCLUDE($ZLIB_DIR/include) AC_MSG_RESULT([found in $ZLIB_DIR]) AC_DEFINE(HAVE_ZLIB,1,[libz found and included]) PHP_NEW_EXTENSION(zlib, zlib.c, $ext_shared) PHP_SUBST(ZLIB_SHARED_LIBADD) fi
config.m4 ファイルは、これまで使用してきたファイルよりも明らかに優れています。 、その構文は非常にシンプルで理解しやすく、bash スクリプトに慣れている場合は、
ファイルと第 5 章「最初の拡張機能」の最初のファイルから始まると、毎回同じことが起こります。 PHP_ARG_WITH() マクロを使用すると、このマクロの動作は、使用した PHP_ARG_ENABLE() マクロと同じですが、./configure オプションが - ではなく --with-extname/--without-extname になります。 -enable-extname/--disable-extname.
回顾这些宏, 它们的功能是等同的, 不同仅在于是否让终端用户给你的包一些暗示. 你可以在自己创建的私有扩展上使用任意一种方式. 不过, 如果你计划公开发布, 那就应该知道php正式的编码标准, 它指出enable/disable用于哪些不需要链接外部库的扩展, with/without则反之.
由于我们这里假设的扩展将链接zlib库, 因此你的config.m4脚本要以查找扩展源代码中将包含的zlib.h头文件. 这通过检查一些标准位置/usr, /usr/local, /opt中include/zlib目录下的zlib.h完成对其下两个目录的定位.
如果找到了zlib.h, 则将基路径设置到临时变量ZLIB_DIR中. 一旦循环完成, config.m4脚本检查ZLIB_DIR是否包含内容来确定是否找到了zlib.h. 如果没有找到, 则产生一个有意义的错误让用户知道./configure不能继续.
此刻, 脚本假设头文件存在, 对应的库也必须存在, 因此在下面的两行使用它修改构建环境, 最终增加-lz -L$ZLIB_DIR/lib到LDFLAGS以及-I$ZLIB_DIR/include到CFLAGS.
最终, 输出一个确认消息指示zlib安装已经找到, 并且在编译期间使用它的路径. config.m4的其他部分从前面部分的学习中你应该已经熟悉了. 为config.h定义一个#define, 定义扩展并指定它的源代码文件, 同时标识一个变量完成将扩展附加到构建系统的工作.
功能测试
迄今为止, 这个config.m4示例指示查找了需要的头文件. 尽管这已经够用了, 但它仍然不能确保产生的二进制正确的进行链接, 因为可能不存在匹配的库文件, 或者版本不正确.
最简单的检查zlib.h对应的libz.so库文件是否存在的方式就是检查文件是否存在:
if ! test -f $ZLIB_DIR/lib/libz.so; then AC_MSG_ERROR([zlib.h found, but libz.so not present!]) fi
当然, 这仅仅是问题的一面. 如果安装了其他的同名库但和你要查找的库不兼容怎么办呢? 确保你的扩展可以成功编译的最好方式是测试找到的库实际编译所需的内容. 要这样做就需要在config.m4中PHP_ADD_LIBRARY_WITH_PATH调用之前加入下面代码:
PHP_CHECK_LIBRARY(z, deflateInit,,[ AC_MSG_ERROR([Invalid zlib extension, gzInit() not found]) ],-L$ZLIB_DIR/lib)
这个工具宏将展开输出一个完整的程序, ./configure将尝试编译它. 如果编译成功, 表示第二个参数定义的符号在第一个参数指定的库中存在. 成功后, 第三个参数中指定的autoconf脚本将会执行; 失败后, 第四个参数中指定的autoconf脚本将执行. 在这个例子中, 第三个参数为空, 因为没有消息就是最好的消息(译注: 应该是unix哲学之一), 第五个参数也就是左后一个参数, 用于指定额外的编译器和链接器标记, 这里, 使用-L致命了一个额外的用于查找库的路径.
可选功能
那么现在你已经有正确的库和头文件了, 但依赖的是所安装库的哪个版本呢? 你可能需要某些功能或排斥某些功能. 由于这种类型的变更通常涉及到某些特定入口点的增加或删除, 因此可以重用PHP_CHECK_LIBRARY()宏来检查库的某些能力.
PHP_CHECK_LIBRARY(z, gzgets,[ AC_DEFINE(HAVE_ZLIB_GETS,1,[Having gzgets indicates zlib >= 1.0.9]) ],[ AC_MSG_WARN([zlib < 1.0.9 installed, gzgets() will not be available]) ],-L$ZLIB_DIR/lib)
测试实际行为
可能知道某个符号存在也还不能确保你的代码正确编译; 某些库的特定版本可能存在bug需要运行一些测试代码进行检查.
AC_TRY_RUN()宏可以编译一个小的源代码文件为可执行程序并执行. 依赖于传回给./configure的返回代码, 你的脚本可以设置可选的#define语句或直接输出消息(比如如果bug导致不能工作则提示升级)安全退出. 考虑下面的代码(摘自ext/standard/config.m4):
AC_TRY_RUN([ #include <math.h> double somefn(double n) { return floor(n*pow(10,2) + 0.5); } int main() { return somefn(0.045)/10.0 != 0.5; } ],[ PHP_ROUND_FUZZ=0.5 AC_MSG_RESULT(yes) ],[ PHP_ROUND_FUZZ=0.50000000001 AC_MSG_RESULT(no) ],[ PHP_ROUND_FUZZ=0.50000000001 AC_MSG_RESULT(cross compile) ]) AC_DEFINE_UNQUOTED(PHP_ROUND_FUZZ, $PHP_ROUND_FUZZ, [Is double precision imprecise?])
你可以看到, AC_TRY_RUN()的第一个参数是一块C语言代码, 它将被编译执行. 如果这段代码的退出代码是0, 位于第二个参数的autoconf脚本将被执行, 这种情况标识round()函数和期望一样工作, 返回0.5.
如果代码块返回非0值, 位域第三个参数的autoconf脚本将被执行. 第四个参数(最后一个)在php交叉编译时使用. 这种情况下, 尝试运行示例代码是没有意义的, 因为目标平台不同于扩展编译时使用的平台.
强制模块依赖
在php 5.1中, 扩展之间的内部依赖是可以强制性的. 由于扩展可以静态构建到php中, 也可以构建为共享对象动态加载, 因此强制依赖需要在两个地方实现.
配置时模块依赖
第一个位置是你在本章课程中刚刚看到的config.m4文件中. 你可以使用PHP_ADD_EXTENSION_DEP(extname, depname[ , optional])宏标识extname这个扩展依赖于depname这个扩展. 当extname以静态方式构建到php中时, ./configure脚本将使用这一行代码确认depname必须首先初始化. optional参数是一个标记, 用来标识depname如果也是静态构建的, 应该在extname之前加载, 不过它并不是必须的依赖.
这个宏的一个使用示例是pdo驱动, 比如pdo_mysql是可预知依赖于pdo扩展的:
ifdef([PHP_ADD_EXTENDION_DEP], [ PHP_ADD_EXTENSION_DEP(pdo_mysql, pdo) ])
要注意PHP_ADD_EXTENSION_DEP()宏被包裹到一个ifdef()结构中. 这是因为pdo和它的驱动在编译大于或等于5.0版本的php时都是存在的, 然而PHP_ADD_EXTENSION_DEP()宏是直到5.1.0版本才出现的.
运行时模块依赖
另外一个你需要注册依赖的地方是zend_module_entry结构体中. 考虑下面第5章中你定义的zend_module_entry结构体:
zend_module_entry sample_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_SAMPLE_EXTNAME, php_sample_functions, NULL, /* MINIT */ NULL, /* MSHUTDOWN */ NULL, /* RINIT */ NULL, /* RSHUTDOWN */ NULL, /* MINFO */ #if ZEND_MODULE_API_NO >= 20010901 PHP_SAMPLE_EXTVER, #endif STANDARD_MODULE_PROPERTIES };
增加运行时模块依赖信息就需要对STANDARD_MOUDLE_HEADER部分进行一些小修改:
zend_module_entry sample_module_entry = { #if ZEND_MODULE_API_NO >= 220050617 STANDARD_MODULE_HEADER_EX, NULL, php_sample_deps, #elif ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_SAMPLE_EXTNAME, php_sample_functions, NULL, /* MINIT */ NULL, /* MSHUTDOWN */ NULL, /* RINIT */ NULL, /* RSHUTDOWN */ NULL, /* MINFO */ #if ZEND_MODULE_API_NO >= 20010901 PHP_SAMPLE_EXTVER, #endif STANDARD_MODULE_PROPERTIES };
现在, 如果ZEND_MODULE_API_NO高于php 5.1.0 beta发布版, 则STANDARD_MODULE_HEADER(译注: 这里原著笔误为STANDARD_MODULE_PROPERTIES)将被替换为略微复杂的结构, 它将包含一个指向模块依赖信息的引用.
这个目标结构体可以在你的zend_module_entry结构体上面定义如下:
#if ZEND_MODULE_API_NO >= 220050617 static zend_module_dep php_sample_deps[] = { ZEND_MODULE_REQUIRED("zlib") {NULL,NULL,NULL} }; #endif
和zend_function_entry向量类似, 这个列表可以有多项依赖, 按照顺序进行检查. 如果尝试加载某个依赖模块未满足, Zend将会中断加载, 报告不满足依赖的名字, 这样, 终端用户就可以通过首先加载其他模块来解决问题.
Windows方言
由于译者对windows环境不熟悉, 因此略过本节.
小结
如果你的扩展将在未知或不可控制的环境构建, 让它足够聪明以应付奇怪的环境就非常重要. 使用php提供的unix和windows上强有力的脚本能力, 你应该可以检测到麻烦并在未知的管理员需要电话求助之前给于她一个解决方案.
现在你已经有使用php api从头建立php扩展的基础能力了, 你可以准备学习一下使用php提供的扩展开发工具把自己从繁重的重复劳动中解放出来了, 使用它们可以快速, 准确的建立新扩展的原型.
以上就是[翻译][php扩展开发和嵌入式]第17章-php源代码的配置和链接的内容,更多相关内容请关注PHP中文网(www.php.cn)!