Heim  >  Artikel  >  Backend-Entwicklung  >  [Übersetzung] [Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 17 – Konfiguration und Verknüpfung des PHP-Quellcodes

[Übersetzung] [Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 17 – Konfiguration und Verknüpfung des PHP-Quellcodes

黄舟
黄舟Original
2017-02-10 10:34:431459Durchsuche


Konfiguration und Verknüpfung

Der gesamte Code in den vorherigen Beispielen ist der, den Sie in verwendet haben PHP-Benutzer Eine eigenständige Version der C-Sprache, die im Bereich codiert wurde. Wenn das Projekt, an dem Sie arbeiten, mit PHP-Erweiterungen verbunden werden muss, müssen Sie mindestens eine externe Bibliothek verknüpfen.

autoconf

In einer einfachen Anwendung haben Sie möglicherweise die folgenden CFLAGS und LDFLAGS zu Ihrem Makefile hinzugefügt.

CFLAGS = ${CFLAGS} -I/usr/local/foobar/include
LDFLAGS = ${LDFLAGS} -lfoobar -L/usr/local/foobar/lib

Personen, die Ihre Anwendung ohne libfoobar erstellen möchten oder libfoobar an einem anderen Ort installiert haben, erhalten eine verarbeitete Fehlermeldung, die ihnen hilft, die Fehlerursache zu finden.

Die meisten Entwicklungs-Quellcode-Software (OSS) und PHP, die in den letzten zehn Jahren entwickelt wurden, verwenden ein Dienstprogramm namens Autoconf, um mithilfe einiger einfacher Makros komplexe Konfigurationsskripts zu generieren. Dieses generierte Skript führt eine Abhängigkeitssuche durch. In der Bibliothek sind Header-Dateien installiert Basierend auf diesen Informationen kann ein Paket die Build-Codezeile anpassen oder eine aussagekräftige Fehlermeldung bereitstellen, bevor Kompilierungszeit verschwendet wird.

Ob Sie dies beim Erstellen einer PHP-Erweiterung planen Geben Sie es für die Öffentlichkeit frei oder nicht, Sie müssen diesen Autoconf-Mechanismus nutzen. Auch wenn Sie bereits mit Autoconf vertraut sind, nehmen Sie sich bitte ein paar Minuten Zeit, um dieses Kapitel zu lesen. PHP stellt einige automatische Funktionen vor, die im Allgemeinen nicht verfügbar sind autoconf. Makros definieren.

Anders als beim herkömmlichen Autoconf-Schritt (die zentralisierte Datei configure.in enthält alle Konfigurationsmakros des Pakets) verwendet PHP nur configure.in, um viele Bitfelder zu verwalten Quellcodebäume. Das kleine config.m4-Skript koordiniert die verschiedenen Erweiterungen, SAPI, den Kern selbst und ZendEngine.

Eine einfache Version von config.m4 haben Sie bereits im vorherigen gesehen Kapitel. Weiter Als nächstes werden wir dieser Datei eine andere Autoconf-Syntax hinzufügen, damit Ihre Erweiterung mehr Informationen zur Konfigurationszeit sammeln kann.

Bibliothekssuche

Das Skript config.m4 wird hauptsächlich verwendet, um zu überprüfen, ob abhängige Bibliotheken wie MySQL, LDAP, GMP und andere Funktionen installiert wurden, die zwischen dem PHP-Benutzerbereich und den C-Bibliothekserweiterungen implementiert werden sollen die Klebeschicht. Wenn ihre abhängigen Bibliotheken nicht installiert sind oder die installierte Version zu alt ist, treten entweder Kompilierungsfehler auf oder die resultierende Binärdatei wird nicht ausgeführt.

Header Scannen

Der einfachste Schritt beim Scannen abhängiger Bibliotheken besteht darin, die Include-Dateien in Ihrem Skript zu überprüfen, die beim Verknüpfen verwendet werden. Der folgende Code versucht, nach allgemeinen Speicherorten zu suchen 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

Die Datei config.m4 ist offensichtlich größer als die, die Sie bisher verwendet haben. Glücklicherweise ist ihre Syntax sehr einfach und leicht zu verstehen Wenn Sie mit Bash-Skripten vertraut sind, werden Sie damit vertraut sein. Die Datei

ist dieselbe wie die, die erstmals in Kapitel 5 „Ihre erste Erweiterung“ erschien, und endet mit „Start with the“. Das Makro PHP_ARG_WITH() verhält sich genauso wie das von Ihnen verwendete Makro PHP_ARG_ENABLE(), führt jedoch dazu, dass die Option ./configure --with-extname/--without-extname anstelle von --enable -extname/- lautet. -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)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn