编写PHP扩展

创作人 Leo


编辑时间 Sun Nov 29,2020 at 09:30


PHP扩展编写

创建一个扩展

创建扩展
php源代码目录下的 ext/ext_skel 命令工具可以快速创建出一个标准扩展
ext_skel –extname=myExtDemo
简介

/*  PHP_INI
 */
/* Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("Safemysqli.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_Safemysqli_globals, Safemysqli_globals)
    STD_PHP_INI_ENTRY("Safemysqli.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_Safemysqli_globals, Safemysqli_globals)
PHP_INI_END()
*/
/* 这里可以定义一些参数,让用户在 ini 中设置 */

/* Every user-visible function in PHP should document itself in the source
每个用户可用的PHP函数,应该有一个文档,文档包含返回值和函数参数 */
/*  string safemysqli_encodepwd(string pwd)
   Encode the given string for mysqli connecting 
定义一个 php 函数,函数名 safemysqli_encodepwd*/
PHP_FUNCTION(safemysqli_encodepwd)
{
	//encode_pwd 接收字符串参数
	unsigned char *pwd;
	//字符串参数需要提供一个长度指针
	size_t pwd_len;
	zend_string *strg;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &pwd, &pwd_len) == FAILURE) {
		return;
	}

	// invoke AES encode pwd ...
	u_int8_t en[SMYI_ENC_BUF_SIZE] = {'\0'} ;
	crypt_pwd(en, pwd, 1) ;
	RETURN_STRING(en) ;
}
/*  */

开始之前,先简单介绍几个方便调试看效果的函数

php_printf("this is __construct \n"); //在终端输出字符串         
strg = strpprintf(0, "I am %l years old.", age)
RETURN_STR(strg); // 格式化字符串并返回

生命周期

struct _zend_module_entry {
	unsigned short size;
	unsigned int zend_api;
	unsigned char zend_debug;
	unsigned char zts;
	const struct _zend_ini_entry *ini_entry;
	const struct _zend_module_dep *deps;
	const char *name;
	const struct _zend_function_entry *functions;
	int (*module_startup_func)(INIT_FUNC_ARGS);
	int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	int (*request_startup_func)(INIT_FUNC_ARGS);
	int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
	const char *version;
	size_t globals_size;
#ifdef ZTS
	ts_rsrc_id* globals_id_ptr;
#else
	void* globals_ptr;
#endif
	void (*globals_ctor)(void *global);
	void (*globals_dtor)(void *global);
	int (*post_deactivate_func)(void);
	int module_started;
	unsigned char type;
	void *handle;
	int module_number;
	const char *build_id;
}; 

这是 Zend 引擎定义的一个模块的结构体,对外公开访问的api,生命周期函数,类,信息,都包含在这里了

zend_module_entry myext_module_entry = {
	STANDARD_MODULE_HEADER,
	"myext",
	myext_functions,
	PHP_MINIT(myext),
	PHP_MSHUTDOWN(myext),
	PHP_RINIT(myext),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(myext),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(myext),
	PHP_MYEXT_VERSION,
	STANDARD_MODULE_PROPERTIES
}; 

这是我们创建的一个标准模块生成出来的 entry 信息

//  STANDARD_MODULE_HEADER  展开后如下:

#define STANDARD_MODULE_HEADER \
	STANDARD_MODULE_HEADER_EX, NULL, NULL

// 也就是:
	sizeof(zend_module_entry),
	ZEND_MODULE_API_NO, 
	ZEND_DEBUG, 
	USING_ZTS,
	NULL,
	NULL

// 标准信息:	
	"myext",
	myext_functions,
	PHP_MINIT(myext),
	PHP_MSHUTDOWN(myext),
	PHP_RINIT(myext),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(myext),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(myext),
	PHP_MYEXT_VERSION,

#define STANDARD_MODULE_PROPERTIES \
	NO_MODULE_GLOBALS, NULL, STANDARD_MODULE_PROPERTIES_EX

// 也就是:
	0, 
	NULL, 
	NULL, 
	NULL, 
	NULL, 
	0, 
	0, 
	NULL, 
	0, 
	ZEND_MODULE_BUILD_ID

一共 24 个字段
我们先看下标准信息:

	"myext",				// 扩展名字
	myext_functions,		// 这个是注册到扩展的函数集合
	PHP_MINIT(myext),
	PHP_MSHUTDOWN(myext),
	PHP_RINIT(myext),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(myext),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(myext),
	PHP_MYEXT_VERSION,

PHP_MINIT(myext),
对应
PHP_MINIT_FUNCTION(myext)
模块初始化调用,可以定义模块中的内置类,模块中的常量
mysqli 在这里进行内置类的声明

PHP_MINIT_FUNCTION(mysqli)
{
	zend_class_entry *ce,cex;
	zend_object_handlers *std_hnd = zend_get_std_object_handlers();
	...
}

PHP_MSHUTDOWN(myext)
对应
PHP_MSHUTDOWN_FUNCTION(myext)
模块关闭调用
mysqli 在这里对创建的hash表进行内存清理

PHP_MSHUTDOWN_FUNCTION(mysqli)
{
	...
	zend_hash_destroy(&mysqli_driver_properties);
	zend_hash_destroy(&mysqli_result_properties);
	...
}

PHP_RINIT
PHP_RINIT_FUNCTION(myext)
请求初始化调用
mysqli 在这里将mysqli的全局用的东西进行初始化

PHP_RINIT_FUNCTION(mysqli)
{
#if !defined(MYSQLI_USE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000
	if (mysql_thread_init()) {
		return FAILURE;
	}
#endif
	MyG(error_msg) = NULL;
	MyG(error_no) = 0;
	MyG(report_mode) = 0;

	return SUCCESS;
}

PHP_RSHUTDOWN
PHP_RSHUTDOWN_FUNCTION(myext)
请求结束调用
mysqli 在这里进行一些内存释放操作

PHP_RSHUTDOWN_FUNCTION(mysqli)
{
	/* check persistent connections, move used to free */

#if !defined(MYSQLI_USE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000
	mysql_thread_end();
#endif
	if (MyG(error_msg)) {
		efree(MyG(error_msg));
	}
#if defined(A0) && defined(MYSQLI_USE_MYSQLND)
	/* psession is being called when the connection is freed - explicitly or implicitly */
	zend_hash_apply(&EG(persistent_list), (apply_func_t) php_mysqli_persistent_helper_once);
#endif
	return SUCCESS;
}

PHP_MINFO
PHP_RSHUTDOWN_FUNCTION(myext)
给出模块信息
在 phpinfo 中输出的内容

PHP_MINFO_FUNCTION(mysqli)
{
	char buf[32];

	php_info_print_table_start();
	php_info_print_table_header(2, "MysqlI Support", "enabled");
	php_info_print_table_row(2, "Client API library version", mysql_get_client_info());
	snprintf(buf, sizeof(buf), ZEND_LONG_FMT, MyG(num_active_persistent));
	php_info_print_table_row(2, "Active Persistent Links", buf);
	snprintf(buf, sizeof(buf), ZEND_LONG_FMT, MyG(num_inactive_persistent));
	php_info_print_table_row(2, "Inactive Persistent Links", buf);
	snprintf(buf, sizeof(buf), ZEND_LONG_FMT, MyG(num_links));
	php_info_print_table_row(2, "Active Links", buf);
#if !defined(MYSQLI_USE_MYSQLND)
	php_info_print_table_row(2, "Client API header version", MYSQL_SERVER_VERSION);
	php_info_print_table_row(2, "MYSQLI_SOCKET", MYSQL_UNIX_ADDR);
#endif
	php_info_print_table_end();

	DISPLAY_INI_ENTRIES();
}

声明 zend 变量

zval function_name; // 声明一个 zend 变量 
ZVAL_STRING(&function_name,"real_connect"); // 调用zend api 提供的变量初始化函数,ZVAL_STRING 参数1传 zval 的内存地址,参数2 为需要初始化的字符串常亮

数据类型在 Zend/zend_types.h 头文件中定义
使用 zval 声明变量,主要是兼容php的弱类型:

struct _zval_struct {
	zend_value        value;			/* value 变量具体值 */
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)	    /* call info for EX(This) */
		} v;
		uint32_t type_info; /* 变量的类型标志 */
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* literal cache slot */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     extra;                /* not further specified */
	} u2;
};

zval 在程序内部维护了变量的类型和值

#define ZVAL_STRINGL(z, s, l) do {				\
		ZVAL_NEW_STR(z, zend_string_init(s, l, 0));		\
	} while (0)

宏 ZVAL_STRINGL 创建一个字符串的zval

#define ZVAL_NEW_STR(z, s) do {					\
		zval *__z = (z);						\
		zend_string *__s = (s);					\
		Z_STR_P(__z) = __s;						\
		Z_TYPE_INFO_P(__z) = IS_STRING_EX;		\
	} while (0)	

宏 ZVAL_NEW_STR

  1. 对zval进行初始化:
zval *__z = (z);
zend_string *__s = (s);
  1. Z_STR 宏展开后,看到宏指令的内容是将初始化的字符串赋值给 zval 结构体的值引用指针
#define Z_STR(zval)					(zval).value.str
#define Z_STR_P(zval_p)				Z_STR(*(zval_p))
  1. Z_TYPE_INFO_P 宏展开后,看到它是给 zval 结构体的类型标志进行复制,也就是表明这是一个 IS_STRING_EX 的 zval
#define Z_TYPE_INFO(zval)			(zval).u1.type_info
#define Z_TYPE_INFO_P(zval_p)		Z_TYPE_INFO(*(zval_p))

// 以上均在 zend_types.h 中声明

zend 提供了一系列的宏指令,让代码能够方便的进行变量初始化 字符串有两个特殊宏在 Zend/zend_API.h 头文件中定义
声明一个定长的string,这里也是调用了

#define ZVAL_STRINGL(z, s, l) do {				\
		ZVAL_NEW_STR(z, zend_string_init(s, l, 0));		\
	} while (0)

简单声明 string ,将会根据传入的字符串常亮长度决定字符串长度

#define ZVAL_STRING(z, s) do {					\
		const char *_s = (s);					\
		ZVAL_STRINGL(z, _s, strlen(_s));		\
	} while (0)

变量包装

zend 的变量宏在 zend_types.h 中定义
介绍几个常用的
- 先回顾一下php类型

  1. 四种标量类型: boolean(布尔型) integer(整型) float(浮点型,也称作 double) string(字符串)

  2. 三种复合类型: array(数组) object(对象) callable(可调用)

  3. 最后是两种特殊类型: resource(资源) NULL(无类型)

我们这里先介绍标量

	// 未定义类型
#define ZVAL_UNDEF(z) do {				\
		Z_TYPE_INFO_P(z) = IS_UNDEF;	\
	} while (0)

	// NULL 类型
#define ZVAL_NULL(z) do {				\
		Z_TYPE_INFO_P(z) = IS_NULL;		\
	} while (0)

	// 布尔类型:在php内核中 bool 只是一个包装类型,实际上布尔拆分了两个类型 TRUE 和 FALSE,我们在后面介绍函数返回值也是直接用这两个类型
#define ZVAL_FALSE(z) do {				\
		Z_TYPE_INFO_P(z) = IS_FALSE;	\
	} while (0)
#define ZVAL_TRUE(z) do {				\
		Z_TYPE_INFO_P(z) = IS_TRUE;		\
	} while (0)
#define ZVAL_BOOL(z, b) do {			\
		Z_TYPE_INFO_P(z) =				\
			(b) ? IS_TRUE : IS_FALSE;	\
	} while (0)

	// 整数类型:64位长整
#define ZVAL_LONG(z, l) {				\
		zval *__z = (z);				\
		Z_LVAL_P(__z) = l;				\
		Z_TYPE_INFO_P(__z) = IS_LONG;	\
	}

	// 浮点数类型:64位双精度
#define ZVAL_DOUBLE(z, d) {				\
		zval *__z = (z);				\
		Z_DVAL_P(__z) = d;				\
		Z_TYPE_INFO_P(__z) = IS_DOUBLE;	\
	}

	// 字符串类型:字符串一般不用这个,都是用前面提到的包装宏 ZVAL_STRING 创建
#define ZVAL_STR(z, s) do {						\
		zval *__z = (z);						\
		zend_string *__s = (s);					\
		Z_STR_P(__z) = __s;						\
		/* interned strings support */			\
		Z_TYPE_INFO_P(__z) = ZSTR_IS_INTERNED(__s) ? \
			IS_INTERNED_STRING_EX : 			\
			IS_STRING_EX;						\
	} while (0)

数组

对象

#define ZVAL_OBJ(z, o) do {						\
		zval *__z = (z);						\
		Z_OBJ_P(__z) = (o);						\
		Z_TYPE_INFO_P(__z) = IS_OBJECT_EX;		\
	} while (0)

对象不是用这个直接创建的,可以通过在已经声明为类方法的函数中通过 getThis() 获取
例:ext/dom/document.c DOM操作类的构造函数

PHP_METHOD(domdocument, __construct)
{

	zval *id = getThis();
}

也可以通过函数 object_init_ex 创建

/*  php_dom_create_object */
PHP_DOM_EXPORT zend_bool php_dom_create_object(xmlNodePtr obj, zval *return_value, dom_object *domobj)
{
	zend_class_entry *ce;
	...
	switch (obj->type) {
		case XML_DOCUMENT_NODE:
		case XML_HTML_DOCUMENT_NODE:
		{
			ce = dom_document_class_entry;
			break;
		}
		...
		default:
			php_error_docref(NULL, E_WARNING, "Unsupported node type: %d", obj->type);
			ZVAL_NULL(return_value);
			return 0;
	}
	...
	object_init_ex(return_value, ce);
	...
}
/*  end php_domobject_new */

资源

定义返回值

在函数中接收参数

类的创建

https://github.com/walu/phpbook/blob/master/10.2.md

给类添加方法
添加像 PDO::FETCH_ASSOC 似的常量

声明导出函数

const zend_function_entry Safemysqli_functions[] = {
	PHP_FE(confirm_Safemysqli_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE(safemysqli_encodepwd, NULL) 
	PHP_FE_END	/* Must be the last line in Safemysqli_functions[] */
};

PHP_FE(safemysqli_encodepwd, NULL)

  1. name 函数名
  2. arg_info 参数说明,给 zend_get_parameter 用来做参数验证(内核自动类型校对),

main/spprintf.c 字符串处理核心

strg = strpprintf(0, “Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.”, “Safemysqli”, arg); 格式化字符串

  1. max_length 字符串最大程度,0代表不限
  2. 格式,遵循 sprintf
  3. 其他为替换串,遵循 sprintf
int main(){
	printf("%30s\n", "1111111111"); //                    1111111111 至少输出30个字符,不够的在前面加空格
	printf("%.3s\n", "1111111111"); //111 最多输出3个字符
	return 0;
}

返回串

返回 char* 类型
RETURN_STRING(pwd) ;

返回 zend_string 类型
RETURN_STRING(pwd) ;

类定义:給类增加方法

const zend_function_entry mysqli_result_methods[] = {
	PHP_FALIAS(__construct, mysqli_result_construct, NULL)
	PHP_FALIAS(close, mysqli_free_result, arginfo_mysqli_no_params)
	PHP_FALIAS(free, mysqli_free_result, arginfo_mysqli_no_params)
	PHP_FALIAS(data_seek, mysqli_data_seek, arginfo_class_mysqli_data_seek)
	PHP_FALIAS(fetch_field, mysqli_fetch_field, arginfo_mysqli_no_params)
	PHP_FALIAS(fetch_fields, mysqli_fetch_fields, arginfo_mysqli_no_params)
	PHP_FALIAS(fetch_field_direct, mysqli_fetch_field_direct, arginfo_class_mysqli_result_and_fieldnr)
#if defined(MYSQLI_USE_MYSQLND)
	PHP_FALIAS(fetch_all, mysqli_fetch_all, arginfo_class_mysqli_fetch_array)
#endif
	PHP_FALIAS(fetch_array, mysqli_fetch_array, arginfo_class_mysqli_fetch_array)
	PHP_FALIAS(fetch_assoc, mysqli_fetch_assoc, arginfo_mysqli_no_params)
	PHP_FALIAS(fetch_object,mysqli_fetch_object, arginfo_class_mysqli_fetch_object)
	PHP_FALIAS(fetch_row, mysqli_fetch_row, arginfo_mysqli_no_params)
	PHP_FALIAS(field_seek, mysqli_field_seek, arginfo_class_mysqli_result_and_fieldnr)
	PHP_FALIAS(free_result, mysqli_free_result, arginfo_mysqli_no_params)
	PHP_FE_END
};

php内核使用了很多宏定义,这些宏实际上就是在编译的时候会进行展开,变成相应的c代码
例如:

宏:

#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value

函数:

void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_real_connect, zend_bool in_ctor)

解析成:

void mysqli_common_connect(zend_execute_data *execute_data, zval *return_value, zend_bool is_real_connect, zend_bool in_ctor)

里面有个参数传递:

	if (getThis() && !ZEND_NUM_ARGS() && in_ctor) {
		php_mysqli_init(INTERNAL_FUNCTION_PARAM_PASSTHRU, in_ctor);
		return;
	}

这里的:

php_mysqli_init(INTERNAL_FUNCTION_PARAM_PASSTHRU, in_ctor);

通过:

#define INTERNAL_FUNCTION_PARAM_PASSTHRU execute_data, return_value

解析成:

php_mysqli_init(execute_data, return_value, in_ctor); 

很好理解了,这里就是如果是个对象并且没有参数,并且 in_ctor(只有 mysqli_link_construct 这个用,这个是个构造函数)

面向对象

对象的结构体,
包含了 1. gc 引用计数结构,确定何时对对象占用的内存进行回收 2. ce 指向类实体的指针,标志了对象在内存中的具体结构,php 的反射也是基于这个实现的

struct _zend_object {
	zend_refcounted_h gc;
	uint32_t          handle; // TODO: may be removed ???
	zend_class_entry *ce;
	const zend_object_handlers *handlers;
	HashTable        *properties;
	zval              properties_table[1];
};

类的结构体,

包含了 PHP 中我们所知道的类操作的一切,
魔术方法
成员列表
函数列表
序列化操作

struct _zend_class_entry {
	char type;
	zend_string *name;
	struct _zend_class_entry *parent;
	int refcount;
	uint32_t ce_flags;

	int default_properties_count;
	int default_static_members_count;
	zval *default_properties_table;
	zval *default_static_members_table;
	zval *static_members_table;
	HashTable function_table;
	HashTable properties_info;
	HashTable constants_table;

	union _zend_function *constructor;
	union _zend_function *destructor;
	union _zend_function *clone;
	union _zend_function *__get;
	union _zend_function *__set;
	union _zend_function *__unset;
	union _zend_function *__isset;
	union _zend_function *__call;
	union _zend_function *__callstatic;
	union _zend_function *__tostring;
	union _zend_function *__debugInfo;
	union _zend_function *serialize_func;
	union _zend_function *unserialize_func;

	zend_class_iterator_funcs iterator_funcs;

	/* handlers */
	zend_object* (*create_object)(zend_class_entry *class_type);
	zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref);
	int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type); /* a class implements this interface */
	union _zend_function *(*get_static_method)(zend_class_entry *ce, zend_string* method);

	/* serializer callbacks */
	int (*serialize)(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);
	int (*unserialize)(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

	uint32_t num_interfaces;
	uint32_t num_traits;
	zend_class_entry **interfaces;

	zend_class_entry **traits;
	zend_trait_alias **trait_aliases;
	zend_trait_precedence **trait_precedences;

	union {
		struct {
			zend_string *filename;
			uint32_t line_start;
			uint32_t line_end;
			zend_string *doc_comment;
		} user;
		struct {
			const struct _zend_function_entry *builtin_functions;
			struct _zend_module_entry *module;
		} internal;
	} info;
};

例:动手实现一个php内置类

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_myext.h"

static int le_myext;

// 构造函数我们让用户传递两个参数作为初始化,这里做一下参数限制
ZEND_BEGIN_ARG_INFO_EX(arginfo_myext, 0, 0, 2)
	ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
	ZEND_ARG_TYPE_INFO(0, country, IS_STRING, 0)
ZEND_END_ARG_INFO()

// 将类实体在外部定义,让类中的其他函数可以进行操作
zend_class_entry *myext_class_entry;


/* Every user-visible function in PHP should document itself in the source */
/*  proto string confirm_myext_compiled(string arg)
   Return a string to confirm that the module is compiled in */
PHP_FUNCTION(confirm_myext_compiled)
{
	char *arg = NULL;
	size_t arg_len, len;
	zend_string *strg;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
		return;
	}

	strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myext", arg);

	RETURN_STR(strg);
}
/*  */

// 定义一个成员函数
PHP_METHOD(myext_class_entry, greeting)
{
	zval *id = getThis(); // 获取对象本身
	char *greeter ; // 这里接收一个打招呼的话
	size_t greeter_len ; // 接收string类型,需要两个参数,其他类型参照 hack 文档的说明

	php_printf("zend num args %d \n", ZEND_NUM_ARGS());

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &greeter, &greeter_len) == FAILURE) {
		php_printf("zend parse parameters failed \n");
		return;
	}

	// php在对象内部有一个类实体指针,这里获取这个类指针,这样程序就可以知道类的具体定义,从而知道如何设置参数
	zend_class_entry *ce = Z_OBJCE_P(id); 
	zval rv, *name = NULL, *country = NULL;
	// 读取成员变量
	name = zend_read_property(ce, id, "name", sizeof("name")-1, 0 TSRMLS_DC, &rv); 
	// 读取静态变量
	country = zend_read_static_property(ce, "country", sizeof("country")-1, 0 TSRMLS_DC); 
	
	zend_string *strg;
	strg = strpprintf(0, "%s ! My name is %s, I'm from %s.", greeter, Z_STRVAL_P(name), Z_STRVAL_P(country));
	RETURN_STR(strg);
}

PHP_METHOD(myext_class_entry, setname)
{
	zval *id = getThis();
	long age;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &age) == FAILURE) {
		return;
	}

	zend_string *strg;
	strg = strpprintf(0, "I am %ld years old.", age);
	RETURN_STR(strg);
}

PHP_METHOD(myext_class_entry, test)
{
	// 创建一个 long 整数变量
	zval n ;
	ZVAL_LONG(&n, 100) ;

	// 这三个等价
	php_printf("test : %ld \n", n.value.lval);
	php_printf("test : %ld \n", Z_LVAL_P(&n));
	php_printf("test : %ld \n", Z_LVAL(n));

	RETURN_TRUE;
}

// 定义构造函数
PHP_METHOD(myext_class_entry, __construct )
{
	php_printf("this is __construct \n");

	zval *name, *country;
	zend_class_entry *ce;
	ce = Z_OBJCE_P(getThis());
	// 在构造函数里接收两个参数,由于使用 z 来接收参数,为了防止不安全,在PHP_ME 导出时,我们定义了参数检查
	if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &name, &country) == FAILURE )
	{
		printf("Error\n");
		RETURN_NULL();
	}
	
	// 获取zval变量后,可以通过 Z_xxVAL_P 进行类型转换,这个宏返回的就是具体在类型结构中的具体值,
	// 比如 Z_STRVAL_P 返回的就是 _zend_string.val 
	php_printf("p1 %s, p2 %s \n", Z_STRVAL_P(name), Z_STRVAL_P(country));

	// 更新成员变量
	zend_update_property(ce, getThis(), "name", sizeof("name")-1, name TSRMLS_CC);
	
	// 更新静态变量
	zend_update_static_property(ce, "country", sizeof("country")-1, country TSRMLS_CC);
}

// 定义类成员函数列表
// ZEND_ACC_xxx 定义了访问控制:
// ZEND_ACC_PUBLIC 公有,ZEND_ACC_PROTECTED 受保护的,ZEND_ACC_PRIVATE 私有的
// ZEND_ACC_STATIC 静态的
// ZEND_ACC_CTOR 构造函数,ZEND_ACC_DTOR 析构函数
static zend_function_entry myext_method[]={
  // 构造函数我们对参数进行验证,其他的直接给NULL 了
  PHP_ME(myext_class_entry, __construct, arginfo_myext, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
  PHP_ME(myext_class_entry, greeting, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(myext_class_entry, setname, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(myext_class_entry, test, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  PHP_FE_END	/* Must be the last line in Safemysqli_functions[] */
};

/*  PHP_MINIT_FUNCTION 
 */
PHP_MINIT_FUNCTION(myext)
{
	/* If you have INI entries, uncomment these lines
	REGISTER_INI_ENTRIES();
	*/

	// 定义并初始化类实体
	zend_class_entry ce;
	INIT_CLASS_ENTRY(ce, "myext", myext_method)
	// 将类注册到PHP内部
	myext_class_entry = zend_register_internal_class(&ce TSRMLS_CC);

	// 定义成员变量,除了 _null 还有其他的,类似 zend_declare_property_long 定义整形,所有数据类型都有
	zend_declare_property_null(myext_class_entry, "name", strlen("name"), ZEND_ACC_PUBLIC TSRMLS_CC);
    zend_declare_property_null(myext_class_entry, "country", strlen("country"), ZEND_ACC_STATIC|ZEND_ACC_PUBLIC TSRMLS_CC);

	return SUCCESS;
}
/*  */

/*  PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(myext)
{
	/* uncomment this line if you have INI entries
	UNREGISTER_INI_ENTRIES();
	*/
	return SUCCESS;
}
/*  */

/* Remove if there's nothing to do at request start */
/*  PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(myext)
{
#if defined(COMPILE_DL_MYEXT) && defined(ZTS)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif
	return SUCCESS;
}
/*  */

/* Remove if there's nothing to do at request end */
/*  PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(myext)
{
	return SUCCESS;
}
/*  */

/*  PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(myext)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "myext support", "enabled");
	php_info_print_table_end();

	/* Remove comments if you have entries in php.ini
	DISPLAY_INI_ENTRIES();
	*/
}
/*  */

/*  myext_functions[]
 *
 * Every user visible function must have an entry in myext_functions[].
 */
const zend_function_entry myext_functions[] = {
	PHP_FE(confirm_myext_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE_END	/* Must be the last line in myext_functions[] */
};
/*  */

/*  myext_module_entry
 */
zend_module_entry myext_module_entry = {
	STANDARD_MODULE_HEADER,
	"myext",
	myext_functions,
	PHP_MINIT(myext),
	PHP_MSHUTDOWN(myext),
	PHP_RINIT(myext),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(myext),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(myext),
	PHP_MYEXT_VERSION,
	STANDARD_MODULE_PROPERTIES
};
/*  */

#ifdef COMPILE_DL_MYEXT
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(myext)
#endif

在这个位置定义模块信息

zend_module_entry mysqli_module_entry = {
	STANDARD_MODULE_HEADER_EX, NULL,
	mysqli_deps,
	"mysqlis",
	mysqli_functions,
	PHP_MINIT(mysqli),
	PHP_MSHUTDOWN(mysqli),
	PHP_RINIT(mysqli),
	PHP_RSHUTDOWN(mysqli),
	PHP_MINFO(mysqli),
	PHP_MYSQLI_VERSION,
	PHP_MODULE_GLOBALS(mysqli),
	PHP_GINIT(mysqli),
	NULL,
	NULL,
	STANDARD_MODULE_PROPERTIES_EX
};

创建变量

例:创建字符串

zval function_name;
ZVAL_STRING(&function_name,"real_connect");

其他变量使用前面提到的包装函数创建
例:创建一个整形

zval n ;
ZVAL_LONG(&n, 100) ;

变量转型

例:字符串使用 Z_STRVAL_P

zval rv, *name = NULL, *country = NULL;
// 读取成员变量
name = zend_read_property(ce, id, "name", sizeof("name")-1, 0 TSRMLS_DC, &rv); 
// 读取静态变量
country = zend_read_static_property(ce, "country", sizeof("country")-1, 0 TSRMLS_DC); 
zend_string *strg;
strg = strpprintf(0, "%s ! My name is %s, I'm from %s.", greeter, Z_STRVAL_P(name), Z_STRVAL_P(country));

例:整数类型使用 Z_LVAL_P

// 这三个等价
php_printf("test : %ld \n", n.value.lval);
php_printf("test : %ld \n", Z_LVAL_P(&n));
php_printf("test : %ld \n", Z_LVAL(n));

例:对象使用 Z_OBJCE_P

zval *id = getThis(); // 获取对象本身
zend_class_entry *ce = Z_OBJCE_P(id);    

技巧

PHP_FALIAS

用这个 PHP_FALIAS 可以修改函数名字

const zend_function_entry mysqli_link_methods[] = {
	PHP_FALIAS(autocommit, mysqli_autocommit, arginfo_class_mysqli_autocommit)

调用一个函数

zend_call_function 在 pdo 扩展有对内部函数调用的示例

static void pdo_stmt_construct(zend_execute_data *execute_data, pdo_stmt_t *stmt, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /*  */
{
	zval query_string;
	zval z_key;

	ZVAL_STRINGL(&query_string, stmt->query_string, stmt->query_stringlen);
	ZVAL_STRINGL(&z_key, "queryString", sizeof("queryString") - 1);
	std_object_handlers.write_property(object, &z_key, &query_string, NULL);
	zval_ptr_dtor(&query_string);
	zval_ptr_dtor(&z_key);

	if (dbstmt_ce->constructor) {
		zend_fcall_info fci;
		zend_fcall_info_cache fcc;
		zval retval;

		fci.size = sizeof(zend_fcall_info);
		ZVAL_UNDEF(&fci.function_name);
		fci.object = Z_OBJ_P(object);
		fci.retval = &retval;
		fci.param_count = 0;
		fci.params = NULL;
		fci.no_separation = 1;

		zend_fcall_info_args(&fci, ctor_args);

		fcc.initialized = 1;
		fcc.function_handler = dbstmt_ce->constructor;
		fcc.calling_scope = zend_get_executed_scope();
		fcc.called_scope = Z_OBJCE_P(object);
		fcc.object = Z_OBJ_P(object);

		if (zend_call_function(&fci, &fcc) != FAILURE) {
			zval_ptr_dtor(&retval);
		}

		zend_fcall_info_args_clear(&fci, 1);
	}
}

例:调用 mysqli 中的成员函数

/*  bool safemysqli_real_connect(object link [,string hostname [,string username [,string passwd [,string dbname [,int port [,string socket [,int flags]]]]]]])
   Open a connection to a mysql server */
PHP_FUNCTION(safemysqli_real_connect)
{

    // 定义接收的变量,obj 代表我们接收 mysqli 对象,其他参数为 mysqli::real_connect 的参数
    zval				*obj = NULL;
	char				*hostname = NULL, *username=NULL, *passwd=NULL, *dbname=NULL, *socket=NULL;
	size_t				hostname_len = 0, username_len = 0, passwd_len = 0, dbname_len = 0, socket_len = 0 ;
	zend_long			port = 0, flags = 0;
	
    hostname = username = dbname = passwd = socket = NULL;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "o|sssslsl", &obj,
        &hostname, &hostname_len, &username, &username_len, &passwd, &passwd_len, 
        &dbname, &dbname_len, &port, &socket, &socket_len,&flags) == FAILURE) {
        return;
    }

    zval function_name;
    ZVAL_STRING(&function_name,"real_connect");

    zval retval;

    uint32_t pass_parm_n = ZEND_NUM_ARGS()-1;

    zval params[7];
    if(pass_parm_n>0) {
        ZVAL_STRING(&params[0],hostname);
    }
    if(pass_parm_n>1) {
        ZVAL_STRING(&params[1],username);
    }
    if(pass_parm_n>2) {
        // decrypt passwd
        u_int8_t de[SMYI_ENC_BUF_SIZE] = {'\0'} ;
        crypt_pwd(de, passwd, 0) ;
        passwd = de;

        ZVAL_STRING(&params[2],passwd);
    }
    if(pass_parm_n>3) {
        ZVAL_STRING(&params[3],dbname);
    }
    if(pass_parm_n>4) {
        ZVAL_LONG(&params[4],port);
    }
    if(pass_parm_n>5) {
        ZVAL_STRING(&params[5],socket);
    }
    if(pass_parm_n>6) {
        ZVAL_LONG(&params[6],flags);
    }
    
    zend_fcall_info fci;
    fci.size = sizeof(zend_fcall_info);
    fci.object = obj ? Z_OBJ_P(obj) : NULL;
    fci.function_name = function_name; 
    fci.retval = &retval;
    fci.param_count = pass_parm_n;
    fci.params = params;
    fci.no_separation = 1;

    int succ = 0 ;
    if (zend_call_function(&fci, NULL TSRMLS_CC) != FAILURE) {
        if(Z_TYPE_P(&retval) == IS_TRUE) // 对zend 返回值进行判断
        {
            succ = 1;
        }
        zval_ptr_dtor(&retval);
    }

    if (succ) {
        RETURN_TRUE ;
    }else{
        RETURN_FALSE;
    }
}
/*  */

config.m4 编写说明

基于 phpize

config.m4 需要修改的地方
声明模块名称
PHP_NEW_EXTENSION(mysqlis, $mysqli_sources, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
将扩展头文件安装到include路径,给其他程序调用
PHP_INSTALL_HEADERS([ext/mysqlis/php_mysqli_structs.h])
给扩展添加依赖
PHP_ADD_EXTENSION_DEP(mysqlis, mysqlnd)

例:声明一个 –with-xxx 编译时参数

PHP_ARG_WITH(openssl, for OpenSSL support,
[  --with-openssl[=DIR]      Include OpenSSL support (requires OpenSSL >= 1.0.1)])

声明一个 –enable-xxx 编译时判断是否开启扩展功能

PHP_ARG_ENABLE(Safemysqli, whether to enable Safemysqli support,
dnl Make sure that the comment is aligned:
[  --enable-Safemysqli           Enable Safemysqli support])

这里对 –with-xxx 或者 –enable-xxx 进行判断,格式就是 $PHP_XXXXX

if test "$PHP_SAFEMYSQLI" != "no"; then
  
  # 定义一个变量,用来拼接到 openssl 路径,确定头文件查找位置 -I
  SEARCH_FOR="include/openssl/evp.h"  # you most likely want to change this
  # 这里判断命令行是否指定了 openssl 库路径,如果没指定,报错
  if test -r $PHP_OPENSSL/$SEARCH_FOR; then # path given as parameter
    OPENSSL_DIR=$PHP_OPENSSL
  else # search default path list
    AC_MSG_RESULT(found in $SEARCH_FOR)
  fi

  # 用 test 命令判断变量 OPENSSL_DIR 是否定义,-z zero 如果字符串为空串返回真
  if test -z "$OPENSSL_DIR"; then
    AC_MSG_RESULT([not found])
    AC_MSG_ERROR([Please reinstall the openssl distribution])
  fi

  # 测试完成,将openssl 头文件include path 包含到 Makefile
  PHP_ADD_INCLUDE($OPENSSL_DIR/include)

  # 定义变量,用来测试动态链接库是否存在,LIBNAME 是库名,也就是我们编译参数 -l 指定的,LIBSYMBOL 是在库中存在的函数,用来测试 lib 可用性
  LIBNAME=crypto # you may want to change this
  LIBSYMBOL=EVP_CIPHER_CTX_init # you most likely want to change this 

  # 检测lib可用性
  PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
  [
  	# 将链接库查找路径包含到 Makefile,也就是命令行中的 -L
    PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $OPENSSL_DIR/lib, OPENSSL_SHARED_LIBADD)
    AC_DEFINE(HAVE_OPENSSLLIB,1,[ ])
  ],[
    AC_MSG_ERROR([wrong Openssl lib version or lib not found])
  ],[
  	# 增加到命令行的具体值
    -L$OPENSSL_DIR/lib -l$LIBNAME
  ])
  
  PHP_SUBST(SAFEMYSQLI_SHARED_LIBADD)

  PHP_NEW_EXTENSION(Safemysqli, Safemysqli.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)

  # 将头文件安装到php include 下
  PHP_INSTALL_HEADERS([ext/Safemysqli/php_Safemysqli.h])
fi

例: 加上 pkg-config 可以用系统路径的库
pkg-config xxx –libs 打印出该链接库需要的 -L 和 -l 信息
pkg-config xxx –includes 打印出该库需要的头文件信息 -L
pkg-config xxx –exists 判断是否存在,echo $? 可以查看返回值
pkg-config –list-all 列出当前系统库所有共享库文件

例:config.m4 源码

dnl 动态将秘钥加入到头文件 
dnl @TODO 修改
AC_DEFINE(ENCKEY, "123ert&*^uIy*Uy3", the key for encrpyt passwd)
AC_DEFINE(ENCIV,  "1234qwer&*^&yu6T", the iv for encrpyt passwd )

PHP_ARG_WITH(openssl, for OpenSSL support,
[  --with-openssl[=DIR]      Include OpenSSL support (requires OpenSSL >= 1.0.1)])

dnl Otherwise use enable:
PHP_ARG_ENABLE(Safemysqli, whether to enable Safemysqli support,
dnl Make sure that the comment is aligned:
[  --enable-Safemysqli           Enable Safemysqli support])

if test "$PHP_SAFEMYSQLI" != "no"; then

  SEARCH_FOR="include/openssl/evp.h"  # you most likely want to change this
  if test -r $PHP_OPENSSL/$SEARCH_FOR; then # path given as parameter
    dnl search in --with-openssl
    OPENSSL_DIR=$PHP_OPENSSL
    PHP_ADD_INCLUDE($OPENSSL_DIR/include)
    LIBNAME=crypto # you may want to change this
    LIBSYMBOL=EVP_CIPHER_CTX_init # you most likely want to change this 
    PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
    [
      PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $OPENSSL_DIR/lib, OPENSSL_SHARED_LIBADD)
      AC_DEFINE(HAVE_OPENSSLLIB,1,[ ])
    ],[
      AC_MSG_ERROR([wrong Openssl lib version or lib not found])
    ],[
      -L$OPENSSL_DIR/lib -l$LIBNAME
    ])
  else # search default path list
    dnl search in system path
    for i in /usr/local /usr; do
      if test -r $i/$SEARCH_FOR; then
        OPENSSL_DIR=$PHP_OPENSSL=$i
        AC_MSG_RESULT(found in $i)
        break
      fi
    done

    if test -z "$OPENSSL_DIR"; then
      AC_MSG_RESULT([not found])
      AC_MSG_ERROR([Please reinstall the openssl distribution])
    fi

    PHP_ADD_INCLUDE($OPENSSL_DIR/include)

    AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
    AC_MSG_CHECKING(for libopenssl)
    if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists openssl; then
      if $PKG_CONFIG openssl --atleast-version 1.0.0; then
        LIBOPENSSL_CFLAGS=`$PKG_CONFIG openssl --cflags`
        LIBOPENSSL_LIBDIR=`$PKG_CONFIG openssl --libs`
        LIBOPENSSL_VERSON=`$PKG_CONFIG openssl --modversion`
        AC_MSG_RESULT(from pkgconfig: version $LIBOPENSSL_VERSON)
      else
        AC_MSG_ERROR(system libopenssl is too old: version 1.0.0 required)
      fi
    else
      AC_MSG_ERROR(pkg-config not found)
    fi
    PHP_EVAL_LIBLINE($LIBOPENSSL_LIBDIR, SAFEMYSQLI_SHARED_LIBADD)
    PHP_EVAL_INCLINE($LIBOPENSSL_CFLAGS)
  fi

  PHP_SUBST(SAFEMYSQLI_SHARED_LIBADD)

  PHP_NEW_EXTENSION(Safemysqli, Safemysqli.c enc.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
  PHP_INSTALL_HEADERS([ext/Safemysqli/php_Safemysqli.h])
fi

编译命令
/usr/local/php/bin/phpize
./configure –with-php-config=/usr/local/php/bin/php-config –with-openssl=/usr/local/Cellar/openssl/1.0.2t/


阅读:720
搜索
  • Linux 高性能网络编程库 Libevent 简介和示例 2678
  • web rtc 学习笔记(一) 2591
  • react 学习笔记(一) 2490
  • Mac系统编译PHP7【20190929更新】 2388
  • zksync 和 layer2 2369
  • Hadoop Map Reduce 案例:好友推荐 2287
  • Hadoop 高可用集群搭建 (Hadoop HA) 2275
  • 小白鼠问题 2212
  • Linux 常用命令 2176
  • 安徽黄山游 2152
简介
不定期分享软件开发经验,生活经验