0%

PHP扩展开发 - Part 3

本文将简单介绍资源类型、资源的创建、访问、销毁操作。不再建议使用资源类型,使用类更为合适

资源类型

zval可以表示大部分的PHP数据类型,但有一样不能很好的表示其结构:指针。由于不透明的结构、无法使用传统运算符进行操作等,使得指针在PHP的表示变得困难。因此PHP用一个特殊的标记表示指针:资源,为了使资源标记具有意义,必须先注册到zend engine才能使用。

在头文件定义结构体php_test_person以及资源名称,放置在#define语句后,PHP_MINIT_FUNCTION(test);之前

1
2
3
4
5
6
typedef struct {
zend_string *name;
zend_long age;
} php_test_person;

#define PHP_TEST_PERSON_RES_NAME "Person Data"

源文件定义le_*全局变量,MINIT阶段注册,用于获取资源类型、字面意义名称、析构函数

1
2
3
4
5
6
int le_test_person;

PHP_MINIT_FUNCTION(test)
{
le_test_person = zend_register_list_destructors_ex(NULL, NULL, PHP_TEST_PERSON_RES_NAME, module_number);
}

初始化资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PHP_FUNCTION(test_person_new)
{
php_test_person * person;
zend_string * name;
zend_long age;

ZEND_PARSE_PARAMETERS_START(2,2)
Z_PARAM_STR(name);
Z_PARAM_LONG(age);
ZEND_PARSE_PARAMETERS_END();

if (age < 0 || age > 255) {
php_error_docref(NULL, E_WARNING, "Nonsense age (%ld) given, person resource not created.", age);
RETURN_FALSE;
}

person = emalloc(sizeof(php_test_person));
person->name = zend_string_copy(name); // estrndup + zend_string => zend_string_copy
person->age = age;

RETURN_RES(zend_register_resource(person, le_test_person));
}

函数接收参数name以及age,参数进行校验通过后,申请一段内存空间并写入数据,return_value返回该资源。PHP不需要知道资源的具体内部表示,只需要获取该资源存储的指针以及资源类型

函数接收资源参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PHP_FUNCTION(test_person_greet)
{
php_test_person *person;
zval *zperson;

ZEND_PARSE_PARAMETERS_START(1,1)
Z_PARAM_RESOURCE(zperson);
ZEND_PARSE_PARAMETERS_END();

person = (php_test_person *) zend_fetch_resource_ex(zperson, PHP_TEST_PERSON_RES_NAME, le_test_person);

php_printf("Hello ");
PHPWRITE(ZSTR_VAL(person->name), ZSTR_LEN(person->name));
php_printf("!\nAccording to my records, you are %ld years old.\n", person->age);

RETURN_TRUE;
}

ZEND_FETCH_RESOURCE在PHP7中已被删除,目前获取资源的函数是zend_fetch_resourcezend_fetch_resource_exzend_fetch_resource_ex函数需要一个zval、字面意义名称、资源类型,返回一个指针。函数内部切记不要free该指针

销毁资源

在PHP中使用fopen打开文件并获得一个资源句柄$fp,接下来unset($fp)时文件被关闭,即使没有使用fclose函数。其中的奥秘在zend_register_list_destructors_ex,该函数第一个参数为常规资源的析构函数,第二个为持久化资源的析构函数,当离开资源变量所在作用域时,自动调用清理/析构函数,释放内存、关闭连接或执行其他清理操作

1
2
3
4
5
6
7
8
9
10
11
12
13
le_test_person = zend_register_list_destructors_ex(php_test_person_dtor, NULL, PHP_TEST_PERSON_RES_NAME, module_number);

static void php_test_person_dtor(zend_resource *res)
{
php_test_person *person = (php_test_person *) res->ptr;

if (person) {
if (person->name) {
zend_string_release(person->name); // efree zend_string => zend_string_release
}
efree(person);
}
}

强制销毁资源

使用zend_list_delete销毁资源,该函数可销毁任何资源类型变量

1
2
3
4
5
6
7
8
9
10
11
12
PHP_FUNCTION(test_person_delete)
{
zval * zperson;

ZEND_PARSE_PARAMETERS_START(1,1)
Z_PARAM_RESOURCE(zperson);
ZEND_PARSE_PARAMETERS_END();

zend_list_delete(Z_RES_P(zperson));

RETURN_TRUE;
}

持久化资源

持久化资源与常规资源不同的地方在析构函数声明注册的位置,数据内存申请使用pemalloc代替emalloc

1
2
3
4
5
6
7
int le_test_person_persist;

PHP_MINIT_FUNCTION(test)
{
le_test_person = zend_register_list_destructors_ex(php_test_person_dtor, NULL, PHP_TEST_PERSON_RES_NAME, module_number);
le_test_person_persist = zend_register_list_destructors_ex(NULL, php_test_person_persist_dtor, PHP_TEST_PERSON_RES_NAME, module_number);
}

通常情况下,php_test_person_dtor会在请求结束后调用,php_test_person_persist_dtor在扩展shutdown阶段调用

1
2
3
4
5
6
7
8
9
10
11
static void php_test_person_persist_dtor(zend_resource *res)
{
php_test_person *person = (php_test_person *) res->ptr;

if (person) {
if (person->name) {
zend_string_release(person->name);
}
pefree(person, 1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PHP_FUNCTION(test_person_pnew)
{
php_test_person * person;
zend_string * name;
zend_long age;

ZEND_PARSE_PARAMETERS_START(2,2)
Z_PARAM_STR(name);
Z_PARAM_LONG(age);
ZEND_PARSE_PARAMETERS_END();

if (age < 0 || age > 255) {
php_error_docref(NULL, E_WARNING, "Nonsense age (%ld) given, person resource not created.", age);
RETURN_FALSE;
}

person = pemalloc(sizeof(php_test_person), 1);
person->name = zend_string_dup(name, 1);
person->age = age;

RETURN_RES(zend_register_resource(person, le_test_person_persist));
}

test_person_pnewtest_person_new仅在数据初始化、资源类型方面有差异

查找已存在的持久化资源

为了可以重用持久化资源,需要将其保存在一个安全的地方,zend engine提供了一个executor global通过EG(persistent_list)访问,该变量类型为HashTable

重新修改test_person_pnew

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
PHP_FUNCTION(test_person_pnew2)
{
php_test_person * person;
char * key_raw;
size_t key_len;
zend_string * name, * key;
zend_long age;
zval * zperson = NULL;

ZEND_PARSE_PARAMETERS_START(2,2)
Z_PARAM_STR(name);
Z_PARAM_LONG(age);
ZEND_PARSE_PARAMETERS_END();

if (age < 0 || age > 255) {
php_error_docref(NULL, E_WARNING, "Nonsense age (%ld) given, person resource not created.", age);
RETURN_FALSE;
}

key_len = spprintf(&key_raw, 0, "test_person_%s_%ld\n", ZSTR_VAL(name), age);
key = zend_string_init(key_raw, key_len, 1);
efree(key_raw);

if ((zperson = zend_hash_find(&EG(persistent_list), key)) != NULL) {
person = (php_test_person *) zend_fetch_resource_ex(zperson, PHP_TEST_PERSON_RES_NAME, le_test_person_persist);

ZVAL_RES(return_value, zend_register_persistent_resource_ex(key, person, le_test_person_persist));
zend_string_release(key);
return ;
}

person = pemalloc(sizeof(php_test_person), 1);
person->name = zend_string_copy(name);
person->age = age;

ZVAL_RES(return_value, zend_register_persistent_resource_ex(key, person, le_test_person_persist));
zend_string_release(key);
}

test_person_pnew2先确定EG(persistent_list)是否已经存在,已存在则直接使用,不存在则申请内存初始化资源

参考文档

Extension Writing Part III: Resources
Extension Writing Part III: Resources
Upgrading PHP extensions from PHP5 to NG
PHP Internals Book
References about Maintaining and Extending PHP