• PHP扩展开发--02.包裹第三方的扩展


    背景

    也许最常见的PHP扩展是那些包裹第三方C库的扩展。这些扩展包括MySQL或Oracle的数据库服务库,libxml2的 XML技术库,ImageMagick 或GD的图形操纵库。

    在本节中,我们编写一个扩展,同样使用脚本来生成骨架扩展,因为这能节省许多工作量。这个扩展包裹了标准C函数fopen(), fclose(), fread(), fwrite()和 feof().

    环境搭建

    利用ext_skel脚本在ext./ 原代码目录执行下面的命令:

    $./ext_skel --extname=myfile --proto=myfile.def
    

    添加扩展函数

    修改头文件 php_myfile.h,添加对外接口

    PHP_FUNCTION(file_open);
    PHP_FUNCTION(file_eof);
    PHP_FUNCTION(file_close);
    PHP_FUNCTION(file_read);
    PHP_FUNCTION(file_write);
    

    修改myfile.c

    //myfile_functions 添加对外接口信息
    const zend_function_entry myfile_functions[] = {
        PHP_FE(file_open, NULL)
        PHP_FE(file_eof, NULL)
        PHP_FE(file_close, NULL)
        PHP_FE(file_read, NULL)
        PHP_FE(file_write,  NULL)
        {NULL, NULL, NULL}  /* Must be the last line in myfile_functions[] */
    };
    //在文件底部添加我们要实现的函数
    //打开文件
    PHP_FUNCTION(file_open){
        char *filename = NULL;
        char *mode = NULL;
        int argc = ZEND_NUM_ARGS();
        int filename_len;
        int mode_len;
        FILE *fp;
        if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename,&filename_len, &mode, &mode_len) == FAILURE) {
             return;
        }
        fp = VCWD_FOPEN(filename, mode);
        if (fp == NULL) {
            RETURN_FALSE;
        }
        ZEND_REGISTER_RESOURCE(return_value, fp, le_myfile);
    }
    //测试文件指针是否到了文件结束的位置
    PHP_FUNCTION(file_eof){
        int argc = ZEND_NUM_ARGS();
        zval *filehandle = NULL;
        FILE *fp;
        if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) ==FAILURE) {
            return;
        }
        ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c-file",le_myfile);
        if (fp == NULL || feof(fp) > 0){
            RETURN_TRUE;
        }
        RETURN_FALSE;
    }
    //删除文件
    PHP_FUNCTION(file_close){
        int argc = ZEND_NUM_ARGS();
        zval *filehandle = NULL;
        if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) {
            return;
        }
        if (zend_list_delete(Z_RESVAL_P(filehandle)) == FAILURE) {
            RETURN_FALSE;
        }
        RETURN_TRUE;
    }
    //读取文件
    PHP_FUNCTION(file_read){
        int argc = ZEND_NUM_ARGS();
        long size;
        zval *filehandle = NULL;
        FILE *fp;
        char *result;
        size_t bytes_read;
        if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle,&size) == FAILURE) {
            return;
        }
        ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
        result = (char *) emalloc(size+1);
        bytes_read = fread(result, 1, size, fp);
        result[bytes_read] = '';
        RETURN_STRING(result, 0);
    }
    //写入文件
    PHP_FUNCTION(file_write){
        char *buffer = NULL;
        int argc = ZEND_NUM_ARGS();
        int buffer_len;
        zval *filehandle = NULL;
        FILE *fp;
        if (zend_parse_parameters(argc TSRMLS_CC, "rs", &filehandle,&buffer, &buffer_len) == FAILURE) {
             return;
        }
        ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
        if (fwrite(buffer, 1, buffer_len, fp) != buffer_len) {
             RETURN_FALSE;
        }
        RETURN_TRUE;
    }
    

    编译安装

    $./configure  --enable-myfile && make
    

    测试扩展

    //test.php
    <?php
        dl('myfile.so');
        $fp_in = file_open("test.txt", "r") or die("Unable to open input file
    ");
        $fp_out = file_open("test.txt.new", "w") or die("Unable to open output file
    ");
        while (!file_eof($fp_in)) {
             $str = file_read($fp_in, 1024);
             print($str);
             file_write($fp_out, $str);
        }
        file_close($fp_in);
        file_close($fp_out);
    ?>
    
    //test.txt
    hello World!
    
    $php -d enable_dl=On test.php
    hello World!
    PHP Warning:  Unknown list entry type in request shutdown (0) in
    $cat test.txt.new
    hello World!
    

    我们成功加载了扩展,对文件进行读写操作。成功流程执行成功。但是报了个错误。该问题是资源没有被关闭造成的。解决该问题时,我们先对代码进行解析。

    代码扫盲

    VCWD宏

    PHP运行在多线程服务器上,不能使用标准的C文件存取函数。这是因为在一个线程里正在运行的PHP脚本会改变当前工作目录,因此另外一个线程里的脚本使用相对路径则无法打开目标文件。为了阻止这种错误发生,PHP框架提供了称作VCWD (virtual current working directory 虚拟当前工作目录)宏,用来代替任何依赖当前工作目录的存取函数。这些宏与被替代的函数具备同样的功能,同时是被透明地处理。在某些没有标准C函数库平台的情况下,VCWD框架则不会得到支持。

    标准C库 | VCWD宏
    getcwd() | VCWD_GETCWD()
    fopen() | VCWD_FOPEN
    open() | VCWD_OPEN() //用于两个参数的版本
    open() | VCWD_OPEN_MODE() //用于三个参数的open()版本
    creat() | VCWD_CREAT()
    chdir() | VCWD_CHDIR()
    getwd() | VCWD_GETWD()
    realpath() | VCWD_REALPATH()
    rename() | VCWD_RENAME()
    stat() VCWD_STAT()
    lstat() | VCWD_LSTAT()
    unlink() | VCWD_UNLINK()
    mkdir() | VCWD_MKDIR()
    rmdir() | VCWD_RMDIR()
    opendir() | VCWD_OPENDIR()
    popen() | VCWD_POPEN()
    access() | VCWD_ACCESS()
    utime() | VCWD_UTIME()
    chmod() | VCWD_CHMOD()
    chown() | VCWD_CHOWN()

    ZEND_REGISTER_RESOURCE 新建和注册资源

    ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type);
    

    宏参数 | 参数类型
    rsrc_result | zval * 设置为zend的资源信息
    rsrc_pointer | 资源数据指针
    rsrc_type | 注册资源类型时获得的资源id

    ZEND_FETCH_RESOURCE 获取资源

    ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type);
    

    参数 | 含义
    rsrc | 资源值保存到的变量名。它应该和rsrc_type有相同类型。
    rsrc_type | rsrc的类型,用于在内部把资源转换成正确的类型
    passed_id | 寻找的zend的资源值,zval **
    default_id | 寻找不到时资源的默认值
    resource_type_name | 注册资源的类型名称,用于错误信息。
    resource_type | 注册资源的资源类型id

    Z_RESVAL_P 通过资源值获取id

    int Z_RESVAL_P(zval **)
    

    同样的还有:

    宏 | 访问对象 | C 类型
    Z_LVAL, Z_LVAL_P, Z_LVAL_PP | 整型值 | long
    Z_BVAL, Z_BVAL_P, Z_BVAL_PP | 布尔值 | zend_bool
    Z_DVAL, Z_DVAL_P, Z_DVAL_PP | 浮点值 | double
    Z_STRVAL, Z_STRVAL_P, Z_STRVAL_PP | 字符串值 | char *
    Z_STRLEN, Z_STRLEN_P, Z_STRLEN_PP | 字符串长度值 int
    Z_RESVAL, Z_RESVAL_P,Z_RESVAL_PP | 资源值 | long
    Z_ARRVAL, Z_ARRVAL_P, Z_ARRVAL_PP | 联合数组 | HashTable *
    Z_TYPE, Z_TYPE_P, Z_TYPE_PP | Zval类型 | Enumeration (IS_NULL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_BOOL, IS_RESOURCE)
    Z_OBJPROP, Z_OBJPROP_P, Z_OBJPROP_PP | 对象属性hash | HashTable *
    Z_OBJCE, Z_OBJCE_P, Z_OBJCE_PP | 对象的类信息 | zend_class_entry

    zend_list_delete 从zend队列中删除资源id

    int zend_list_delete(int id)
    

    写到这里可以发现,file_close 调用的 zend_list_delete 实际上只是将资源的id号从 zend 引擎中删除,真正的有C的fopen打开的资源还是没被释放,所以这里报了个错,下面我们在 PHP_MINIT_FUNCTION 中添加析构函数来处理

    析构资源

    le_myfile 是一个全局的静态变量,你可以理解为是山寨的文件句柄。它实际上在zend在注册资源的句柄。在上面的例子中我们并没有进行赋值就将资源和它进行绑定,现在我们在PHP_MINIT_FUNCTION 进行初始化

    static void myfile_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){
         FILE *fp = (FILE *) rsrc->ptr;
         fclose(fp);
    }
    PHP_MINIT_FUNCTION(myfile)
    {
        /* If you have INI entries, uncomment these lines
        REGISTER_INI_ENTRIES();
        */
        le_myfile = zend_register_list_destructors_ex(myfile_dtor,NULL,"standard-c-file", module_number);
        return SUCCESS;
    }
    

    zend_register_list_destructors_ex 注册析构函数

    int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number);
    

    参数 | 含义
    ld | 析构函数,用于资源关闭时调用
    pld | 析构函数,用于长连接进程关闭时调用
    type_name | 注册资源类型名称
    module_number |

    修改后再次编译安装,没有再出现报错了。





  • 相关阅读:
    普通网站迁移
    小程序注册流程
    批量抓取微信公众号的文章
    uniapp 开发踩坑记录
    微信扫码支付精简版
    QueryList The received content is empty!
    阿里云视频点播sdk封装 php
    我的大伯
    tp5分页携带原有参数
    爬虫项目:破解极验滑动验证码
  • 原文地址:https://www.cnblogs.com/linzhenjie/p/5485519.html
Copyright © 2020-2023  润新知