Home  >  Article  >  Backend Development  >  [Translation] [php extension development and embedded] Chapter 17 - Configuration and linking of php source code

[Translation] [php extension development and embedded] Chapter 17 - Configuration and linking of php source code

黄舟
黄舟Original
2017-02-10 10:34:431447browse


Configuration and linking

All the codes in the previous examples are what you have used in the php user A stand-alone version of the C language that has been coded in the space. If the project you are working on needs to be glued to the PHP extension, then you need to link at least one external library.

autoconf

In a simple application, you may have added the following CFLAGS and LDFLAGS to your Makefile.

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

People who want to build your application without libfoobar, or who install libfoobar to another location, will get a processed error message to help them find the cause of the error.

Most development source code software (OSS) and PHP developed in the past ten years use a utility tool autoconf to generate complex configure scripts through some simple macros. This generated script will execute the search for dependent libraries. Whether the header file installed works. Based on this information, a package can customize the build code line, or provide a meaningful error message before compilation time is wasted.

During the build When extending PHP, whether you plan to release it to the public or not, you need to make use of this autoconf mechanism. Even if you are already familiar with autoconf, please take a few minutes to read this chapter. PHP introduces some custom macros that are not commonly installed in autoconf. .

Different from the traditional autoconf steps (the centralized configure.in file contains all the configuration macros of the package), PHP only uses configure.in to manage many small bit-field source code trees. Coordination of the config.m4 script, including various extensions, SAPI, the core itself, and ZendEngine.

You have seen a simple version of config.m4 in the previous chapter. Next, We will add other autoconf syntax to this file so that your extension can collect more configuration-time information.

Library search

The config.m4 script is mostly used to check whether dependent libraries have been installed. Extensions such as mysql, ldap, gmp and other functions designed to be a glue between the PHP user space and the C library. Layer extensions. If their dependent libraries are not installed, or the installed version is too old, either a compilation error will occur, or the resulting binary will not run.

Header file Scan

The simplest step in scanning dependencies is to check the include files in your script, which will be used when linking. The following code attempts to scan in some common places Look for 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

The config.m4 file is obviously larger than what you have used so far. Fortunately, its syntax is very simple and easy to understand if you are familiar with bash Scripts are no stranger to it. The

file is the same as the one that first appeared in Chapter 5 "Your First Extension", both ending with the PHP_ARG_WITH() macro Start. This macro behaves the same as the PHP_ARG_ENABLE() macro you used, but it will cause the ./configure option to be --with-extname/--without-extname instead of --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)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn