Configuration et liaison
Tout le code de l'exemple précédent est celui que vous avez utilisé dans le Utilisateur php Une version autonome du langage C qui a été codée dans l'espace. Si le projet sur lequel vous travaillez doit être lié à des extensions PHP, vous devez alors lier au moins une bibliothèque externe.
autoconf
Dans une application simple, vous avez peut-être ajouté les CFLAGS et LDFLAGS suivants à votre Makefile.
CFLAGS = ${CFLAGS} -I/usr/local/foobar/include LDFLAGS = ${LDFLAGS} -lfoobar -L/usr/local/foobar/lib
Les personnes qui souhaitent créer votre application sans libfoobar, ou qui ont installé libfoobar à un autre emplacement, recevront un message d'erreur traité pour les aider à trouver la cause de l'erreur.
La plupart des logiciels de développement de code source (OSS) et PHP développés au cours des dix dernières années utilisent un utilitaire autoconf pour générer des scripts de configuration complexes via quelques macros simples. Ce script généré effectuera une recherche de dépendances. La bibliothèque a des fichiers d'en-tête installés. work Sur la base de ces informations, un package peut personnaliser la ligne de code de construction ou fournir un message d'erreur significatif avant que le temps de compilation ne soit perdu.
Lors de la création d'extensions PHP, si vous envisagez de publier. publiquement ou non, vous devez utiliser ce mécanisme d'autoconf. Même si vous êtes déjà familier avec autoconf, veuillez prendre quelques minutes pour lire ce chapitre. PHP introduit certaines fonctionnalités automatiques qui ne sont pas disponibles dans les macros autoconf couramment installées. .
Différent de l'étape autoconf traditionnelle (le fichier configure.in centralisé contient toutes les macros de configuration du package), PHP utilise uniquement configure.in pour gérer de nombreuses arborescences de code source de champs de bits. Le petit script config.m4 coordonne les différentes extensions, SAPI, le noyau lui-même et ZendEngine.
Vous avez déjà vu une version simple de config.m4 dans le chapitre précédent. , nous ajouterons une autre syntaxe autoconf à ce fichier afin que votre extension puisse collecter plus d'informations sur l'heure de configuration.
Recherche dans la bibliothèque
Le script config.m4 est principalement utilisé pour vérifier si des bibliothèques dépendantes ont été installées. Des extensions telles que mysql, ldap, gmp et d'autres fonctions conçues pour être implémentées entre l'espace utilisateur PHP et les extensions de la bibliothèque C de la couche de colle. Si leurs bibliothèques dépendantes ne sont pas installées ou si la version installée est trop ancienne, soit des erreurs de compilation se produiront, soit le binaire résultant ne s'exécutera pas.
Analyse des en-têtes
L'étape la plus simple dans l'analyse des bibliothèques dépendantes consiste à vérifier les fichiers d'inclusion dans votre script, qui seront utilisés lors de la liaison. Le code suivant tente d'accéder aux emplacements communs pour rechercher 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
Le fichier config.m4 est évidemment plus volumineux que ce que vous avez utilisé jusqu'à présent. Heureusement, sa syntaxe est très simple et facile à comprendre et si vous êtes familier avec les scripts bash. , vous le connaîtrez sans doute. Le fichier
est le même que celui apparu pour la première fois dans le chapitre 5 "Votre première extension", tous deux se terminant par Start with the PHP_ARG_WITH() macro. . Cette macro se comporte de la même manière que la macro PHP_ARG_ENABLE() que vous avez utilisée, mais elle fera en sorte que l'option ./configure soit --with-extname/--without-extname au lieu de --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)!