一、扩展ini配置参数
本段参考:
https://blog.csdn.net/barfoo/article/details/1550913
https://blog.csdn.net/chichou0337/article/details/100721024
1 ini参数定义
扩展可以支持在 php.ini中提供一些参数,来控制扩展的行为,那么这些参数是在扩展中是怎么定义的呢?
所有的配置项参数都必须在PHP_INI_BEGIN() 和 PHP_INI_END()之间,每个配置项则通过 PHP_INI_ENTRY 宏来定义,示例如下
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("myecho.number", "100", PHP_INI_ALL, OnUpdateLong, global_number, zend_myecho_globals, myecho_globals)
STD_PHP_INI_ENTRY("myecho.string", "ab", PHP_INI_ALL, OnUpdateString, global_string, zend_myecho_globals, myecho_globals)
STD_PHP_INI_ENTRY("myecho.boolean", "0", PHP_INI_ALL, OnUpdateBool, boolean, zend_myecho_globals, myecho_globals)
PHP_INI_END()
上述的例子宏展开后的结果如下:
static zend_ini_entry ini_entries[] = { // BEGIN 的定义
{ 0, PHP_INI_ALL, "foo_bar.global_value", sizeof("foo_bar.global_value"), NULL, NULL, NULL, null, "42", sizeof("42")-1, NULL, 0, 0, NULL},
{ 0, PHP_INI_ALL, "foo_bar.global_string", sizeof("foo_bar.global_string"), NULL, NULL, NULL, null, "foobar", sizeof("foobar")-1, NULL, 0, 0, NULL},
{ 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, NULL } } ; // END的定义
可见所有的ini变量都被放到了一个 zend_ini_entry类型的数组中,数组名为 ini_entries。
此时只是定义了这么一个变量,为了能够使用它,需要在MINIT的方法内部进行调用,使用REGISTER_INI_ENTRIES()完成,这个宏的定义如下:
#define REGISTER_INI_ENTRIES() zend_register_ini_entries(ini_entries, module_number TSRMLS_CC)
其实就是直接调用了 zend_register_ini_entries() 进行了注册,并且强制使用了TSRM的方式。
为了保证干净的使用这些ini变量,在MSHUTDOWN时需要对已经注册的ini变量进行清除,使用UNREGISTER_INI_ENTRIES()宏完成,定义如下:
#define UNREGISTER_INI_ENTRIES() zend_unregister_ini_entries(module_number TSRMLS_CC)
二、模块全局变量
本段参考:
https://blog.csdn.net/toozy/article/details/12237271
1 什么是模块全局变量
每个PHP扩展都可以定义自己的全局变量,这些变量可以成为扩展全局变量或者模块全局变量,模块全局变量在模块初始化(PHP_MINIT_FUNCTION(extname))时定义,到模块注销(PHP_MSHUTDOWN_FUNCTION(extname))时同步销毁。在fpm模式下,每个fpm进程所处理的所有请求共享模块全局变量,不同fpm请求的模块全局变量互相独立。
2 模块变量类型的定义
PHP为扩展全局变量的实现进行了良好的封装,每个扩展将自己所有的全局变量统一定义在一个结构体中,然后将这个结构体注册到TSRM中,这样扩展就可以像使用EG、CG那样访问这个结构体。
定义:
// extname 改为实际的扩展名
PHP_BEGIN_MODULE_GLOBALS(extname)
// 这里罗列全部的全局变量
// 给出字段类型和字段名
std::string name;
std::string version;
PHP_END_MODULE_GLOBALS(extname)
上面两个宏定义如下
// PHP_BEGIN_MODULE_GLOBALS 宏定义
#define ZEND_BEGIN_MODULE_GLOBALS(module_name) \
typedef struct _zend_##module_name##_globals {
// PHP_END_MODULE_GLOBALS 宏定义
#define ZEND_END_MODULE_GLOBALS(module_name) \
} zend_##module_name##_globals;
所以上面的模块变量定义展开来就是:
typedef struct _zend_extname_globals {
std::string name;
std::string version;
} zend_extname_globals;
可见模块变量是定义为 zend_extname_globals 类型。
3 模块变量的定义
在模块文件的顶部加上:
ZEND_DECLARE_MODULE_GLOBALS(extname)
ZEND_DECLARE_MODULE_GLOBALS 宏在线程安全的环境下的定义为:
#define ZEND_DECLARE_MODULE_GLOBALS(module_name)
ts_rsrc_id module_name##_globals_id;
也就是,在线程安全下,模块变量的定义,展开来是定义了一个 名为extname_globals_id 的 ts_rsrc_id 类型的变量,改变量用于定位模块变量的存取位置,后面会讲到。
ZEND_DECLARE_MODULE_GLOBALS 宏在非线程安全下的定义为:
#define ZEND_DECLARE_MODULE_GLOBALS(module_name)
zend_##module_name##_globals module_name##_globals;
展开来就是定义了一个类型为该模块变量结构体的变量 extname_globals。可以通过该变量直接存取模块全局变量。
另外还有一个宏: ZEND_EXTERN_MODULE_GLOBALS(module_name) ,用于在其他文件声明模块全局变量。
4 模块全局变量初始化
在PHP_MINIT_FUNCTION(extname){ //这里} 里面:
ZEND_INIT_MODULE_GLOBALS(extname,php_extname_init_globals_ctor,php_extname_init_globals_dtor)
ZEND_INIT_MODULE_GLOBALS(extname,globals_ctor,global_dtor) 的线程安全下的定义为:
#define ZEND_INIT_MODULE_GLOBALS(module_name,globals_ctor,globals_dtor)
ts_allocate_id(&module_name##globals_id,sizeof(zend##module_name##_globals),(ts_allocate_ctor) globals_ctor,(ts_allocate_dtor) globals_dtor);
也就是初始化 extname_globals_id。
ZEND_INIT_MODULE_GLOBALS(extname,globals_ctor,global_dtor) 的非线程安全下的定义为:
#define ZEND_INIT_MODULE_GLOBALS(module_name,globals_ctor,globals_dtor)
globals_ctor(&module_name##_globals);
在非线程下,只是简单的调用global_ctor来初始化 extname_globals
其中的global_ctor 是初始化模块全局变量函数,global_dtor是注销模块全局变量函数。他们可以在PHP_MINIT_FUNCTION调用前像下面那样子定义:
static void php_extname_init_globals_ctor(zend_extname_globals *extname_globals TSRMLS_DC){
extname_globals->name = “extname”;
extname_globals->version = “v0.1”;
}
static void php_extname_init_globals_dtor(zend_extname_globals *extname_globals TSRMLS_DC){
//如果模块有申请堆空间的话,在这里释放
}
另外,根据我们看到的,非线程情况下,我们需要在PHP_MSHUTDOWN_FUNCTION 里手动调用php_extname_init_globals_dtor,以释放资源。
5 模块全局变量的存取
为了方便存取,我们可以定义EXTNAME(name)宏来统一在线程或非线程下的存取。
#ifdef ZTS
#define EXTNAME_G(name) TSRMG(extname_globals_id,zend_extname_globals*,name)
#else
#define EXTNAME_G(name) (extname_globals.name)
#endif
然后,就可以通过,EXTNAME_G(name) ,EXTNAME_G(version)来存取模块全局变量了。
四、php的字符串管理 zend_string
https://www.php.cn/php-weizijiaocheng-456945.html
五、扩展相关函数
5.1 常量
https://www.cnblogs.com/orlion/p/5344135.html
https://blog.csdn.net/chichou0337/article/details/100721028
https://blog.csdn.net/u013756836/article/details/106721158/
字符串相关
void zend_str_tolower(char *str, size_t len); // 转换字符串 char * 为小写
zend_string* zend_string_tolower(zend_string *str); // 转换字符串 zend_string * 为小写
zend_string* php_string_tolower(zend_string *str); // 功能跟上面一样,是 strtolower 的原型
zend_string* php_string_toupper(zend_string *str); // strtoupper 的原型 // 格式化成 zend_string *, 使用完记得 zend_string_release
zend_string *strpprintf(size_t max_len, const char *format, …); // 格式化成 char *, 使用完记得 efree
size_t spprintf( char **pbuf, size_t max_len, const char format, …); smart_str_ // 待分析
zend_string *zval_get_string(zval *val); // 从 zval 转换为 zend_string 并 COPY 一份
数组相关
uint32_t zend_hash_num_elements(HashTable *ht); // 获取数组大小
zval* zend_hash_find(HashTable *ht, zend_string *key); // 根据 zend_string * 作为 key 查找数组
zval* zend_hash_str_find(HashTable *ht, char *str, size_t len); // 根据 char * 作为 key 查找数组
zval* zend_hash_index_find(HashTable *ht, zend_ulong h); // 查找索引 h 的数组元素
void* zend_hash_find_ptr(HashTable *ht, zend_string *key); // 同上,只是返回元素指针指向的值
void* zend_hash_str_find_ptr(HashTable *ht, char *str, size_t len); // 跟上同类
void* zend_hash_index_find_ptr(HashTable *ht, zend_ulong h); // 跟上同类
zend_bool zend_hash_exists(HashTable *ht, zend_string *key); // zend_string * key 是否存在
zend_bool zend_hash_str_exists(HashTable *ht, char *str, size_t len); // char * key 是否存在
zend_bool zend_hash_index_exists(HashTable *ht, zend_ulong h); // 索引 h 是否存在
zend_array *HASH_OF(zval *val); // 其实 HASH_OF 是一个宏,参数 value 可以是数组 IS_ARRAY
或者对象 IS_OBJECT
,否则返回 NULL
方法和函数相关
zval* zend_call_method(zval *object_pp, zend_class_entry *obj_ce,
zend_function **fn_proxy,
const char *function_name,
size_t function_name_len,
zval *retval, int param_count,
zval* arg1, zval* arg2); // 调用类实例成员方法
zval* zend_call_method_with_0_params(
zval *object_pp,
zend_class_entry *obj_ce,
zend_function **fn_proxy,
const char *function_name,
zval *retval); // zend_call_method 的缩写,不传递参数 // 同类还有 zend_call_method_with_1_params 和 zend_call_method_with_2_params
输出
void php_var_dump(zval *struc, int level); // 类似 var_dump 方法, 需要引入 ext/standard/php_var.h
size_t php_printf(const char *format, …); // 格式化输出到 PHP
size_t php_output_write(const char *str, size_t len); // 等同于 PHPWRITE 宏
六、类
在 PHP 扩展中创建一个类
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Person", NULL);
person_ce = zend_register_internal_class(&ce);
这个代码段会定义一个 Person 类,并在 PHP 内核中注册它。
在类中添加一个方法
PHP_METHOD(Person, handle)
{
// Your code here
}
这段代码会定义一个名为 handle() 的方法,它属于 Person 类。
注册方法
zend_function_entry person_methods[] = {
PHP_ME(Person, handle, NULL, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};
zend_class_entry *person_ce;
INIT_CLASS_ENTRY(ce, "Person", person_methods);
person_ce = zend_register_internal_class(&ce);
这段代码将 handle() 方法注册到 Person 类中,并将 Person 类注册到 PHP 内核中。
现在,您就可以在 PHP 扩展中使用 Person 类的对象了。例如,在扩展中创建一个 Person 类的对象:
zend_object *person_obj;
person_obj = zend_objects_new(&person_ce);
这就是在 PHP 扩展中定义一个类的基本步骤。
INIT_NS_CLASS_ENTRY 是一个可以方便地在 PHP 扩展中定义命名空间类的宏。它的定义如下:
#define INIT_NS_CLASS_ENTRY(ce, ns, name, methods) \
INIT_CLASS_ENTRY(ce, ns #name, methods)
通过这个宏定义,我们可以更加简洁地定义命名空间类,而无需使用拼接字符串的方式。具体用法如下:
zend_class_entry ce;
INIT_NS_CLASS_ENTRY(ce, "MyNamespace\\SubNamespace", "MyClass", NULL);
my_class_ce = zend_register_internal_class(&ce TSRMLS_CC);
在上面的例子中,我们使用 INIT_NS_CLASS_ENTRY 宏定义了一个名为 MyClass 的命名空间类 MyNamespace\SubNamespace\MyClass。这个命名空间类位于 MyNamespace 命名空间下的 SubNamespace 子命名空间内。NULL 表示这个类没有任何方法。
在定义命名空间类之后,我们可以使用 zend_register_internal_class() 将其注册到 PHP 内核中。
七、扩展版本号
扩展采用版本号来管理,基本上扩展版本号会被定义为一个包含 “_VERSION” 的字符串常量,常量可能定义在m5、h或者c文件中
# swool 扩展版本号定义
swoole-4.5.8/include/swoole_version.h:25: #define SWOOLE_VERSION "4.5.8"
# redis扩展版本号定义
redis-5.3.2/php_redis.h:26: #define PHP_REDIS_VERSION "5.3.2"
执行 php --ri 扩展名
可以了解扩展具体信息
php --ri redis
八、开发扩展
php源码中提供了生成扩展骨架代码的脚本,脚本位于php源码 ext子目录中,早期脚本名为 ext_skel,从 php 7.3 开始脚本名 ext_skel.php ,下面是生成扩展的命令示例
./ext_skel.php --ext core_test --author andler.yang
注意,
1、源码下载后,即可执行上面的命令,不需要预先执行 ./configure 命令构建php
2、源码的php版本和系统所安装的php次版本号需要相同,比如源码是 7.1,那么php系统也必须是7.1,否则./ext_skel.php 执行后会报下面的错误
env: php\r: No such file or directory
/path/of/phpize
./configure --with-php-config=/path/of/php-config
make && make install
https://www.jianshu.com/p/f12d90b71776
E_CORE_ERROR
https://blog.csdn.net/weixin_39601657/article/details/116287662
九 扩展开发过程可能遇到的问题
8.1 configure 报 Cannot find php-config
执行完 ./configure 命令,如果报下面的错误
configure: error: Cannot find php-config. Please use --with-php-config=PATH
表示找不到 php-config,需要加上 --with-php-config=PATH,PATH代表php-config 的绝对路径,通常和 php命令在一个目录,下面是一个示例
./configure --with-php-config=/usr/local/php/bin/php-config