Skip to content

Commit 4ef9b87

Browse files
committed
跳票增加一节,中间的小节还没想整理好。太久没push了,表示我还在。
1 parent 96fb2e2 commit 4ef9b87

11 files changed

+814
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# PHP7 使用资源包裹第三方扩展
2+
3+
我们分为两大块:
4+
首先实现一个自定义的文件打开、读取、写入、关闭的文件操作扩展;
5+
然后分析各个操作背后的实现原理,其中某些部分的实现会和PHP 5.3 使用资源对比分析。
6+
7+
## 通过原型生成扩展骨架
8+
9+
首先进入到源码目录的`ext`目录中,添加一个文件操作的原型文件
10+
11+
[shell]
12+
[root@localhost php-src-php-7.0.3]# cd ext/
13+
[root@localhost ext]# vim tipi_file.proto
14+
15+
编辑为
16+
17+
[shell]
18+
resource file_open(string filename, string mode)
19+
string file_read(resource filehandle, int size)
20+
bool file_write(resource filehandle, string buffer)
21+
bool file_close(resource filehandle)
22+
23+
然后生成骨架,这些前面都说过,我们不再详细说
24+
25+
[shell]
26+
[root@localhost ext]# ./ext_skel --extname=tipi_file --proto=./tipi_file.proto
27+
28+
完整的代码 [tipi_file.c](https://github.com/reeze/tipi/tree/master/book/sample/chapt11/11-02-05-zend-resource/tipi_file.c) 可以先有一个大致的了解,这样后面阅读时,思路可能会清晰很多。
29+
30+
## 注册资源类型
31+
32+
### 认识注册资源类型API
33+
34+
[c]
35+
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, const char *type_name, int module_number)
36+
37+
参数 |描述
38+
--------------|------------------------------------------------
39+
ld |释放该资源时调用的函数。
40+
pld |释放用于在不同请求中始终存在的永久资源的函数。
41+
type_name |描述性类型名称的字符串别名。
42+
module_number |为引擎内部使用,已经定义好了,比如在PHP_FUNCTION宏中已定义
43+
44+
该 API 返回一个资源类型 id,该 id 应当被作为全局变量保存在扩展里,以便在必要的时候传递给其他资源 API。
45+
46+
### 添加资源释放回调函数
47+
48+
该方法表示在释放该类型资源时都需要关闭打开的文件描述符。
49+
50+
[c]
51+
static void tipi_file_dtor(zend_resource *rsrc TSRMLS_DC){
52+
FILE *fp = (FILE *) rsrc->ptr;
53+
fclose(fp);
54+
}
55+
56+
我们发现该函数的参数类型是 `zend_resource` 。这是 PHP7 新增的数据结构,在 PHP 5 则是 `zend_rsrc_list_entry` 。细节的内容,我们留在后面分析。
57+
58+
### 在 PHP_MINIT_FUNCTION 中注册资源类型
59+
60+
我们知道在 PHP 生命周期中,当 PHP 被装载时,`PHP_MINIT_FUNCTION`(模块启动函数)即被引擎调用。
61+
这使得引擎做一些例如资源类型,注册INI变量等的一次初始化。
62+
那么我们需要在这里通过 `zend_register_list_destructors_ex``PHP_MINIT_FUNCTION`来注册资源类型。
63+
64+
[c]
65+
PHP_MINIT_FUNCTION(tipi_file)
66+
{
67+
/* If you have INI entries, uncomment these lines
68+
REGISTER_INI_ENTRIES();
69+
*/
70+
71+
le_tipi_file = zend_register_list_destructors_ex(tipi_file_dtor, NULL, TIPI_FILE_TYPE, module_number);
72+
return SUCCESS;
73+
}
74+
75+
> **NOTICE** 其中 `TIPI_FILE_TYPE` 在前面已经定义了,是该扩展的别名。具体请参考最该节最开始给予的完整代码样例。
76+
77+
## 注册资源
78+
79+
前面是注册了新的资源类型,然后需要注册一个该类型的资源。
80+
81+
### 注册资源 API
82+
83+
在 PHP 7 中删除了原来的 `ZEND_REGISTER_RESOURCE` 宏,直接使用 `zend_register_resource` 函数
84+
85+
[c]
86+
ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)
87+
88+
参数 |描述
89+
--------------|------------------------------------------------
90+
rsrc_pointer |资源数据指针
91+
rsrc_type |注册资源类型时获得的资源类型 id
92+
93+
### 在自定义的 file_open 函数中实现资源的注册
94+
95+
[c]
96+
PHP_FUNCTION(file_open)
97+
{
98+
char *filename = NULL;
99+
char *mode = NULL;
100+
int argc = ZEND_NUM_ARGS();
101+
size_t filename_len;
102+
size_t mode_len;
103+
104+
#ifndef FAST_ZPP
105+
if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename, &filename_len, &mode, &mode_len) == FAILURE)
106+
return;
107+
#else
108+
ZEND_PARSE_PARAMETERS_START(2, 2)
109+
Z_PARAM_STRING(filename, filename_len)
110+
Z_PARAM_STRING(mode, mode_len)
111+
ZEND_PARSE_PARAMETERS_END();
112+
#endif
113+
114+
// 使用 VCWD 宏取代标准 C 文件操作函数
115+
FILE *fp = VCWD_FOPEN(filename, mode);
116+
117+
if (fp == NULL) {
118+
RETURN_FALSE;
119+
}
120+
121+
RETURN_RES(zend_register_resource(fp, le_tipi_file));
122+
}
123+
124+
其中 `RETURN_RES` 宏的作用是将返回的 `zend_resource`添加到 `zval` 中,然后将最后的 `zval` 作为返回值。也就是说该函数的返回值为`zval` 指针。`RETURN_RES(zend_register_resource(fp, le_tipi_file))` 会将返回值的 `value.res` 设为 `fp``u1.type_info` 设为 `IS_RESOURCE_EX` 。大家可以根据源码非常直观的了解到,这里不粘贴代码详细说明了。
125+
126+
## 使用资源
127+
128+
### 使用资源API
129+
130+
在 PHP 7 中删除了原有的 `ZEND_FETCH_RESOURCE` 宏,直接使用函数 `zend_fetch_resource` ,而且解析方式也变得简单了很多,相比 PHP 5 要高效很多,后面我们再通过绘图的形式分析对比。
131+
132+
[c]
133+
ZEND_API void *zend_fetch_resource(zend_resource *res, const char *resource_type_name, int resource_type)
134+
135+
参数 |描述
136+
--------------------|------------------------------------------------
137+
res |资源指针
138+
resource_type_name |该类资源的字符串别名
139+
resource_type |该类资源的类型 id
140+
141+
### 解析资源的实现
142+
143+
当我们要实现文件的读取时,最终还是需要使用原生的 `fread` 函数,所以这里需要通过 `zend_fetch_resource``zend_resource` 解析成为该资源包裹的原始的 `FILE *` 的指针。
144+
145+
[c]
146+
PHP_FUNCTION(file_read)
147+
{
148+
int argc = ZEND_NUM_ARGS();
149+
int filehandle_id = -1;
150+
zend_long size;
151+
zval *filehandle = NULL;
152+
FILE *fp = NULL;
153+
char *result;
154+
size_t bytes_read;
155+
156+
#ifndef FAST_ZPP
157+
if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle, &size) == FAILURE)
158+
return;
159+
#else
160+
ZEND_PARSE_PARAMETERS_START(2, 2)
161+
Z_PARAM_RESOURCE(filehandle)
162+
Z_PARAM_LONG(size)
163+
ZEND_PARSE_PARAMETERS_END();
164+
#endif
165+
166+
if ((fp = (FILE *)zend_fetch_resource(Z_RES_P(filehandle), TIPI_FILE_TYPE, le_tipi_file)) == NULL) {
167+
RETURN_FALSE;
168+
}
169+
170+
result = (char *) emalloc(size+1);
171+
bytes_read = fread(result, 1, size, fp);
172+
result[bytes_read] = '\0';
173+
174+
RETURN_STRING(result, 0);
175+
}
176+
177+
这里需要说明,脚本自动生成的扩展代码中还是使用 `ZEND_FETCH_RESOURCE`, 是个 BUG,因为自动生成的脚本(ext/skeleton/create_stubs)还没更新。
178+
179+
与之类似的文件的写入操作,也很类似,这里就不复制代码了。
180+
181+
## 资源的删除
182+
183+
### 资源删除 API
184+
185+
[c]
186+
ZEND_API int zend_list_close(zend_resource *res)
187+
188+
传入需要被删除的资源即可。该 API 看似非常简单,实际做了很多工作,后面原理分析细说。
189+
190+
### 资源删除的实现
191+
192+
我们在函数 `file_close` 中需要调用资源删除 API
193+
194+
[c]
195+
PHP_FUNCTION(file_close)
196+
{
197+
int argc = ZEND_NUM_ARGS();
198+
int filehandle_id = -1;
199+
zval *filehandle = NULL;
200+
201+
#ifndef FAST_ZPP
202+
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE)
203+
return;
204+
#else
205+
ZEND_PARSE_PARAMETERS_START(1, 1)
206+
Z_PARAM_RESOURCE(filehandle)
207+
ZEND_PARSE_PARAMETERS_END();
208+
#endif
209+
zend_list_close(Z_RES_P(filehandle));
210+
RETURN_TRUE;
211+
}
212+
213+
## 编译安装以及测试
214+
215+
关于编译的代码请参考本章的第一节,这里不再说明,我们说下测试环节。直接用 php 脚本测试,就不一个功能一个功能写测试样例了,修改 `tipi_file.php`文件。
216+
217+
[php]
218+
$fp = file_open("./CREDITS","r+");
219+
var_dump($fp);
220+
var_dump(file_read($fp,6));
221+
var_dump(file_write($fp,"zhoumengakng"));
222+
var_dump(file_close($fp));
223+
224+
然后通过命令行执行
225+
226+
[shell]
227+
php7 -d"extension=tipi_file.so" tipi_file.php
228+

0 commit comments

Comments
 (0)