PHP 資源型別 是連結 C 層面不透明指標(如 FILE*、DB 連線、GD 圖片句柄)至 PHP 變數的橋樑。本文完整解析資源型別定義、註冊、析構器機制與實戰範例。
EG(regular_list) (HashTable) 以 ID 為鍵,值為 zend_rsrc_list_entry:typedef struct _zend_rsrc_list_entry {
void *ptr; // 實際資源指標
int type; // 資源型別 ID
int refcount; // 參考計數
} zend_rsrc_list_entry;
static int le_sample_descriptor; // 全域變數儲存資源型別 ID
PHP_MINIT_FUNCTION(sample) {
// 註冊資源型別,取得唯一 ID
le_sample_descriptor = zend_register_list_destructors_ex(
NULL, // 非持久資源析構器
NULL, // 持久資源析構器
PHP_SAMPLE_DESCRIPTOR_RES_NAME, // 型別名稱 (除錯用)
module_number // 模組編號
);
return SUCCESS;
}
// 模組結構中掛載 MINIT
zend_module_entry sample_module_entry = {
// ...
PHP_MINIT(sample), // MINIT
// ...
};
// 頭文件定義型別名稱
#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "Sample File Descriptor"
PHP_FUNCTION(sample_fopen) {
char *filename, *mode;
int filename_len, mode_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
&filename, &filename_len, &mode, &mode_len) == FAILURE) {
RETURN_NULL();
}
if (!filename_len || !mode_len) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid filename or mode length");
RETURN_FALSE;
}
FILE *fp = fopen(filename, mode);
if (!fp) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to open %s using mode %s", filename, mode);
RETURN_FALSE;
}
// 關鍵:將 FILE* 註冊為資源,ID 存入 return_value
ZEND_REGISTER_RESOURCE(return_value, fp, le_sample_descriptor);
}
PHP_FUNCTION(sample_fread) {
zval *res;
long length = 8192;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|l", &res, &length) == FAILURE) {
RETURN_NULL();
}
FILE *fp;
// 從資源 ID 取得 FILE*,並驗證型別
ZEND_FETCH_RESOURCE(fp, FILE*, &res, -1, PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor);
char *buf = emalloc(length + 1);
size_t read_len = fread(buf, 1, length, fp);
buf[read_len] = '\0';
RETVAL_STRINGL(buf, read_len, 0); // 0=不複製,直接接管記憶體
efree(buf); // 注意:RETVAL_STRINGL(...,0) 會接管 buf,此處 efree 會導致 double free,實際應省略或 dup=1
}
// 非持久資源析構器 (請求結束調用)
void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) {
FILE *fp = (FILE*)rsrc->ptr;
if (fp) {
fclose(fp);
}
}
// 持久資源析構器 (模組關閉調用)
void php_sample_descriptor_persist_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) {
FILE *fp = (FILE*)rsrc->ptr;
if (fp) {
fclose(fp);
}
}
// MINIT 註冊時指定析構器
le_sample_descriptor = zend_register_list_destructors_ex(
php_sample_descriptor_dtor, // 非持久
php_sample_descriptor_persist_dtor, // 持久
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
module_number
);
PHP 腳本呼叫 sample_fopen()
│
▼
fopen() 取得 FILE* 指標
│
▼
ZEND_REGISTER_RESOURCE()
│
├─► zend_hash_next_free_element(EG(regular_list)) → 取得新 ID
│
├─► 建立 zend_rsrc_list_entry {ptr=FILE*, type=le_sample_descriptor, refcount=1}
│
├─► 存入 EG(regular_list)[ID] = entry
│
└─► return_value = ID (IS_RESOURCE 型別)
│
▼
PHP 變數持有資源 ID
│
▼
請求結束 (RSHUTDOWN)
│
▼
zend_hash_graceful_reverse_destroy(EG(regular_list))
│
▼
呼叫對應型別的非持久析構器 → fclose(fp)
│
▼
釋放 entry 記憶體
typedef struct {
FILE *fp;
char *filename;
int mode;
} sample_file_handle;
PHP_FUNCTION(sample_fopen_ex) {
// ... 解析參數 ...
sample_file_handle *handle = pemalloc(sizeof(sample_file_handle), 0);
handle->fp = fopen(filename, mode);
handle->filename = estrdup(filename);
handle->mode = (strcmp(mode, "w") == 0) ? 1 : 0;
ZEND_REGISTER_RESOURCE(return_value, handle, le_sample_handle);
}
// 析構器需釋放所有子記憶體
void php_sample_handle_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) {
sample_file_handle *h = (sample_file_handle*)rsrc->ptr;
if (h) {
if (h->fp) fclose(h->fp);
if (h->filename) efree(h->filename);
efree(h);
}
}
| 宏 | 用途 |
|---|---|
ZEND_REGISTER_RESOURCE(return_value, ptr, type) | 註冊資源,ID 存入 return_value |
ZEND_FETCH_RESOURCE(var, c_type, zval_ptr, id, name, type) | 從 zval 取得資源指標並驗證型別 |
zend_register_list_destructors_ex(ndtor, pdtor, name, module) | 註冊資源型別,取得型別 ID |
zend_list_insert(ptr, type) | 低層註冊,手動管理 ID |
zend_list_delete(id) | 手動刪除資源 (遞減 refcount) |
zend_list_find(id, type) | 查找資源指標 |