PHP经验分享PHP那些事儿

实现一个PHP的C扩展 [入门篇]

2019-07-19  本文已影响3人  laotoutou

最近想尝试写PHP的C扩展,在《PHP7底层设计与源码实现》书中有一个PHP扩展的例子,这里动手实现了一下。

准备

Linux
PHP7
PHP7源码包 点击下载

预览

Linux查看文本行数可以使用wc -l查看,现在需要将这一功能实现为PHP的自带功能。


假如有这样一个文本,中间有空行,要分析该文本行数,有些时候需要让函数输出3,有些时候让函数输出7(包括空行),可以通过配置php.ini文件保存这一配置。
然后在PHP中这样使用就好了:
开始
  1. 首先使用PHP7源码包中的ext_skel工具生成扩展的基本框架,进入源码包目录执行./ext_skel --extname=wcl,之后会在ext_skel同目录下生成一个wcl文件夹,其中wcl.c是扩展的主要源文件,php_wcl.h是头文件。
  2. 编辑config.m4,去掉PHP_ARG_ENABLE和[--enable-wcl]的注释dnl,我这边生成的config.m4默认是下面的样子,就不用改了。


  3. 编辑wcl.c文件:


/* Every user-visible function in PHP should document itself in the source */
/* {{{ proto string confirm_wcl_compiled(string arg)
   Return a string to confirm that the module is compiled in */
PHP_FUNCTION(wcl)
{
    size_t arg_len, len;

    int argc = ZEND_NUM_ARGS();
    char *arg = NULL;
    if (zend_parse_parameters(argc, "s", &arg, &arg_len) == FAILURE) {
        return;
    }

//  zend_string *strg;
//  strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "wcl", arg);
//  RETURN_STR(strg);

    FILE *fp;
    if ((fp = fopen(arg, "r")) == NULL) {
        RETURN_FALSE;
    }

    char ch, pre = '\n';
    zend_long lcount = 0;
    while ((ch = fgetc(fp)) != EOF) {
        if (ch == '\n') {
            lcount++;
        }
        pre = ch;
    }

    fclose(fp);
    RETURN_LONG(lcount);
}
/* }}} */



/* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(wcl)
{
#if defined(COMPILE_DL_WCL) && defined(ZTS)
    ZEND_TSRMLS_CACHE_UPDATE();
#endif
    return SUCCESS;
}
/* }}} */


/* {{{ PHP_MINFO_FUNCTION
这里是执行phpinfo()函数的输出
 */
PHP_MINFO_FUNCTION(wcl)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "wcl support", "enabled");
    php_info_print_table_end();

    /* Remove comments if you have entries in php.ini
    DISPLAY_INI_ENTRIES();
    */
}
/* }}} */

/* {{{ wcl_functions[]
 *
 * Every user visible function must have an entry in wcl_functions[].
 */
const zend_function_entry wcl_functions[] = {
    PHP_FE(wcl, NULL)       /* 注册上面定义的wcl函数*/
    PHP_FE_END  /* Must be the last line in wcl_functions[] */
};
/* }}} */

/* {{{ wcl_module_entry
 */
zend_module_entry wcl_module_entry = {
    STANDARD_MODULE_HEADER,
    "wcl",
    wcl_functions,
    PHP_MINIT(wcl),
    PHP_MSHUTDOWN(wcl),
    PHP_RINIT(wcl),     /* Replace with NULL if there's nothing to do at request start */
    PHP_RSHUTDOWN(wcl), /* Replace with NULL if there's nothing to do at request end */
    PHP_MINFO(wcl),
    PHP_WCL_VERSION,
    STANDARD_MODULE_PROPERTIES
};
/* }}} */

计数功能主要在wcl函数中实现,无法打开文件返回false,然后按文件内容判断为换行符,计数器+1,最后返回文件行数。

  1. 之前说过对于前面的test.txt(包含多个空行),有时我们需要让函数不对空行计数,有时又需要,因此需要加载php.ini文件,将对应配置放到配置文件中由扩展加载。
    编辑php_wcl.h文件:
/*
    Declare any global variables you may need between the BEGIN
    and END macros here:
*/
ZEND_BEGIN_MODULE_GLOBALS(wcl)
    // filter_blank变量表示是否过滤空行,声明扩展内的全局变量
    zend_long  filter_blank;
//  char *global_string;
ZEND_END_MODULE_GLOBALS(wcl)
  1. 编辑wcl.c文件
    添加配置项:
/* If you declare any globals in php_wcl.h uncomment this:*/
ZEND_DECLARE_MODULE_GLOBALS(wcl)

// Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()
    // filter_blank变量赋默认值0
    STD_PHP_INI_ENTRY("wcl.filter_blank",      "0", PHP_INI_ALL, OnUpdateBool, filter_blank, zend_wcl_globals, wcl_globals)
PHP_INI_END()
/* }}} */

wcl函数做如下修改,使用WCL_G函数获取php.ini中对应配置项的值

    char ch, pre = '\n';
    zend_long lcount = 0;
    while ((ch = fgetc(fp)) != EOF) {
        if (ch == '\n') {
            // filter_blank in php.ini
            if (WCL_G(filter_blank) && pre == ch) {
                continue;
            }
            lcount++;
        }
        pre = ch;
    }
  1. 由于增加了配置项,现在要在扩展启动和销毁时对配置项做相应操作:
/* {{{ PHP_MINIT_FUNCTION
    配置项的注册在该阶段完成
 */
PHP_MINIT_FUNCTION(wcl)
{
    /* If you have INI entries, uncomment these lines */
    REGISTER_INI_ENTRIES();

    return SUCCESS;
}
/* }}} */


PHP_MSHUTDOWN_FUNCTION(wcl)
{
    /* uncomment this line if you have INI entries*/
    UNREGISTER_INI_ENTRIES();

    return SUCCESS;
}
  1. 编译生成动态链接.so文件
    phpize命令需要安装php-dev
phpize
./configure
sudo make
sudo make install
  1. 配置php.ini
    执行上述过程不报错那么扩展对应的目录下会生成wcl.so文件,编辑php.ini
extension=wcl.so

[Wcl]
wcl.filter_blank = 1

到这里wcl扩展就完成了,可以重启fpm然后直接使用wcl(filename)来测试输出。

再多bb两句,php启动常见两种方式:cli和fpm,如果配置的是fpm的php.ini文件,那么修改php.ini后需要重启fpm,由此可见php.ini是在fpm启动时候加载的,参见 https://www.php.net/manual/zh/configuration.file.php
关于PHP请求以及PHP扩展的生命周期参见https://www.cnblogs.com/beatzeus/archive/2016/11/16/6071902.html

上一篇下一篇

猜你喜欢

热点阅读