存档

文章标签 ‘php扩展函数’

PHP扩展开发之扩展函数

2015年9月22日 没有评论

本来想着看了前面的文章,大家对php扩展开发中写自己的扩展函数会有一个很清晰的了解.但后来自己又梳理了一番,发现有几个地方可能还是需要展开一下的,这样会对扩展函数的开发有一个更深层次的理解.首先我们先来写一个简单的求和的php扩展函数.


PHP_FUNCTION(king_sum){
 long num1,num2,sum;
 if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC,"ll",&num1,&num2) == FAILURE){
 RETURN_FALSE();
 }
 sum = num1+num2;
 RETURN_LONG(sum);
}

代码很简单,接受2个参数,然后求和返回结果.接下来我们看看这里面都用到那些知识点.

PHP_FUNCTION宏

虽然上一篇在GDB里面已经展开说过这个宏了,这里还是要再展开一下,多了一些东西.


//main/php.h
#define PHP_FUNCTION ZEND_FUNCTION

//Zend/zend_API.h
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

//Zend/zend.h
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

上面PHP_FUNCTION(king_sum)最终的展开结果是”void zif_king_sum(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)”,除了函数名没有其他的变化(如果你对宏不是很清楚,可以google一下C语言宏).宏展开的过程这里面就不再多说了,今天注意一下宏”INTERNAL_FUNCTION_PARAMETERS”,里面几个参数是什么意思哪?我们来看一下官方的文档.

INTERNAL_FUNCTION_PARAMETERS
名称和类型 描述 访问宏
int ht 用户实际传递参数的数量 ZEND_NUM_ARGS()
zval *return_value PHP 变量的指针,可填充返回值传递给用户。默认值是 IS_NULL RETVAL_*, RETURN_*
zval **return_value_ptr 当返回引用时,PHP 将其设为变量的指针。不建议返回引用。
zval *this_ptr 假如这是一个方法调用,其指向存放 $this对象的 PHP 变量。 getThis()
int return_value_used 指示返回值是否会被调用者使用的标志。

这几个参数的作用上面已经说的很清楚了.ht这个参数我们在解析参数时会遇到,前面的几篇文章里面有传参时,我们都会看到ZEND_NUM_ARGS()宏,return_value参数是我们目前写扩展函数中要经常使用到的,下面会在函数返回值时候提到操作相关函数的宏.this_ptr是调用类的时候才会用到的,再开始了解PHP面向对象相关的知识以前,我们是很少会遇到它的.return_value_used是内核传递进来的一个参数,脚本里面调用函数的时候返回值是否使用.

从PHP_FUNCTION这个宏可以看出,扩展函数展开以后都是void类型的,而且函数声明中我们也就看到一个PHP函数传参相关的,其余都是和返回相关的。由于前面用了一整篇来写扩展函数的传参,所以这里我们就不在展开,没有看到的朋友可以回头看一下php扩展开发之传参.

变量修改

在最初的 文章php弱类型的实现中,我们就已经知道了php中的变量最终是通过zval来存储的,相信通过这几篇文章中的例子,大家也可以看出来一下。php在创建变量的时候需要申请内存空间和一些初始化。在申请空间的时候,php提供了一套自己的函数,通过“emalloc,ecalloc,erealloc,efree”这些函数申请的内存,php会进行自己的垃圾回收,有php自身的一套垃圾回收机制.就算你忘记efree了,在请求接受以后这部分内存也会被释放掉.但是你需要注意的是如果你的php-fpm是以静态模式运行的时候,php模块初始化部分的内存是不会释放掉的,这对高并发的网站来说有效的减少了php内存的申请和释放的次数,大大的提高了php的运行效率。但是如果你的内存只有1G而且跑的还有其他运行程序,那么你以这种方式来跑php可能是灾难性的,很容易就把系统的内存资源耗尽。同时需要注意的有使用emalloc申请的内存要用efree去释放,malloc申请的内存要用free去释放。因为efree释放的内存情况上面它是把占用的内存交还给了php的内存管理,方便其他系统调用。关于php的内存管理这里就不在展开,以后如果有机会,会再详细的谈一谈。

zval的内存申请和初始化时,php也提供了几个相关的宏来方便我们操作:


//Zend/zend.h
#define MAKE_STD_ZVAL(zv) \
 ALLOC_ZVAL(zv); \
 INIT_PZVAL(zv);

#define ALLOC_INIT_ZVAL(zp) \
 ALLOC_ZVAL(zp); \
 INIT_ZVAL(*zp);

#define INIT_PZVAL(z) \
 (z)->refcount__gc = 1; \
 (z)->is_ref__gc = 0;

#define INIT_ZVAL(z) z = zval_used_for_init;

//Zend/zend_gc.h
#define ALLOC_ZVAL(z) \
 do { \
 (z) = (zval*)emalloc(sizeof(zval_gc_info)); \
 GC_ZVAL_INIT(z); \
 } while (0)

用这些php提供的宏我们可以很容易的创建出一个变量,那么怎么去给这些变量赋值那?

zval要处理所有php变量,其最终存储字符串/数字/对象/数组等数据是靠的zvalue_value联合体。


typedef union _zvalue_value {
 long lval; /* long value */
 double dval; /* double value */
 struct {
 char *val;
 int len;
 } str;
 HashTable *ht; /* hash table value */
 zend_object_value obj;
 zend_ast *ast;
} zvalue_value;

如果我们自己直接对这个联合体进行赋值操作,那么我们需要根据真实情况进行复杂的操作,这是让人很头疼的问题。不过Zend内核也为我们提供了一系列的宏来解决这个问题。


#define ZVAL_RESOURCE(z, l) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_RESOURCE;\
 } while (0)

#define ZVAL_BOOL(z, b) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = ((b) != 0); \
 Z_TYPE_P(__z) = IS_BOOL; \
 } while (0)

#define ZVAL_NULL(z) { \
 Z_TYPE_P(z) = IS_NULL; \
 }

#define ZVAL_LONG(z, l) { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_LONG; \
 }

#define ZVAL_DOUBLE(z, d) { \
 zval *__z = (z); \
 Z_DVAL_P(__z) = d; \
 Z_TYPE_P(__z) = IS_DOUBLE; \
 }

#define ZVAL_STRING(z, s, duplicate) do { \
 const char *__s=(s); \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = strlen(__s); \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, Z_STRLEN_P(__z)):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_STRINGL(z, s, l, duplicate) do { \
 const char *__s=(s); int __l=l; \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = __l; \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, __l):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_EMPTY_STRING(z) do { \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = 0; \
 Z_STRVAL_P(__z) = STR_EMPTY_ALLOC();\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_ZVAL(z, zv, copy, dtor) do { \
 zval *__z = (z); \
 zval *__zv = (zv); \
 ZVAL_COPY_VALUE(__z, __zv); \
 if (copy) { \
 zval_copy_ctor(__z); \
 } \
 if (dtor) { \
 if (!copy) { \
 ZVAL_NULL(__zv); \
 } \
 zval_ptr_dtor(&__zv); \
 } \
 } while (0)

宏函数虽然不少,但是其命名很规范,都是ZVAL_*的格式,很容易记忆的,都是对应相应的数据类型。我们来看一下用这些宏创建变量和赋值的例子吧。


zval *name,*age;

MAKE_STD_ZVAL(name);

MAKE_STD_ZVAL(age);

ZVAL_STRING(name,"King",1);

ZVAL_LONG(age,25);

函数调用

我们写php代码的时候,经常要调用自己写的其他函数.同理如果其他的我们需要调用其他的扩展函数时,我们没必要再把扩展函数代码赋值一份过来调用,Zend内核提供了相应的方法来让我们调用其他扩展函数。但是今天这个部分就先跳过去,后面我会在花费一个整篇文章来介绍这个话题。

函数体中都是C/C++代码,这也没什么好说的。如果你对这些知识欠缺,自己找些C/C++的书籍补一补。

函数返回

从上面PHP_FUNCTION宏展开中,我们可以看到扩展函数被声明成void,是没有返回内容的,但是扩展里面传递进来了2个指针,return_value和return_value_ptr.这不像我们写PHP函数,一般函数结果都是直接返回的,而扩展函数毕竟是要由C/C++开发出来,所以难免会类似C/C++的风格,熟悉C/C++编程的朋友对这些肯定不会陌生。这是Zend内核设计的模式,我们做为开发者也没有办法去更改他,只能适应这个模式。

在Zend/zend_API.h中,我们可以看到一些操作return_value相关的宏RETVAL_*和RETURN_*,其实RETURN_*是对RETVAL_*的封装.


#define RETVAL_RESOURCE(l) ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b) ZVAL_BOOL(return_value, b)
#define RETVAL_NULL() ZVAL_NULL(return_value)
#define RETVAL_LONG(l) ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d) ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate) ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING() ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor) ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE ZVAL_BOOL(return_value, 1)

#define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
#define RETURN_NULL() { RETVAL_NULL(); return;}
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE { RETVAL_FALSE; return; }
#define RETURN_TRUE { RETVAL_TRUE; return; }

RETURN_*宏是对RETVAL_*宏的二次封装,而RETVAL_*宏是对return_value支持的操作,而return_value指针前面我们已经提过它是我们返回结果的重要手段之一.

模块载入函数

当然如果你这写到这里就以为结束了,然后去动手编译你的扩展,你会看到提示你这个方法不存在的。那是因为C扩展和PHP语言之间有一个zend_module_entry,里面有个zend_function_entry数组,这里面是记录我们当前扩展中有那些php函数的。如果你是手动创建的话需要自己来填写这个模块信息的变量,zend_module_entry内容很多,我们可以按照ext_skel工具生成的模板来填写。

zend_module_entry king_module_entry = {
 STANDARD_MODULE_HEADER, //标准的一个结构体头部
 "king",//模块名称
 king_functions,//zend_function_entry数组,保存扩展php函数的数组
 PHP_MINIT(king),//模块初始化函数,没有具体的需要做的可以为NULL
 PHP_MSHUTDOWN(king),//模块关闭调用函数,没有具体的需要做的可以为NULL
 PHP_RINIT(king), //模块运行前调用函数,跟在PHP_MINIT函数之后,
 PHP_RSHUTDOWN(king), /* Replace with NULL if there's nothing to do at request end, */
 PHP_MINFO(king),//
 PHP_KING_VERSION,//这个地方是我们扩展的版本,版本信息
 STANDARD_MODULE_PROPERTIES
};

const zend_function_entry king_functions[] = {
 PHP_FE(confirm_king_compiled, NULL) /* For testing, remove later. */
 PHP_FE(king_sum,king_sum_arginfo)//添加我们自己的函数,前面是调用函数,后面是参数信息
 PHP_FE_END /* Must be the last line in king_functions[] */
};

//king_sum函数参数信息
ZEND_BEGIN_ARG_INFO(king_sum_arginfo,1)
 ZEND_ARG_INFO(0,king_sum_num1)
 ZEND_ARG_INFO(0,king_sum_num2)
ZEND_END_ARG_INFO()

//
typedef struct _zend_arg_info {
 const char *name;
 zend_uint name_len;
 const char *class_name;
 zend_uint class_name_len;
 zend_uchar type_hint;
 zend_uchar pass_by_reference;
 zend_bool allow_null;
 zend_bool is_variadic;
} zend_arg_info;

//位置main/php.h
#define PHP_FE ZEND_FE
//位置Zend/zebd_API.h
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },

上面的代码中我们初始化了一个名为king_module_entry的zend_module_entry结构体,里面第三行的参数为当前模块函数数组。king_functions是一个zend_function_entry数组,我们可以使用PHP_FE宏往数组里面添加函数,第一个参数为函数名字,第二个参数为参数信息.参数信息是一个zend_arg_info数组:


typedef struct _zend_arg_info {
const char *name; //参数名称
zend_uint name_len;
const char *class_name;
zend_uint class_name_len;
zend_uchar type_hint;
zend_uchar pass_by_reference; //是否引用传递
zend_bool allow_null;
zend_bool is_variadic;
} zend_arg_info;

同样php也提供了一些宏来填充这些数组,下面我们来看一下这些宏:


#define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, sizeof(#name)-1, NULL, 0, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 1 },

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
static const zend_arg_info name[] = { \
{ NULL, 0, NULL, required_num_args, 0, return_reference, 0, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() };

ZEND_BEGIN_ARG_INFO是一个通用的结构体头部宏,要和ZEND_END_ARG_INFO配套使用.ZEND_ARG_INFO提供了否引用和参数名称的设置,pass_by_ref为是否引用,0为否,1为是,后面的name 为参数名称。第二个ZEND_ARG_PASS_INFO提供对参数是否引用的设置,参数具体值同上.后面的宏就不仔细介绍了,可以根据其名称推断出来其作用。

完成了这些,我们才算完成了一个完整的PHP扩展。我们可以按照前面文章进行编译和安装,然后进行测试了.

分类: PHP 标签: ,