1

PHP扩展开发 - Part 3

 2 years ago
source link: https://y2k38.github.io/posts/php-extension-writing-part3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

PHP扩展开发 - Part 3

发表于

2020-02-09 更新于 2020-02-10

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

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

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

typedef struct {
zend_string *name;
zend_long age;
} php_test_person;

#define PHP_TEST_PERSON_RES_NAME "Person Data"

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

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);
}

初始化资源

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不需要知道资源的具体内部表示,只需要获取该资源存储的指针以及资源类型

函数接收资源参数

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,该函数第一个参数为常规资源的析构函数,第二个为持久化资源的析构函数,当离开资源变量所在作用域时,自动调用清理/析构函数,释放内存、关闭连接或执行其他清理操作

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销毁资源,该函数可销毁任何资源类型变量

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

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阶段调用

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);
}
}
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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK