背景

也许最常见的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] = '\0';
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\n");
$fp_out = file_open("test.txt.new", "w") or die("Unable to open output file\n");
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 |

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

PHP扩展开发--02.包裹第三方的扩展的更多相关文章

  1. PHP扩展开发--01.编写一个helloWorld扩展

    为什么要用C扩展 C是静态编译的,执行效率比PHP代码高很多.同样的运算代码,使用C来开发,性能会比PHP要提升数百倍. 另外C扩展是在进程启动时加载的,PHP代码只能操作Request生命周期的数据 ...

  2. Chrome浏览器扩展开发系列之十七:扩展中可用的chrome.events API

    chrome.events中定义了一些常见的事件类型,可以供Chrome浏览器扩展程序发出对应的事件对象. 对于关注的事件,首先要通过addListener()在对应的事件上注册监听器,示例如下: c ...

  3. Chrome浏览器扩展开发系列之八:Chrome扩展的数据存储

    Google Chrome浏览器扩展可以使用如下任何一种存储机制: HTML5的localStorage API实现的本地存储(此处略) Google的chrome.storage.* API实现的浏 ...

  4. Spark RDD API扩展开发

    原文链接: Spark RDD API扩展开发(1) Spark RDD API扩展开发(2):自定义RDD 我们都知道,Apache Spark内置了很多操作数据的API.但是很多时候,当我们在现实 ...

  5. PHP 扩展开发(将自己的一些代码封装成PHP扩展函数)

    今天时间不多,先给个地址,能搜到我这篇blog的朋友先看看我最近在看的一些文章.资料吧: 我的环境是 lnmp1.1 的 (LNMP一键安装包),所以要进行PHP扩展开发首先应该对环境配置和shell ...

  6. 【转发】NPAPI学习(Firefox和Chrome扩展开发 )

    NPAPI学习(Firefox和Chrome扩展开发 ) 2011-11-08 14:41:02 by [6yang], 1172 visits, 收藏 | 返回 Firefox和Chrome扩展开发 ...

  7. PHP扩展开发相关总结

    1.线程安全宏定义 在TSRM/TSRM.h文件中有如下定义 #define TSRMLS_FETCH() void ***tsrm_ls = (void ***) ts_resource_ex(0, ...

  8. Chrome扩展开发(Gmail附件管理助手)系列之〇——概述

    目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...

  9. PHP扩展开发(1):入门

    有关PHP扩展开发的文章.博客已经很多了,比较经典的有: TIPI项目(http://www.php-internals.com/,强烈推荐) <Extending and Embedding ...

随机推荐

  1. lintcode-424-逆波兰表达式求值

    424-逆波兰表达式求值 求逆波兰表达式的值. 在逆波兰表达法中,其有效的运算符号包括 +, -, *, / .每个运算对象可以是整数,也可以是另一个逆波兰计数表达. 样例 ["2" ...

  2. tomcat下部署了多个项目启动报错java web error:Choose unique values for the 'webAppRootKey' context-param in your web.xml files

    应该是tomcat下部署了多个项目且都使用log4j. <!--如果不定义webAppRootKey参数,那么webAppRootKey就是缺省的"webapp.root". ...

  3. js & 快捷键

    js & 快捷键 demo js-keyboard-shortcuts.html https://codepen.io/webgeeker/pen/MLYrNq let isCtrl = fa ...

  4. Debugger DataSet 调试时查看DataSet

    delphi  跟踪调试的时候查看DataSet数据记录 Ctrl+F7调试 增强工具DataSethttp://edn.embarcadero.com/article/40268 http://do ...

  5. 内存测试——内存泄露工具 LeakCanary

    项目地址 https://github.com/square/leakcanary 接入方法 1. 配置依赖 dependencies { debugCompile 'com.squareup.lea ...

  6. 第164天:js方法调用的四种模式

    js方法调用的四种模式 1.方法调用模式 function Persion() { var name1 = "itcast", age1 = 19, show1 = functio ...

  7. 第82天:jQuery中prop()和attr()的区别

    在高版本的jquery引入prop方法后,什么时候该用prop?什么时候用attr?它们两个之间有什么区别?这些问题就出现了. 关于它们两个的区别,网上的答案很多.这里谈谈我的心得,我的心得很简单: ...

  8. 搭建属于自己的NuGet服务器

    文章导读 创建NuGetServer Web站点 发布站点到IIS 添加本地站点到包包数据源 在上一篇NuGet学习笔记(2) 使用图形化界面打包自己的类库 中讲解了如何打包自己的类库,接下来进行最重 ...

  9. 转--- 秒杀多线程第七篇 经典线程同步 互斥量Mutex

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  10. Python网络编程socket

    网络编程之socket 看到本篇文章的题目是不是很疑惑,what is this?,不要着急,但是记住一说网络编程,你就想socket,socket是实现网络编程的工具,那么什么是socket,什么是 ...