创作人 Leo
编辑时间 Sun Nov 29,2020 at 09:30
创建扩展
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
zval *__z = (z);
zend_string *__s = (s);
#define Z_STR(zval) (zval).value.str
#define Z_STR_P(zval_p) Z_STR(*(zval_p))
#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类型
四种标量类型: boolean(布尔型) integer(整型) float(浮点型,也称作 double) string(字符串)
三种复合类型: array(数组) object(对象) callable(可调用)
最后是两种特殊类型: 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)
strg = strpprintf(0, “Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.”, “Safemysqli”, arg); 格式化字符串
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 可以修改函数名字
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(¶ms[0],hostname);
}
if(pass_parm_n>1) {
ZVAL_STRING(¶ms[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(¶ms[2],passwd);
}
if(pass_parm_n>3) {
ZVAL_STRING(¶ms[3],dbname);
}
if(pass_parm_n>4) {
ZVAL_LONG(¶ms[4],port);
}
if(pass_parm_n>5) {
ZVAL_STRING(¶ms[5],socket);
}
if(pass_parm_n>6) {
ZVAL_LONG(¶ms[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;
}
}
/* */
基于 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 列出当前系统库所有共享库文件
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/