php扩展开发

一、扩展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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值