PHP扩展开发教程(总结)

PHP是一种解释型的语言,对于用户而言,我们精心的控制内存意味着easier prototyping和更少的崩溃!当我们深入到内核之后,所有的安全防线都已经被越过,最终还是要依赖于真正有责任心的软件工程师来保证系统的稳定运行。

1、线程安全宏定义

在TSRM/TSRM.h文件中有如下定义

#define TSRMLS_FETCH()       void ***tsrm_ls = (void ***) ts_resource_ex(0, NULL)
#define TSRMLS_FETCH_FROM_CTX(ctx) void ***tsrm_ls = (void ***) ctx
#define TSRMLS_SET_CTX(ctx)   ctx = (void ***) tsrm_ls
#define TSRMG(id, type, element)   (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
#define TSRMLS_D   void ***tsrm_ls
#define TSRMLS_DC  , TSRMLS_D
#define TSRMLS_C   tsrm_ls
#define TSRMLS_CC  , TSRMLS_C

在ext/xsl/php_xsl.h有这么一段话

/* In every utility function you add that needs to use variables.                                                                    
   in php_xsl_globals, call TSRM_FETCH(); after declaring other.
   variables used by that function, or better yet, pass in TSRMLS_CC
   after the last function argument and declare your utility function
   with TSRMLS_DC after the last declared argument.  Always refer to
   the globals in your function as XSL_G(variable).  You are.
   encouraged to rename these macros something shorter, see
   examples in any other php module directory.
*/

1.在方法定义时加上TSRMLS_D(如果方法没有参数用这个)或者TSRMLS_DC(有1个以上的参数)

2.在方法调用时用TSRMLS_C(如果方法没有参数用这个)或者TSRMLS_CC(有1个以上的参数)

应该可以这样理解

第一个后缀字母D表示定义,即D=Define,第一个后缀字母C表示调用,即C=Call,而第二个后缀字母C是不是表示逗号呢? C=Comma (逗号)

TSRMLS_D就是定义了,所以是  void ***tsrm_ls

TSRMLS_DC是带逗号的定义,所以是 , void ***tsrm_ls

TSRMLS_C是调用,即tsrm_ls

TSRMLS_CC是调用并带逗号,即 ,tsrm_ls

所以一个是形参、一个是实参

可以这样使用

int php_myext_action(int action_id, char *message TSRMLS_DC);
php_myext_action(42, "The meaning of life" TSRMLS_CC);

一般推荐使用tsrm_ls指针定义的方式来保证线程安全

TSRMLS_FETCH调用需要一定的处理时间。这在单次迭代中并不明显,但是随着你的线程数增多,随着你调用TSRMLS_FETCH()的点的增多,你的扩展就会显现出这个瓶颈。因此,请谨慎的使用它。 注意:为了和c++编译器兼容,请确保将TSRMLS_FETCH()和所有变量定义放在给定块作用域的顶部(任何其他语句之前)。因为TSRMLS_FETCH()宏自身有多种不同的解析方式,因此最好将它作为变量定义的最后一行

2、PHP的生命周期

PHP的最多的两种运行模式是WEB模式、CLI模式,无论哪种模式,PHP工作原理都是一样的,作为一种SAPI运行。

1、当我们在终端敲入php这个命令的时候,它使用的是CLI。

它就像一个web服务器一样来支持php完成这个请求,请求完成后再重新把控制权交给终端。

2、当使用Apache作为宿主时,当一个请求到来时,PHP会来支持完成这个请求

PHP_MINIT_FUNCTION  初始化module时运行 
PHP_MSHUTDOWN_FUNCTION  当module被卸载时运行 
PHP_RINIT_FUNCTION  当一个REQUEST请求初始化时运行 
PHP_RSHUTDOWN_FUNCTION  当一个REQUEST请求结束时运行 
PHP_MINFO_FUNCTION  这个是设置phpinfo中这个模块的信息 
PHP_GINIT_FUNCTION  初始化全局变量时 
PHP_GSHUTDOWN_FUNCTION  释放全局变量时

比如PHP_GINIT_FUNCTION

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

PHP_GINIT_FUNCTION(test)

{

  /** 初始化全局变量 */

}

//对应的C代码

void zm_globals_ctor_test (zend_test_globals *test_globals TSRMLS_DC)

{

  /** 初始化全局变量 */

}

//在线程退出时,需要将之前自己申请的资源释放时,可以使用 PHP_GSHUTDOWN_FUNCTION来注册析构函数。

PHP_GSHUTDOWN_FUNCTION(test)

{

  /** 清除全局变量 */

}

//对应的C代码

void zm_globals_dtor_test (zend_test_globals *test_globals TSRMLS_DC)

{

  /** 清除全局变量 */

}

这里有一段代码,可以测试一下

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

int minit_time;

PHP_MINIT_FUNCTION(test)

{

  minit_time = time(NULL);

  return SUCCESS;

}

PHP_MSHUTDOWN_FUNCTION(test)

{

  FILE *fp=fopen("mshutdown.txt","a+");

  fprintf(fp,"%ld\n",time(NULL));

  fclose(fp);

  return SUCCESS;

}

int rinit_time;

PHP_RINIT_FUNCTION(test)

{

  rinit_time = time(NULL);

  return SUCCESS;

}

PHP_RSHUTDOWN_FUNCTION(test)

{

  FILE *fp=fopen("rshutdown.txt","a+");

  fprintf(fp,"%ld\n",time(NULL));

  fclose(fp);

  return SUCCESS;

}

PHP_MINFO_FUNCTION(test)

{

  php_info_print_table_start();

  php_info_print_table_header(, "module info", "enabled");

  php_info_print_table_end();

  /* Remove comments if you have entries in php.ini

  DISPLAY_INI_ENTRIES();

  */

}

PHP_FUNCTION(test)

{

  php_printf("%d",time_of_minit);

  php_printf("%d",time_of_rinit);

  return;

}

3、段错误调试

Linux下的C程序常常会因为内存访问错误等原因造成segment fault(段错误)此时如果系统core dump功能是打开的,那么将会有内存映像转储到硬盘上来,之后可以用gdb对core文件进行分析,还原系统发生段错误时刻的堆栈情况。这对于我们发现程序bug很有帮助。
使用ulimit -a可以查看系统core文件的大小限制;使用ulimit -c [kbytes]可以设置系统允许生成的core文件大小。

ulimit -c 0 不产生core文件
ulimit -c 100 设置core文件最大为100k
ulimit -c unlimited 不限制core文件大小

步骤:

1、当发生段错误时,我们查看ulimit -a (core file size (blocks, -c) 0)并没有文件, 
2、设置 :ulimit -c unlimited 不限制core文件大小
3、运行程序 ,发生段错误时会自动记录在core中 (php -f WorkWithArray.php)
4、ls -al core.* 在那个文件下(-rw------- 1 leconte leconte 139264 01-06 22:3 1 core.2065)
5、使用gdb 运行程序和段错误记录的文件。(gdb ./test core.2065)
6、会提哪行有错。

很多系统默认的core文件大小都是0,我们可以通过在shell的启动脚本/etc/bashrc或者~/.bashrc等地方来加入 ulimit -c 命令来指定core文件大小,从而确保core文件能够生成。
除此之外,还可以在/proc/sys/kernel/core_pattern里设置core文件的文件名模板,详情请看core的官方man手册。

4、常见的变量操作宏

CG    -> Complier Global      编译时信息,包括函数表等(zend_globals_macros.h:32)
EG    -> Executor Global      执行时信息(zend_globals_macros.h:43)
PG    -> PHP Core Global      主要存储php.ini中的信息
SG    -> SAPI Global          SAPI信息

1、SG  针对SAPI信息 在main/SAPI.h文件中

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

typedef struct _sapi_globals_struct {

  void *server_context;

  sapi_request_info request_info;

  sapi_headers_struct sapi_headers;

  int read_post_bytes;

  unsigned char headers_sent;

  struct stat global_stat;

  char *default_mimetype;

  char *default_charset;

  HashTable *rfc1867_uploaded_files;

  long post_max_size;

  int options;

  zend_bool sapi_started;

  double global_request_time;

  HashTable known_post_content_types;

  zval *callback_func;

  zend_fcall_info_cache fci_cache;

  zend_bool callback_run;

} sapi_globals_struct;

看一下SG的定义

BEGIN_EXTERN_C()
#ifdef ZTS
# define SG(v) TSRMG(sapi_globals_id, sapi_globals_struct *, v)
SAPI_API extern int sapi_globals_id;
#else
# define SG(v) (sapi_globals.v)
extern SAPI_API sapi_globals_struct sapi_globals;
#endif
SAPI_API void sapi_startup(sapi_module_struct *sf);
SAPI_API void sapi_shutdown(void);
SAPI_API void sapi_activate(TSRMLS_D);
SAPI_API void sapi_deactivate(TSRMLS_D);
SAPI_API void sapi_initialize_empty_request(TSRMLS_D);
END_EXTERN_C()

成员都在sapi_globals_struct这里了

那么我么可以这样调用

SG(default_mimetype)
SG(request_info).request_uri

可以感受一下这么一段代码

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)

{

  char buf[SAPI_CGI_MAX_HEADER_LENGTH];

  sapi_header_struct *h;

  zend_llist_position pos;

  long rfc2616_headers = 0;

  if(SG(request_info).no_headers == 1) {

    return SAPI_HEADER_SENT_SUCCESSFULLY;

  }

  if (SG(sapi_headers).http_response_code != 200) {

    int len;

    len = sprintf(buf, "Status: %d\r\n", SG(sapi_headers).http_response_code);

    PHPWRITE_H(buf, len);

  }

  if (SG(sapi_headers).send_default_content_type) {

    char *hd;

    hd = sapi_get_default_content_type(TSRMLS_C);

    PHPWRITE_H("Content-type:", sizeof("Content-type: ")-1);

    PHPWRITE_H(hd, strlen(hd));

    PHPWRITE_H("\r\n", 2);

    efree(hd);

  }

  h = zend_llist_get_first_ex(&sapi_headers->headers, &pos);

  while (h) {

    PHPWRITE_H(h->header, h->header_len);

    PHPWRITE_H("\r\n", 2);

    h = zend_llist_get_next_ex(&sapi_headers->headers, &pos);

  }

  PHPWRITE_H("\r\n", 2);

  return SAPI_HEADER_SENT_SUCCESSFULLY;

}

2、EG  Executor Globals

EG获取的是struct _zend_execution_globals结构体中的数据

?


1

2

3

4

5

6

struct _zend_execution_globals {

 ...

 HashTable symbol_table;  /* 全局作用域,如果没有进入函数内部,全局=活动 */

 HashTable *active_symbol_table; /* 活动作用域,当前作用域 */

 ...

}

通常,使用EG(symbol_table)获取的是全局作用域中的符号表,使用EG(active_symbol_table)获取的是当前作用域下的符号表

例如 来定义$foo = ‘bar‘

zval *fooval;
 
MAKE_STD_ZVAL(fooval);
ZVAL_STRING(fooval, "bar", 1);
ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", fooval);

或者从符号表中查找$foo

zval **fooval;
if(zend_hash_find(&EG(symbol_table), "foo", sizeof("foo"), (void **)&fooval) == SUCCESS) {
    RETURN_STRINGL(Z_STRVAL_PP(fooval), Z_STRLEN_PP(fooval));
} else {
    RETURN_FALSE;
}

上面的代码中,EG(active_symbol_table) == &EG(symbol_table)

3、CG() 用来访问核心全局变量。(zend/zend_globals_macros.h)

4、PG() PHP全局变量。我们知道php.ini会映射一个或者多个PHP全局结构。(main/php_globals.h)

5、FG() 文件全局变量。大多数文件I/O或相关的全局变量的数据流都塞进标准扩展出口结构。(ext/standard/file.h)

5、获取变量的类型和值

#define Z_TYPE(zval)        (zval).type
#define Z_TYPE_P(zval_p)    Z_TYPE(*zval_p)
#define Z_TYPE_PP(zval_pp)  Z_TYPE(**zval_pp)

比如获取一个变量的类型

?


1

2

3

4

5

6

7

8

9

10

11

void describe_zval(zval *foo)

{

  if ( Z_TYPE_P(foo) == IS_NULL )

  {

    php_printf("这个变量的数据类型是: NULL");

  }

  else

  {

    php_printf("这个变量的数据类型不是NULL,这种数据类型对应的数字是: %d", Z_TYPE_P(foo));

  }

}

有这么几种类型

#define IS_NULL     0
#define IS_LONG     1
#define IS_DOUBLE   2
#define IS_BOOL     3
#define IS_ARRAY    4
#define IS_OBJECT   5
#define IS_STRING   6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY   9
#define IS_CALLABLE 10

php_printf()函数是内核对printf()函数的一层封装,我们可以像使用printf()函数那样使用它,以一个P结尾的宏的参数大多是*zval型变量。 此外获取变量类型的宏还有两个,分别是Z_TYPE和Z_TYPE_PP,前者的参数是zval型,而后者的参数则是**zval

比如gettype函数的实现

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

//开始定义php语言中的函数gettype

PHP_FUNCTION(gettype)

{

  //arg间接指向调用gettype函数时所传递的参数。是一个zval**结构

  //所以我们要对他使用__PP后缀的宏。

  zval **arg;

  //这个if的操作主要是让arg指向参数~

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) {

    return;

  }

  //调用Z_TYPE_PP宏来获取arg指向zval的类型。

  //然后是一个switch结构,RETVAL_STRING宏代表这gettype函数返回的字符串类型的值

  switch (Z_TYPE_PP(arg)) {

    case IS_NULL:

      RETVAL_STRING("NULL", 1);

      break;

    case IS_BOOL:

      RETVAL_STRING("boolean", 1);

      break;

    case IS_LONG:

      RETVAL_STRING("integer", 1);

      break;

    case IS_DOUBLE:

      RETVAL_STRING("double", 1);

      break;

    case IS_STRING:

      RETVAL_STRING("string", 1);

      break;

    case IS_ARRAY:

      RETVAL_STRING("array", 1);

      break;

    case IS_OBJECT:

      RETVAL_STRING("object", 1);

      break;

    case IS_RESOURCE:

      {

        char *type_name;

        type_name = zend_rsrc_list_get_rsrc_type(Z_LVAL_PP(arg) TSRMLS_CC);

        if (type_name) {

          RETVAL_STRING("resource", 1);

          break;

        }

      }

    default:

      RETVAL_STRING("unknown type", 1);

  }

}

获取变量的值,有这么多宏来获取


Long


Boolean


Double


String value


String length

Z_LVAL( )
Z_BVAL( )
Z_DVAL( )
Z_STRVAL( )
Z_STRLEN( )
Z_LVAL_P( )
Z_BVAL_P( )
Z_DVAL_P( )
Z_STRVAL_P( )
Z_STRLEN_P( )
Z_LVAL_PP( )
Z_BVAL_PP( )
Z_DVAL_PP( )
Z_STRVAL_PP( )
Z_STRLEN_PP( )

HashTable


Object


Object properties


Object class entry


Resource value

Z_ARRVAL( )
Z_OBJ( )
Z_OBJPROP( )
Z_OBJCE( )
Z_RESVAL( )
Z_ARRVAL_P( )
Z_OBJ_P( )
Z_OBJPROP_P( )
Z_OBJCE_P( )
Z_RESVAL_P( )
Z_ARRVAL_PP( )
Z_OBJ_PP( )
Z_OBJPROP_PP( )
Z_OBJCE_PP( )
Z_RESVAL_PP( )

rot13函数的实现

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

PHP_FUNCTION(rot13)

{

 zval **arg;

 char *ch, cap;

 int i;

  

 if (ZEND_NUM_ARGS( ) != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) {

   WRONG_PARAM_COUNT;

 }

 *return_value = **arg;

 zval_copy_ctor(return_value);

 convert_to_string(return_value);

  

 for(i=0, ch=return_value->value.str.val;

   i<return_value->value.str.len; i++, ch++) {

    cap = *ch & 32;

    *ch &= ~cap;

    *ch = ((*ch>=‘A‘) && (*ch<=‘Z‘) ? ((*ch-‘A‘+13) % 26 + ‘A‘) : *ch) | cap;

  }

}

要获取变量的值,也应该使用Zend定义的宏进行访问。对于简单的标量数据类型、Boolean,long,double, 使用Z_BVAL, Z_LVAL, Z_DVAL

?


1

2

3

4

5

6

7

8

9

10

11

12

void display_values(zval boolzv, zval *longpzv, zval **doubleppzv)

{

 if (Z_TYPE(boolzv) == IS_BOOL) {

  php_printf("The value of the boolean is : %s\n", Z_BVAL(boolzv) ? "true" : "false");

 }

 if(Z_TYPE_P(longpzv) == IS_LONG) {

  php_printf("The value of the long is: %ld\n", Z_LVAL_P(longpzv));

 }

 if(Z_TYPE_PP(doubleppzv) == IS_DOUBLE) {

  php_printf("The value of the double is : %f\n", Z_DVAL_PP(doubleppzv));

 }

}

对于字符串类型,因为它含有两个字段char * (Z_STRVAL) 和 int (Z_STRLEN),因此需要用两个宏来进行取值,因为需要二进制安全的输出这个字符串

?


1

2

3

4

5

6

7

8

void display_string(zval *zstr)

{

 if (Z_TYPE_P(zstr) != IS_STRING) {

  php_printf("The wronng datatype was passed!\n");

  return ;

 }

 PHPWRITE(Z_STRVAL_P(zstr), Z_STRLEN_P(zstr));

}

因为数组在zval中是以HashTable形式存在的,因此使用Z_ARRVAL()进行访问

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

void display_zval(zval *value)

{

  switch (Z_TYPE_P(value)) {

    case IS_NULL:

      /* 如果是NULL,则不输出任何东西 */

      break;

 

    case IS_BOOL:

      /* 如果是bool类型,并且true,则输出1,否则什么也不干 */

      if (Z_BVAL_P(value)) {

        php_printf("1");

      }

      break;

    case IS_LONG:

      /* 如果是long整型,则输出数字形式 */

      php_printf("%ld", Z_LVAL_P(value));

      break;

    case IS_DOUBLE:

      /* 如果是double型,则输出浮点数 */

      php_printf("%f", Z_DVAL_P(value));

      break;

    case IS_STRING:

      /* 如果是string型,则二进制安全的输出这个字符串 */

      PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));

      break;

    case IS_RESOURCE:

      /* 如果是资源,则输出Resource #10 格式的东东 */

      php_printf("Resource #%ld", Z_RESVAL_P(value));

      break;

    case IS_ARRAY:

      /* 如果是Array,则输出Array5个字母! */

      php_printf("Array");

      break;

    case IS_OBJECT:

      php_printf("Object");

      break;

    default:

      /* Should never happen in practice,

       * but it‘s dangerous to make assumptions

       */

       php_printf("Unknown");

       break;

  }

}

一些类型转换函数

ZEND_API void convert_to_long(zval *op);
ZEND_API void convert_to_double(zval *op);
ZEND_API void convert_to_null(zval *op);
ZEND_API void convert_to_boolean(zval *op);
ZEND_API void convert_to_array(zval *op);
ZEND_API void convert_to_object(zval *op);
ZEND_API void _convert_to_string(zval *op ZEND_FILE_LINE_DC);

6、常量的实例化

我们可以这样实例化

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

PHP_MINIT_FUNCTION(consts) //模块初始化时定义常量

{

  REGISTER_LONG_CONSTANT("CONSTS_MEANING_OF_LIFE", 42, CONST_CS | CONST_PERSISTENT);

  REGISTER_DOUBLE_CONSTANT("CONSTS_PI", 3.1415926, CONST_PERSISTENT);

  REGISTER_STRING_CONSTANT("CONSTS_NAME", "leon", CONST_CS|CONST_PERSISTENT);

}

PHP_RINIT_FUNCTION(consts) //每次请求时定义常量

{

  char buffer[40];

  srand((int)time(NULL));

  snprintf(buffer, sizeof(buffer), "%d", rand());

  REGISTER_STRING_CONSTANT("CONSTS_RAND", estrdup(buffer), CONST_CS);

  return SUCCESS;

}

常见的宏

/*注册LONG类型常量*/
#define REGISTER_LONG_CONSTANT(name, lval, flags)  zend_register_long_constant((name), sizeof(name), (lval), (flags), module_number TSRMLS_CC)

/*注册double类型常量*/
#define REGISTER_DOUBLE_CONSTANT(name, dval, flags)  zend_register_double_constant((name), sizeof(name), (dval), (flags), module_number TSRMLS_CC)

/*注册STRING类型常量*/
#define REGISTER_STRING_CONSTANT(name, str, flags)  zend_register_string_constant((name), sizeof(name), (str), (flags), module_number TSRMLS_CC)

/*注册STRING类型常量*/
#define REGISTER_STRINGL_CONSTANT(name, str, len, flags)  zend_register_stringl_constant((name), sizeof(name), (str), (len), (flags), module_number TSRMLS_CC)

7、全局变量

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

#php-fpm 生成 POST|GET|COOKIE|SERVER|ENV|REQUEST|FILES全局变量的流程

php_cgi_startup() -> php_module_startup() -> php_startup_auto_globals() -> 保存变量到symbol_table符号表

php_cgi_startup()在 fpm/fpm/fpm_main.c中定义

php_module_startup() 在main/main.c中定义

php_startup_auto_globals() 在main/php_variables.h中定义

zend_hash_update(&EG(symbol_table), "_GET", sizeof("_GET") + 1, &vars, sizeof(zval *), NULL);

/* 读取$_SERVER变量 */

static PHP_FUNCTION(print_server_vars) {

  zval **val;

  if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&val) == SUCCESS) {

    RETURN_ZVAL(*val, 1, 0);

  }else{

   RETURN_FALSE;

  }

}

/* 读取$_SERVER[$name] */

ZEND_BEGIN_ARG_INFO(print_server_var_arginfo, 0)

  ZEND_ARG_INFO(0, "name")

ZEND_END_ARG_INFO()

static PHP_FUNCTION(print_server_var) {

  char *name;

  int name_len;

  zval **val;

  HashTable *ht_vars = NULL;

  HashPosition pos;

  zval **ret_val;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &name, &name_len) == FAILURE) {

    RETURN_NULL();

  }

  if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&val) == SUCCESS) {

    ht_vars = Z_ARRVAL_PP(val);

    //此处需传入大于name长度+1的值,因为字符串值后面需要‘\0‘

    if (zend_hash_find(ht_vars, name, name_len+1, (void **)&ret_val) == SUCCESS) {       RETURN_STRING(Z_STRVAL_PP(ret_val), 0);

    }else{

      RETURN_NULL();

    }

  }else{

    RETURN_NULL();

  }

}

8、包装第三方库

配置(config.m4)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

SEARCH_PATH="/usr/local /usr"   #lib搜索的目录

SEARCH_FOR="/include/curl/curl.h" #lib头文件的路径

if test -r $PHP_LIBS/$SEARCH_FOR; then

  LIBS_DIR=$PHP_LIBS

else # search default path list

  AC_MSG_CHECKING([for libs files in default path])

  for i in $SEARCH_PATH ; do

    if test -r $i/$SEARCH_FOR; then

      LIBS_DIR=$i        #搜索到的lib的路径

      AC_MSG_RESULT(found in $i)

    fi

  done

fi

/*验证lib是否存在*/

if test -z "$LIBS_DIR"; then

  AC_MSG_RESULT([not found])

  AC_MSG_ERROR([Please reinstall the libs distribution])

fi

/*编译的时候添加lib的include目录, -I/usr/include*/

PHP_ADD_INCLUDE($LIBS_DIR/include)

LIBNAME=curl      #lib名称

LIBSYMBOL=curl_version #lib的一个函数,用来PHP_CHECK_LIBRARY验证lib

/*验证lib*/

PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,

[

  PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $LIBS_DIR/$PHP_LIBDIR, LIBS_SHARED_LIBADD) #编译的时候链接lib, -llibcurl

  AC_DEFINE(HAVE_LIBSLIB,1,[ ])

],[

  AC_MSG_ERROR([wrong libs lib version or lib not found])

],[

  -L$LIBS_DIR/$PHP_LIBDIR -lm

])

PHP_SUBST(LIBS_SHARED_LIBADD)

9、用于返回的宏

//这些宏都定义在Zend/zend_API.h文件里
#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; }

其实,除了这些标量类型,还有很多php语言中的复合类型我们需要在函数中返回,如数组和对象,我们可以通过RETVAL_ZVAL与RETURN_ZVAL来操作它们

10、hashTable的遍历函数

//基于long key的操作函数
zval *v3;
MAKE_STD_ZVAL(v3);
ZVAL_STRING(v3, "value3", 1);
zend_hash_index_update(names, 0, &v3, sizeof(zval *), NULL);//按数字索引键更新HashTable元素的值
zval **v4;
zend_hash_index_find(names, 1, &v4); //按数字索引获取HashTable元素的值
php_printf("v4 : ");
PHPWRITE(Z_STRVAL_PP(v4), Z_STRLEN_PP(v4));
php_printf("\n");
ulong idx;
idx = zend_hash_index_exists(names, 10);//按数字索引查找HashTable,如果找到返回 1, 反之则返回 0
zend_hash_index_del(names, 2);    //按数字索引删除HashTable元素
//hashTable的遍历函数
zend_hash_internal_pointer_reset(names); //初始化hash指针
zend_hash_internal_pointer_reset_ex(names, &pos);//初始化hash指针,并付值给pos
zend_hash_get_current_data(names, (void**) &val); //获取当前hash存储值,data should be cast to void**, ie: (void**) &data
zend_hash_get_current_data_ex(names, (void**) &val, &pos) == SUCCESS; //获取当前hash存储值
zend_hash_get_current_key(names, &key, &klen, &index, 0) == HASH_KEY_IS_LONG
zend_hash_get_current_key_ex(names, &key, &klen, &index, 0, &pos) == HASH_KEY_IS_LONG; //读取hashtable当前的KEY,返回值会有两种 HASH_KEY_IS_LONG | HASH_KEY_IS_STRING ,分别对应array("value"),array("key"=>"value")两种hashtable
zend_hash_move_forward(names);
zend_hash_move_forward_ex(names, &pos); //hash指针移至下一位
//HashTable长度
php_printf("%*carray(%d) {\n", depth * 2, ‘ ‘, zend_hash_num_elements(Z_ARRVAL_P(zv))

一个简单的函数

?


1

2

3

4

5

6

7

function hello_array_strings($arr) {

  if (!is_array($arr)) return NULL;

  printf("The array passed contains %d elements ", count($arr));

  foreach($arr as $data) {

    if (is_string($data)) echo "$data ";

  }

}

PHP内核实现

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

PHP_FUNCTION(hello_array_strings)

{

  zval *arr, **data;

  HashTable *arr_hash;

  HashPosition pointer;

  int array_count;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) {

    RETURN_NULL();

  }

  arr_hash = Z_ARRVAL_P(arr);

  array_count = zend_hash_num_elements(arr_hash);

  php_printf("The array passed contains %d elements ", array_count);

  for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) {

    if (Z_TYPE_PP(data) == IS_STRING) {

      PHPWRITE(Z_STRVAL_PP(data), Z_STRLEN_PP(data));

      php_printf(" ");

    }

  }

  RETURN_TRUE;

}

以上所述就是本文给大家介绍的PHP扩展开发教程,希望大家喜欢。

原文地址:https://www.cnblogs.com/nr-zhang/p/9777252.html

时间: 2024-10-27 07:55:25

PHP扩展开发教程(总结)的相关文章

Packetbeat协议扩展开发教程 一

原文链接:http://elasticsearch.cn/article/48 Packetbeat(https://www.elastic.co/products/beats/packetbeat)是一个开源的网络抓包与分析框架,内置了很多常见的协议解析,如HTPP.MySQL.Thrift等.但是网络协议有很多,如何扩展一个自己的协议呢,本文将为您介绍如何在Packetbeat基础上扩展实现您自己的协议. 开发环境:1.Go语言Packetbeat是由Go语言编写,具有高性能和易部署的特点,

微信公众账号开发教程

微信公众账号开发教程 一.第1篇-引言 本文转载来自柳峰老师的博客,在这里非常感谢柳峰老师的分享和贡献! 内容方面,大概会涉及到: 1)前沿知识:微信公众帐号的分类.两种模式各自的特点和区别.开发模式的配置使用等: 2)API中各类消息的使用(我已经对api进行封装并打成了jar包,到时候会考虑分享出来): 3)微信公众帐号开发中的小技巧(如换行.通过代码发送表情.屏幕飘雪花.表情的接收识别.在Android和iOS上表现不一致等等): 4)与业务系统对接的方法(链接.短信等,除了技术讲解还会做

微信公众平台开发教程(八)Session处理

微信公众平台开发教程(八)Session处理 在微信窗口,输入的信息有限,我们需要将一些信息分多次请求. 比如:在进行用户绑定时,我们需要输入用户的相关信息,比如:用户名.密码,或者姓名.电话号码,服务端验证通过,即可将系统用户与微信用户绑定. 然后,此微信账户就有一定的功能权限了,可以查积分,消费记录等.服务号:招商银行信用卡,就有很多功能. 微信客户端无法缓存信息,而且输入信息有限,需要进行多次请求,在服务端保存当前会话状态.这就需要Session. 本文以用户认证,绑定账号为例,来说明具体

Eclipse SWT开发教程以及一个连连看游戏的代码实现下载

原创整理不易,转载请标明出处:Eclipse SWT开发教程以及一个连连看游戏的代码实现下载 代码下载地址:http://www.zuidaima.com/share/1772672482675712.htm 我在前面讲过:如果讲GUI编程一味只讲各个控件的使用方法,那么纯粹是浪费大家时间,如果出书,那绝对是骗钱的.所以我并不会详细地讲解SWT各个控件的具体使用方法.然而的众所周知,Eclipse的UI界面是建立在SWT基础之上的,如果一字不提SWT,似乎也不大可能.SWT是一个优秀的GUI编程

微信公众平台开发教程(五)自定义菜单

应大家强烈要求,将自定义菜单功能课程提前. 一.概述: 如果只有输入框,可能太简单,感觉像命令行.自定义菜单,给我们提供了很大的灵活性,更符合用户的操作习惯.在一个小小的微信对话页面,可以实现更多的功能.菜单直观明了,不仅能提供事件响应,还支持URL跳转,如果需要的功能比较复杂,我们大可以使用URL跳转,跳转至我们的网页即可. 注意:自定义菜单,只有服务号才有此功能 如何注册,见第一章:微信公众账号开发教程(一) 基本原理及微信公众账号注册 效果如下, 接着我们详细介绍,如何实现自定义菜单? 二

微信公众平台开发教程(三) 基础框架搭建

微信公众平台开发教程(三) 基础框架搭建 上一章,我们已经初步讲解了微信公众账号开发的基本原理,今天我们来探索设计实现. 首先我们设计了模块层次图,当然图中只是给出一种实现方式,不局限于此.具体见下图. 主要功能介绍如下: 1)请求接口层.处理HTTP请求,及响应 2)分发层.由接口层传入请求,然后具体分析请求类型,分发至不同的处理器 3)业务逻辑层.这里是我们的具体业务逻辑了,根据请求,实现具体的业务逻辑. 4)数据层.我们在实现某个应用时可能需要访问数据,可以是数据库或者是文件.如果是简单应

微信公众平台开发教程(九)微信公众平台通用开发框架

微信公众平台开发教程(九)微信公众平台通用开发框架 一.思考 开发了几个微信项目,一直在思考:如何将微信相关的处理与业务系统联系在一起?如何做到彼此分离,且易于扩展?能否开发一套独立的微信服务框架,支持各种业务应用? 二.现有常用的服务框架 支持多种业务应用,我们通过分层的方式来实现.将复杂的系统进行分层,将一些功能或者特有的逻辑进行封装,封装为不同的基础服务或中间件.业务层无需关心底层具体实现,只需进行简单调用.组装,即可支撑强大的业务应用.这样保证了层级独立,也使得系统易于维护和扩展.在一个

微信公众平台开发教程(四) 实例入门:机器人(附源码)

微信公众平台开发教程(四) 实例入门:机器人(附源码) 上一篇文章,写了基本框架,可能很多人会觉得晕头转向,这里提供一个简单的例子来予以说明,希望能帮你解开谜团. 一.功能介绍 通过微信公众平台实现在线客服机器人功能.主要的功能包括:简单对话.查询天气等服务. 这里只是提供比较简单的功能,重在通过此实例来说明公众平台的具体研发过程.只是一个简单DEMO,如果需要的话可以在此基础上进行扩展. 当然后续我们还会推出比较复杂的应用实例. 二.具体实现 1.提供访问接口 这里不再赘述,参照上一章,微信公

ArcGIS Runtime for Android开发教程V2.0(8)基础篇-----地图事件

转自:http://blog.csdn.net/arcgis_mobile/article/details/8263283 ArcGIS Runtime sdk for Android为我们提供了丰富的事件监听器,本节将主要介绍我们经常使用的监听器,并且介绍通过这些监听器可以实现哪些功能,在下面的监听器中只有MapOnTouchListener是类,其他皆为接口类型,如图所示: 1.1 MapOnTouchListener MapOnTouchListener是MapView最为重要的监听器之一