美文网首页php之道家居万能侠
PHP: 编写第一个PHP扩展

PHP: 编写第一个PHP扩展

作者: 麦兜菠萝油王子 | 来源:发表于2017-12-04 20:01 被阅读0次

    今天看了一些PHP扩展的入门文章,很好的文章,但看的还是很吃力。主要是因为不了解文章里说的生命周期,内存分配,SAPI这些概念。然后代码里全是宏,如果不查源码,和看天书没什么区别。所以想记录一下从源码的角度分析 hello.c 这个文件的过程,其它文章里有的我就不重复了,我先放代码,这里有一个部分和原文章不一样。

    随便新建一个文件夹,里面包含下面3个文件:

    config.m4

    PHP_ARG_ENABLE(hello, whether to enable Hello
    World support,
    [ --enable-hello   Enable Hello World support])
    if test "$PHP_HELLO" = "yes"; then
      AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
      PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
    fi
    

    php_hello.h

    #ifndef PHP_HELLO_H
    #define PHP_HELLO_H 1
    #define PHP_HELLO_WORLD_VERSION "1.0"
    #define PHP_HELLO_WORLD_EXTNAME "hello"
    
    PHP_FUNCTION(hello_world);
    
    extern zend_module_entry hello_module_entry;
    #define phpext_hello_ptr &hello_module_entry
    
    #endif
    

    hello.c

    #ifdef HAVE_CONFIG_H
    #include "config.h"
    #endif
    #include "php.h"
    #include "php_hello.h"
    
    // 模块所包含的函数列表信息
    static zend_function_entry hello_functions[] = {
        PHP_FE(hello_world, NULL)
        {NULL, NULL, NULL}
    };
    
    // 模块自身的相关信息
    // 如模块名,模块包含的函数,生命周期,版本号等
    zend_module_entry hello_module_entry = {
    #if ZEND_MODULE_API_NO >= 20010901
        STANDARD_MODULE_HEADER,
    #endif
        PHP_HELLO_WORLD_EXTNAME,
        hello_functions,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
    #if ZEND_MODULE_API_NO >= 20010901
        PHP_HELLO_WORLD_VERSION,
    #endif
        STANDARD_MODULE_PROPERTIES
    };
    
    // 与动态加载有关 Dynamic Loading,后面解释
    #ifdef COMPILE_DL_HELLO
    ZEND_GET_MODULE(hello)
    #endif
    
    // 你的扩展函数
    PHP_FUNCTION(hello_world)
    {
        RETURN_STRING("Hello World", 1);
    }
    

    运行下面的命令可以验证扩展。

    phpize
    ./configure
    make
    php -dextension=modules/hello.so -r "echo hello_world();"
    

    和原文不同的是这里使用 zend_function_entry 而不是 function_entry。这个和 PHP 的版本有关,不然编译会出错。

    我们先看这一段代码

    static zend_function_entry hello_functions[] = {
        PHP_FE(hello_world, NULL)
        {NULL, NULL, NULL}
    };
    

    如果你查看 PHP 官方文档,也有可能会这样写。

    static zend_function_entry hello_functions[] = {
        PHP_FE(hello_world, NULL)
        ZEND_FE_END
    };
    

    其实说到底,把这些你看不懂的宏的定义找出来,就好了,我们就先找 zend_function_entry,因为我的源码在下面这个路径下,所以我先 cd 过去。

    /usr/local/Cellar/php56/5.6.32_8/include/php
    

    然后用下面的命令找 zend_function_entry 的定义。

    grep -rnw . -e 'zend_function_entry'
    

    发现在 zend_API.h 这个文件。

    ./Zend/zend_API.h:41:} zend_function_entry;
    

    源码是一个结构体,用到存储函数的信息。

    typedef struct _zend_function_entry {
            const char *fname;
            void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
            const struct _zend_arg_info *arg_info;
            zend_uint num_args;
            zend_uint flags;
    } zend_function_entry;
    

    接着再看 PHP_FE,同理使用下面命令。

    grep -rnw . -e 'PHP_FE'
    

    得到下面的结果。

    ./main/php.h:352:#define PHP_FE         ZEND_FE
    

    再找 ZEND_FE,下面就不写了,最后查到是这个定义。

    #define ZEND_FENTRY(zend_name, name, arg_info, flags)   { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeo
    f(struct _zend_arg_info)-1), flags }, 
    #define ZEND_FE(name, arg_info)                                         ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
    

    其实就是一个关于函数结构体的信息,和之前的 zend_function_entry 对应。

    再看 zend_module_entry 这一段代码,其实做法也是参照上面的,#if ZEND_MODULE_API_NO >= 20010901 是用来判断 api 版本是否大于等于 20010901 (这个是年月日),如果大于则在编译期包含 STANDARD_MODULE_HEADER 这个宏,这些信息都可以在下面这个源文件找到,ZEND_MODULE_API_NO 这个宏也是定义这个文件中的。

    zend_modules.h
    

    最后说说这个

    // 与动态加载有关 Dynamic Loading,后面解释
    #ifdef COMPILE_DL_HELLO
    ZEND_GET_MODULE(hello)
    #endif
    

    其实我也不是很懂,查了 ZEND_GET_MODULE 这个宏,他是一个函数,用于在运行时供 Zend 引擎获取模块的名字。今天大概就到这里,下次再写函数带数的情况。

    相关文章

      网友评论

        本文标题:PHP: 编写第一个PHP扩展

        本文链接:https://www.haomeiwen.com/subject/ivmtixtx.html