某某茶叶有限公司欢迎您!
金沙棋牌在线 > Web前端 > PHP扩展编写方法金沙棋牌在线

PHP扩展编写方法金沙棋牌在线

时间:2020-03-23 18:06

独立的 PHP 扩展可以独立于 PHP 源码之外进行分发。要创建一个这样的扩展,需要准备好两样东西:

今天看了一些PHP扩展的入门文章,很好的文章,但看的还是很吃力。主要是因为不了解文章里说的生命周期,内存分配,SAPI这些概念。然后代码里全是宏,如果不查源码,和看天书没什么区别。所以想记录一下从源码的角度分析 hello.c 这个文件的过程,其它文章里有的我就不重复了,我先放代码,这里有一个部分和原文章不一样。

本方案中采用的PHP扩展方式为:

php程序写的时间长了,自然对他所提供的功能了如指掌,他所提供的一大堆功能,真是觉得很好用,但有时候会发现php也缺少一些功能,自己总是会产生为php添加一些自定义的功能的想法。久而久之,终于今天憋不住了,开始动手研究如何添加。

  • 配置文件 (config.m4)
  • 你的模块源码

随便新建一个文件夹,里面包含下面3个文件:

Ø下载PHP对应版本的源码,在其中加入、生成扩展(如smsupport.so);

下载一个php的源代码包,这里使用的是php 4.0.5版,解压后会看到php的根目录下会有README.EXT_SKEL这样一个文件,打开具体阅读了一下,发现了一个非常好用的工具,这个工具可以帮你构建一个空的php扩展,然后你向里面添加相应的代码就可以完成你自己的功能扩展了。下面我们就来介绍如何使用这个工具。

接下来我们来描述一下如果创建这些文件并组合起来。

config.m4

Ø然后针对安装同一版本的PHP(注意,不需要是源码安装的,可以通过yum install,apt-get install安装的),将smsupport.so放置到extension_dir中;在php.ini最后一行加入extension = smsupport.so;

首先转移你的目录到php的目录下的ext目录,假如你只需要一个基本的扩展框架的话,执行下面的命令:

准备好系统工具

想要扩展能够在系统上编译并成功运行,需要准备转以下工具:

  • GNU autoconf
  • GNU automake
  • GNU libtool
  • GNU m4

以上这些都可以从 获取。

注:以上这些都是类 Unix 环境下才能使用的工具。

PHP_ARG_ENABLE(hello, whether to enable Hello
World support,
[ --enable-hello   Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

Ø重启apached服务

./ext_skel --extname=module_name

改装一个已经存在的扩展

为了显示出创建一个独立的扩展是很容易的事情,我们先将一个已经内嵌到 PHP 的扩展改成独立扩展。安装 PHP 并且执行以下命令:

$ mkdir /tmp/newext
$ cd /tmp/newext

现在你已经有了一个空目录。我们将 mysql 扩展目录下的文件复制过来:

$ cp -rp php-4.0.X/ext/mysql/* .
# 注:看来这篇 README 真的需要更新一下了
# PHP7 中已经移除了 mysql 扩展部分

到这里扩展就完成了,执行:

$ phpize

现在你可以独立存放这个目录下的文件到任何地方,这个扩展可以完全独立存在了。

用户在编译时需要使用以下命令:

$ ./configure 
       [--with-php-config=/path/to/php-config] 
       [--with-mysql=MYSQL-DIR]
$ make install

这样 MySQL 模块就可以使用内嵌的 MySQL 客户端库或者已安装的位于 MySQL 目录中的 MySQL。

注:意思是说想要编写 PHP 扩展,你既需要已经安装了 PHP,也需要下载一份 PHP 源码。

php_hello.h

Ø此后,在php文件中直接调用smsupport.so提供的方法即可。

module_name是你自己可以选择的扩展模块的名字,例如我选择的my_module。执行工具后会自动在ext目录下建立你选择的module_name名字的目录,里面已经生成了相关的代码,这些代码中只需要调整config.m4文件中的三行注释就可以正常的编译带这个自定义扩展模块的php了。在php的根目录执行下列操作就可以得到。

定义一个新扩展

我们给示例扩展命名为 “foobar”。

新扩展包含两个资源文件:foo.c 和 bar.c(还有一些头文件,但这些不只重要)。

示例扩展不引用任何外部的库(这点很重要,因为这样用户就不需要特别指定一些编译选项了)。

LTLIBRARY_SOURCES 选项用于指定资源文件的名字,你可以有任意数量的资源文件。

注:上面说的是 Makefile.in 文件中的配置选项,可以参考 xdebug。

#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"

PHP_FUNCTION(hello_world);

extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry

#endif

注意点

./buildconf

修改 m4 后缀的配置文件

m4 配置文件可以指定一些额外的检查。对于一个独立扩展来说,你只需要做一些宏调用即可。

PHP_ARG_ENABLE(foobar,whether to enable foobar,
[  --enable-foobar            Enable foobar])

if test "$PHP_FOOBAR" != "no"; then
  PHP_NEW_EXTENSION(foobar, foo.c bar.c, $ext_shared)
fi

PHP_ARG_ENABLE 会自动设置好正确的变量以保证扩展能够被 PHP_NEW_EXTENSION 以共享模式启动。

PHP_NEW_EXTENSION 的第一个参数是扩展的名称,第二个参数是资源文件。第三个参数 $ext_shared 是由 PHP_ARG_ENABLE/WITHPHP_NEW_EXTENSION 设定的。

请始终使用 PHP_ARG_ENABLEPHP_ARG_WITH 进行设置。即使你不打算发布你的 PHP 模块,这些设置也可以保证让你的模块和 PHP 主模块的接口保持一体。

注:PHP_ARG_ENABLEPHP_ARG_WITH 应该是用于定义模块是动态扩展还是静态编译进 PHP 中,就跟编译 PHP 时使用的 --enable-xxx--with-xxx 一样。

hello.c

Ø在进行扩展及测试的过程中我们需要设置PHP的SAFE_MODE为OFF,否则可能无法完成扩展或测试。SAFE_MODE默认为OFF。

./configure --enable-module_name

创建资源文件

ext_skel 可以为你的 PHP 模块创建一些通用的代码,你也可以编写一些基本函数定义和 C 代码来处理函数的参数。具体信息可以查看 READNE.EXT_SKEL。

不要担心没有范例,PHP 中有很多模块供你参考,选择一个简单的点开始,添加你自己的代码。

注:ext_skel 可以生成好基本模块需要的资源文件和配置文件,不需要自己创建。

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_hello.h"

// 模块所包含的函数列表信息
static zend_function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    {NULL, NULL, NULL}
};

// 模块自身的相关信息
// 如模块名,模块包含的函数,生命周期,版本号等
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

// 与动态加载有关 Dynamic Loading,后面解释
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif

// 你的扩展函数
PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}

Ø编译php扩展的环境和运行环境需要一致!

make

修改自定义模块

将 config.m4 文件和资源文件放到同一个目录中,然后执行 phpize (PHP 4.0 以上的版本编译 PHP 的时候都安装了 phpize)。

如果你的 phpize 不在系统环境变量中,你需要指定绝对路径,例如:

$ /php/bin/phpize

这个命令会自动复制必需的构建文件到当前目录并根据 config.m4 创建配置文件。

通过以上的步骤,你已经有了一个独立的扩展了。

运行下面的命令可以验证扩展。

Ø建议先使用PHP执行脚本,此时提供错误信息较为丰富。

下面我来演示建立my_module扩展框架的全过程,为了更有效果,我们来完成一个php的扩展功能,在php中调用这个功能可以在web页面中显示hello world这个经典单词。

安装扩展

扩展可以通过以下命令编译安装:

$ ./configure 
            [--with-php-config=/path/to/php-config]
$ make install
phpize
./configure
make
php -dextension=modules/hello.so -r "echo hello_world();"

PHP扩展开发

在php目录下的ext目录中,执行下面的命令

给模块添加共享支持

有时候独立扩展需要是共享的已供其他模块加载。接下来我会解释如何给已经创建好的 foo 模块添加共享支持。

  1. 在 config.m4 文件中,使用 PHP_ARG_WITH/PHP_ARG_ENABLE 来设定扩展,这样就可以自动使用 --with-foo=shared[,..]--enable-foo=shared[,..] 这样的指令作为编译参数了。
  2. 在 config.m4 文件中,使用 PHP_NEW_EXTENSION(foo,.., $ext_shared) 使扩展可以被构建。
  3. 添加以下代码到你的 C 语言资源文件中:
   #ifdef COMPILE_DL_FOO
   ZEND_GET_MODULE(foo)
   #endif

这一段讲的上面都提到过了,这里只是又强调了一下。

和原文不同的是这里使用 zend_function_金沙棋牌在线,entry 而不是 function_entry。这个和 PHP 的版本有关,不然编译会出错。

1.从 php中下载对应目标的PHP版本;

./ext_skel --extname=my_module

PECL 网站约定

如果你打算发布你的扩展到 PECL 的网站,需要考虑以下几点:

  1. 添加 LICENSE 或 COPYING 到 package.xml
  2. 需要在扩展头文件中定义好版本信息,这个宏会被 foo_module_entry 调用来声明扩展版本:
   #define PHP_FOO_VERSION "1.2.3"

我们先看这一段代码

2.由于php依赖libxml2-dev,为此,首先通过apt-get install libxml2-dev或者yum install libxml2-dev(注libxml2-dev在不同Linux下名称不同,baidu下)

得到反馈结果:

static zend_function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    {NULL, NULL, NULL}
};

3.解压缩1中下载的php包,进入目录,运行./configure完成配置,此时会生成编写extension所需的一些文件

Creating directory my_module

如果你查看 PHP 官方文档,也有可能会这样写。

4.进入到php源码中的ext目录,执行./ext_skel --extname=smsupport,其会自动生成一系列目录及目录内的文件

Creating basic files: config.m4 Makefile.in .cvsignore my_module.c php_my_module.h tests/001.phpt my_module.php [done].

static zend_function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    ZEND_FE_END
};

5.在ext/smsupport目录中

To use your new extension, you will have to execute the following steps:

其实说到底,把这些你看不懂的宏的定义找出来,就好了,我们就先找 zend_function_entry,因为我的源码在下面这个路径下,所以我先 cd 过去。

a)在.c文件中找到const zend_function_entry cqwei_functions[] = {,在其后添加自己定义的函数

  1. $ cd ..

  2. $ vi ext/my_module/config.m4

  3. $ ./buildconf

  4. $ ./configure --[with|enable]-my_module

  5. $ make

  6. $ ./php -f ext/my_module/my_module.php

  7. $ vi ext/my_module/my_module.c

  8. $ make

/usr/local/Cellar/php56/5.6.32_8/include/php

注意:自动生成的为c文件,由于本扩展中要用到cpp,强行将其改为cpp,在编译时在后端加上-lstdc++,使得编译成功

Repeat steps 3-6 until you are satisfied with ext/my_module/config.m4 and

然后用下面的命令找 zend_function_entry 的定义。

PHP_FE(testadd,NULL)/*For testing, remove later. */

step 6 confirms that your module is compiled into PHP. Then, start writing

grep -rnw . -e 'zend_function_entry'

PHP_FE(dcsSM2Verify,arg_dcsSM2Verify)

code and repeat the last two steps as often as necessary.

发现在 zend_API.h 这个文件。

PHP_FE(dcsSM4Decrypt,arg_dcsSM4Decrypt)

在文件的最后添加下列代码

./Zend/zend_API.h:41:} zend_function_entry;

注意点:

PHP_FUNCTION(say_hello)

源码是一个结构体,用到存储函数的信息。

ü将PHP_FE的名称改成自己的函数,可以同时包含多个;

{

typedef struct _zend_function_entry {
        const char *fname;
        void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
        const struct _zend_arg_info *arg_info;
        zend_uint num_args;
        zend_uint flags;
} zend_function_entry;

ü如果PHP_FE中包含的函数需要参数,则应加入参数信息,如arg_Verify,其中arg_Verify包含4个参数,arg_Decrypt包含2个参数。

zend_printf("hello worldn");

接着再看 PHP_FE,同理使用下面命令。

nZEND_BEGIN_ARG_INFO_EX(arg_Verify,

}

grep -rnw . -e 'PHP_FE'

0, 0, 1)

保存文件退出

得到下面的结果。

ZEND_ARG_INFO(0,user)

vi php_my_module.h

./main/php.h:352:#define PHP_FE         ZEND_FE

ZEND_ARG_INFO(0,pubKey)

在文件中PHP_FUNCTION(confirm_my_module_compiled);一行前面添加下面的代码

再找 ZEND_FE,下面就不写了,最后查到是这个定义。

ZEND_ARG_INFO(0,ticket)

PHP_FUNCTION(say_hello);

#define ZEND_FENTRY(zend_name, name, arg_info, flags)   { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeo
f(struct _zend_arg_info)-1), flags }, 
#define ZEND_FE(name, arg_info)                                         ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)

ZEND_ARG_INFO(0,sig)

保存文件退出

其实就是一个关于函数结构体的信息,和之前的 zend_function_entry 对应。

ZEND_END_ARG_INFO()

退回到php的根目录下,执行下面的命令

再看 zend_module_entry 这一段代码,其实做法也是参照上面的,#if ZEND_MODULE_API_NO >= 20010901 是用来判断 api 版本是否大于等于 20010901 (这个是年月日),如果大于则在编译期包含 STANDARD_MODULE_HEADER 这个宏,这些信息都可以在下面这个源文件找到,ZEND_MODULE_API_NO 这个宏也是定义这个文件中的。

nZEND_BEGIN_ARG_INFO_EX(arg_Decrypt,0, 0, 1)

./buildconf

zend_modules.h

ZEND_ARG_INFO(0,key)

./configure --enable-my_module

最后说说这个

ZEND_ARG_INFO(0,cipher)

make

// 与动态加载有关 Dynamic Loading,后面解释
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif

ZEND_END_ARG_INFO()

假如一切顺利的话,我们现在已经将扩展模块my_module编译到php里面了。我们编写下面的代码进行测试

其实我也不是很懂,查了 ZEND_GET_MODULE 这个宏,他是一个函数,用于在运行时供 Zend 引擎获取模块的名字。今天大概就到这里,下次再写函数带数的情况。

b)PHP_FUNCTION(testadd)在定义自定义函数testadd的函数体,如下所示为最简单的内容

Say_hello();

PHP_FUNCTION(testadd)

?>

{

保存文件为say_hello.php

zend_printf("testadd00");

在php的根目录下运行

}

./php

c)PHP_FUNCTION(Verify),稍微复杂,因为有参数和返回值,示例如下