Skip to content

PHP Extension Develop and Test

yourlovemyall edited this page Oct 25, 2017 · 7 revisions

PHP扩展说明

PHP扩展是php扩展的功能。常见的扩展有哪些呢?

1,mysql

2,redis

3,DES加密等

一般扩展的安装步奏

Linux 编译安装

$ /$path/phpize
$ ./configure + php-cofnig
$ make && make install
$ 添加生成的 ext.so 到 php.ini

不同扩展部署测试

  1. config.m4为配置定义文件,用来读取编译命令。
  2. myext.so为编译后生成的二进制文件。
  3. 代码在myext.c中,编译完成并且加入到php.ini后。
  4. 运行 $ php test.php

那么扩展开发包含哪些部分呢

以下是ext_skel可以帮你生成的基本框架,本节课主要是以ext_skel生成的目录结构解析。

  1. config.m4
  2. php_{extname}.h
  3. {extname}.c

config.m4 主要包含哪些内容?

扩展没有引用外部组件, 则使用enable,否则用with。以下说明都是采用enable的模式,使用with的扩展比如mysql等等

dnl $Id$
dnl config.m4 for extension myext

dnl Comments in this file start with the string 'dnl'.
dnl Remove where necessary. This file will not work
dnl without editing.

dnl If your extension references something external, use with:

dnl PHP_ARG_WITH(myext, for myext support,
dnl Make sure that the comment is aligned:
dnl [  --with-myext             Include myext support])

dnl Otherwise use enable:

PHP_ARG_ENABLE(myext, whether to enable myext support, 
Make sure that the comment is aligned:
[  --enable-myext           Enable myext support])

if test "$PHP_MYEXT" != "no"; then
  dnl Write more examples of tests here...

  dnl # --with-myext -> check with-path
  dnl SEARCH_PATH="/usr/local /usr"     # you might want to change this
  dnl SEARCH_FOR="/include/myext.h"  # you most likely want to change this
  dnl if test -r $PHP_MYEXT/$SEARCH_FOR; then # path given as parameter
  dnl   MYEXT_DIR=$PHP_MYEXT
  dnl else # search default path list
  dnl   AC_MSG_CHECKING([for myext files in default path])
  dnl   for i in $SEARCH_PATH ; do
  dnl     if test -r $i/$SEARCH_FOR; then
  dnl       MYEXT_DIR=$i
  dnl       AC_MSG_RESULT(found in $i)
  dnl     fi
  dnl   done
  dnl fi
  dnl
  dnl if test -z "$MYEXT_DIR"; then
  dnl   AC_MSG_RESULT([not found])
  dnl   AC_MSG_ERROR([Please reinstall the myext distribution])
  dnl fi

  dnl # --with-myext -> add include path
  dnl PHP_ADD_INCLUDE($MYEXT_DIR/include)

  dnl # --with-myext -> check for lib and symbol presence
  dnl LIBNAME=myext # you may want to change this
  dnl LIBSYMBOL=myext # you most likely want to change this 

  dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
  dnl [
  dnl   PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $MYEXT_DIR/lib, MYEXT_SHARED_LIBADD)
  dnl   AC_DEFINE(HAVE_MYEXTLIB,1,[ ])
  dnl ],[
  dnl   AC_MSG_ERROR([wrong myext lib version or lib not found])
  dnl ],[
  dnl   -L$MYEXT_DIR/lib -lm
  dnl ])
  dnl
  dnl PHP_SUBST(MYEXT_SHARED_LIBADD)

  PHP_NEW_EXTENSION(myext, myext.c, $ext_shared)
fi

已上文件内容包含了那些语义?

AC_MSG_CHECKING(message)
在执行 configure 命令时输出“checking <message>”等信息。

AC_MSG_RESULT(value)
取得 AC_MSG_CHECKING 的执行结果,一般情况下 value 应为 yes 或 no。

AC_MSG_ERROR(message)
在执行 configure 命令时输出一条错误消息 message 并中止脚本的执行。

AC_DEFINE(name,value,description)
向 php_config.h 添加一行定义:#define name value // description (这对模块的条件编译很有用。)

AC_ADD_INCLUDE(path)
添加一条编译器的包含路径,比如用于模块需要为头文件添加搜索路径。

AC_ADD_LIBRARY_WITH_PATH(libraryname,librarypath)
指定一个库的连接路径。

AC_ARG_WITH(modulename,description,unconditionaltest,conditionaltest)
这是一款比较强大的宏,用于将模块的描述 description 添加到“configure –help”命令的输出里面。PHP 会检查当前执行的 configure 脚本里面有没有–with-<modulename> 这个选项。 如果有则执行 unconditionaltest 语句(比如 –with-myext=yes 等), 此时,选项的值会被包含在 $withval 变量里面。否则就执行 conditionaltest 语句。

PHP_NEW_EXTENSION(myredis, myredis.c library.c, $ext_shared)

{extname}.c核心代码文件部分

zend_module_entry

zend_module_entry myext_module_entry = {
 #if ZEND_MODULE_API_NO >= 20010901
         STANDARD_MODULE_HEADER,
 #endif
         "myext",
         NULL,   /*const struct _zend_function_entry *functions*/
         PHP_MINIT(myext),
         PHP_MSHUTDOWN(myext),
         PHP_RINIT(myext),               /* Replace with NULL if there's nothing to do at request start */
         PHP_RSHUTDOWN(myext),   /* Replace with NULL if there's nothing to do at request end */
         PHP_MINFO(myext),
 #if ZEND_MODULE_API_NO >= 20010901
         PHP_MYEXT_VERSION,
 #endif
         STANDARD_MODULE_PROPERTIES
};

打开“php_myext.h”,会看到里面有怎么一行:

extern zend_module_entry myext_module_entry;

那么该PHP Extension的源码结构是如何的呢?(Zend/zend_modules.h)

typedef struct _zend_module_entry zend_module_entry;
 
struct _zend_module_entry {
    unsigned short size;
    unsigned int zend_api;
    unsigned char zend_debug;
    unsigned char zts;
    const struct _zend_ini_entry *ini_entry;
    const struct _zend_module_dep *deps;
    const char *name;
    const struct _zend_function_entry *functions;
    int (*module_startup_func)(INIT_FUNC_ARGS);
    int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    int (*request_startup_func)(INIT_FUNC_ARGS);
    int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
    const char *version;
    size_t globals_size;
#ifdef ZTS
    ts_rsrc_id* globals_id_ptr;
#else
    void* globals_ptr;
#endif
    void (*globals_ctor)(void *global TSRMLS_DC);
    void (*globals_dtor)(void *global TSRMLS_DC);
    int (*post_deactivate_func)(void);
    int module_started;
    unsigned char type;
    void *handle;
    int module_number;
    char *build_id;
};

上面生成的结构体中和已上源码结构体是匹配上的。其中有几个指针函数(module_startup_func,module_shutdown_func,request_startup_func,request_shutdown_func),这四个函数会在相应时机被调用,分别是“扩展模块加载时”、“扩展模块卸载时”、“每个请求开始时”和“每个请求结束时”。这四个函数可以看成是一种拦截机制,主要用于相应时机的资源分配、释放等相关操作。 (后面再说)

独立函数demo (自行尝试)

ZEND_FUNCTION(say_hello)
{
    php_printf("Hello World!");
}

static zend_function_entry say_hello_functions[] = {
    ZEND_FE(say_hello, NULL)
    {
        NULL,
        NULL,
        NULL
    }
};

zend_module_entry gglinux_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
     STANDARD_MODULE_HEADER,
#endif
    "say_hello", 
    say_hello_functions, /* Functions */
    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    "2.1",
#endif
    STANDARD_MODULE_PROPERTIES
};

上面我们了解了如何注册一个函数到自定义模块中,那么下面我们该如何传入参数和获取返回值呢?

返回值如何返回

PHP内核定义

#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

以上定义说明

int ht
zval *return_value,我们在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。
zval **return_value_ptr,
zval *this_ptr,如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$this变量差不多。
int return_value_used,代表用户端在调用此函数时有没有使用到它的返回值。

以上return_value这个指针用来保存用户返回的结果值。那么扩展中是如何写的呢?

ZVAL_LONG(return_value, 42);

宏展开后

Z_TYPE_P(return_value) = IS_LONG;
Z_LVAL_P(return_value) = 42;
 
//更彻底的讲,应该是这样的:
return_value->type = IS_LONG;
return_value->value.lval = 42;

return_value有关的宏

return_value如此重要,内核肯定早已经为它准备了大量的宏,来简化我们的操作,提高程序的质量。 在前几章我们接触的宏大多都是以ZVAL_开头的,而接下来我们要介绍的宏的名字是:RETVAL。 再回到上面的那个例子,我们用RETVAL来重写一下:

RETVAL_LONG(42);
// 下面是其他的例子
/*
//这些宏都定义在Zend/zend_API.h文件里
#define RETVAL_RESOURCE(l)              ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b)                  ZVAL_BOOL(return_value, b)
#define RETVAL_NULL()                   ZVAL_NULL(return_value)
#define RETVAL_LONG(l)                  ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d)                ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate)         ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate)     ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING()           ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor)     ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE                    ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE                     ZVAL_BOOL(return_value, 1)
 
#define RETURN_RESOURCE(l)              { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b)                  { RETVAL_BOOL(b); return; }
#define RETURN_NULL()                   { RETVAL_NULL(); return;}
#define RETURN_LONG(l)                  { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d)                { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate)     { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING()           { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor)     { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE                    { RETVAL_FALSE; return; }
#define RETURN_TRUE                     { RETVAL_TRUE; return; }
*/

还有一种特殊的情况,如果返回值未被使用呢? 我们来看看这种情况。

<?php 
function sample_array_range() {
    $ret = array();
    for($i = 0; $i < 1000; $i++) {
        $ret[] = $i;
    }
    return $ret;
}
sample_array_range();

上面这种情况运行了一下 但是并没有将数据返回给调用者,那么就可以使用变量return_value_used来减少内存使用。当然这个例子比较极端。

ZEND_FUNCTION(sample_array_range)
{
    if (return_value_used) {
        int i;
         
        //把返回值初始化成一个PHP语言中的数组
        array_init(return_value);
        for(i = 0; i < 1000; i++)
        {
            //向retrun_value里不断的添加新元素,值为i
            add_next_index_long(return_value, i);
        }
        return;
    }
    else
    {
        //抛出一个E_NOTICE级错误
        php_error_docref(NULL TSRMLS_CC, E_NOTICE,"猫了个咪的,我就知道你没用我的劳动成果!");
        RETURN_NULL();
    }
}

还有其他引用返回等等,感兴趣的可以自行研究一下。扩展函数的返回值已经简单的说完了,那么输入参数如何传入呢?

参数输入获取

那我们举个简单例子, PHP参数输入是通过zend api的zend_parse_parameters函数获取的。

ZEND_FUNCTION(sample_getlong) {
 
    long foo;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"l", &foo) == FAILURE)
    {
        RETURN_NULL();
    }
    php_printf("The integer value of the parameter is: %ld\n", foo);
    RETURN_TRUE;
}

long, string, resource等等都有不同的获取标识符号。string需要两个字段接受,一个是字符串,一个是长度。

PHP扩展的生命周期 (插一句PHP扩展执行的生命周期过程)

PHP_MINIT (扩展模块加载时调用)

PHP_MSHUTDOWN (扩展模块加载时调用)

PHP_RINIT (每个请求开始时)

PHP_RSHUTDOWN (每个请求结束时)

Clone this wiki locally