为php开发c 扩展 -凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 6498002
  • 博文数量: 1159
  • 博客积分: 12444
  • 博客等级: 上将
  • 技术积分: 12570
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-13 21:34
文章分类

(1159)

  • (164)
  • (104)
  • (65)
  • (101)
  • (10)
  • (39)
  • (1)
  • (16)
  • (268)
  • (4)
  • (16)
  • (10)
  • (13)
  • (28)
  • (12)
  • (9)
  • (17)
  • (16)
  • (48)
  • (51)
  • (80)
  • (12)
  • (27)
  • (14)
  • (34)
  • (0)
文章存档

(126)

(350)

(56)

(91)

(182)

(193)

(138)

(23)

我的朋友
相关博文
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·

分类: php

2015-11-04 11:00:23




摘要

有时候,单纯依靠 php “本身”是不行的。尽管普通用户很少遇到这种情况,但一些专业性的应用则经常需要将 php 的性能发挥到极致(这里的性能是指速度或功能)。由于受到 php 语言本身的限制,同时还可能不得不把庞大的库文件包含到每个脚本当中。因此,某些新功能并不是总能被顺利实现,所以我们必须另外寻找一些方法来克服 php 的这些缺点。

了解到了这一点,我们就到了应该接触一下 php 的心脏并探究一下它的内核——可以编译成 php 并让之工作的 c 代码——的时候了。

概述:

php调用动态链接库几个必要步骤为:

1. c/c 编写动态链接库,编译打包成.so文件

2. 初始化一个新的php扩展

3. 配置、编写php扩展内容,在扩展中使用c/c 调用.so

4. 编译并添加php扩展

5. 在php应用中直接调用php扩展里暴露出来的api

为了从能运行的最简单的例子开始,所以下面的叙述可能不会严格按照上面列的步骤来写,也有可能会重复穿插着写。但总体顺序是一致的。

一. c/c 编写动态链接库,编译打包成.so文件

如果还不会用c 调用自己写的.so库,请参考我的这篇文章:

文中从什么是name mangling开始,到如何用调用一个包含简单函数的so库,再到如何从so中加载类,都有详细叙述,并附有可运行示例代码。

二. 初始化php扩展

本文中,我们将创建一个叫“vehicle”的php扩展,其中包含一个“car”类。

将要创建的扩展会涉及到以下文件需要修改,这些文件将会出现在vehicle目录下。后面会一一叙述这些文件是怎么来的,现在只是大致了解一下。

  • car.h —— 包含了c 写的类,即car的定义
  • car.cc —— car类的具体实现
  • php_vehicle.h —— 包含了php创建扩展所需要的一些头文件,外部变量定义等。
  • vehicle.cc —— 扩展的主要源码文件,这里面会调用到car类
  • config.m4 —— php扩展的配置文件

1. 需要php源码包

如果你不是通过源码包方式安装的php,而是通过apt-get install 安装的,那么首先确保你安装了 php-devel 包,然后需要下载php源代码,然后可以跳到第2步。

如果想从源码包编译安装php,可以参考我写的另外一篇文章《linux(ubuntu12.10)搭建php开发环境(源码包方式)》,有详细的每一步的介绍。地址为:

安装完成以后,跳到第2步。

2. 制作php外部扩展

去到php源码包目录的ext目录下,我的在

/usr/local/src/php-5.3.22/ext

然后输入命令

sudo ./ext_skel --extname=vehicle

该命令会在ext目录下新建一个vehicle目录,并创建新模块“vehicle”目前所需的所有文件,包括

config.m4, php_vehicle.h, vehicle.php, credits等。

还会列出应该执行哪些后续步骤来使用新的扩展,具体如下图所示。

图中我一共使用了三个命令

$ pwd 显示我的php源码包的ext目录所在位置

$ sudo ./ext_skel --extname=vehicle 前面已解释

$ ls 列出了执行上一条命令以后,在vehicle目录下,为我们创建的文件及目录。

如上图所示,在它的指导步骤1~8中,大致了解到我们需要编译config.m4文件,然后需要运行配置,然后需要使用make命令编译等。现在不用知道具体都干些什么,接下来会分别叙述。

三. 配置、搭建最基本的php扩展骨架

1. 配置php构建系统——编写config.m4

首先我们需要明确的是,为了在php扩展中使用c ,那么必须通知php构建系统使用c 编译器

通过在config.m4 文件中添加宏php_require_cxx()来实现。

使用c 的过程中,肯定会用到c 的一些库,至少需要标准库(libstdc 大多系统都是这样的)

通过在config.m4 文件中添加宏php_add_library()来实现。

将下面的代码添加到config.m4中

php_arg_enable(vehicle,
    [whether to enable the "vehicle" extension],
    [  --enable-vehicle      enable "vehicle" extension support])
if test $php_vehicle != "no"; then
    php_require_cxx()
    php_subst(vehicle_shared_libadd)
    php_add_library(stdc  , 1, vehicle_shared_libadd)
    php_new_extension(vehicle, vehicle.cc car.cc, $ext_shared)
fi

即去掉config.m4文件中某些行前面的注释符号“dnl”,然后添加我们需要的配置。如果还不清楚,可以参考我的配置文件,内容如下图

这里的宏php_subst()是标准autoconf的ac_subst()宏的php修改版, 它在将扩展构建为共享模块时需要。

php_new_extension宏中,第一个参数代表模块的名称;第二个参数是需要编译的文件,用空格隔开;第三个参数跟宏php_subst()是一样的。

2. 编写头文件php_vehicle.h

该头文件应该包含以下内容,其中php_vehicle_extname “vehicle” 代表该扩展的名称,php_vehicle_extver则代表版本号,这些都会在php_info()中显示出来。

#ifndef php_vehicle_h
#define php_vehicle_h
#define php_vehicle_extname  "vehicle"
#define php_vehicle_extver   "1.0"
#ifdef have_config_h
#include "config.h"
#endif 
extern "c" {
#include "php.h"
}
extern zend_module_entry vehicle_module_entry;
#define phpext_vehicle_ptr &vehicle_module_entry;
php_minit_function(vehicle);
php_msutdown_function(vehicle);
php_rinit_function(vehicle);
php_rshutdown_function(vehicle);
php_minfo_function(vehicle);
#endif /* php_vehicle_h */

我的php_vehicle.h文件内容如下图所示。

要理解php_minit_function()函数,就需要了解php的启动步骤。大体就是

  • 当我们启动apache的时候,它就启动php的解释器
  • php会调用每一个扩展的minit函数,可以通过查看php.ini文件来看哪些扩展模块是激活的
  • minit就是module initialization,即模块初始化方法的简称,在每一个模块初始化方法里,会定义并初始化一系列在以后的页面请求中需要用到的函数、类、变量等。
  • 一个典型的minit方法框架如下所示
1
2
3
4
5
php_minit_function(extension_name) {
 
/* initialize functions, classes etc */
 
}

以上是php启动的第一步。为了先跑通整个流程,这里就不在一一叙述每一个方法以及宏的作用了。

3. 编写需要编译的文件(vehicle.cc、car.cc)

使扩展能够运行的最基本的vehicle.cc框架应该包含以下内容

#include "php_vehicle.h"
php_minit_function(vehicle)
{
    return success;
}
zend_module_entry vehicle_module_entry = {
#if zend_module_api_no >= 20010901
    standard_module_header,
#endif
    php_vehicle_extname,
    null,                  /* functions */
    php_minit(vehicle),
    null,                  /* mshutdown */
    null,                  /* rinit */
    null,                  /* rshutdown */
    null,                  /* minfo */
#if zend_module_api_no >= 20010901
    php_vehicle_extver,
#endif
    standard_module_properties
};
#ifdef compile_dl_vehicle
extern "c" {
zend_get_module(vehicle)
}
#endif

我的vehicle.cc文件如下图所示

有了这个文件,我们还不能能创建最基本的php扩展,因为前面我们在配置文件config.m4中指定了需要编译的还有car.cc文件,所以必须给出,即使现在里面什么也没有。

所以还需要新建一个car.cc文件,暂时让它空着。

4. 配置、编译、安装

在当前文件目录(即/usr/local/src/php-5.3.22/ext/vehicle)下执行命令

$ sudo /usr/local/php/bin/phpize

$ sudo ./configure --enable-vehicle --with-php-config=/usr/local/php/bin/php-config

如下图所示

然后执行make & make install 命令

$ sudo make

$sudo make install

安装完成以后,会看到在

/usr/local/php/lib/php/extensions/no-debug-zts-20090626/

目录下有一个新生成的so文件,名为vehicle.so,将库该文件的路径添加到php.ini配置文件中,如下图所示。

即添加这句话

“extension="/usr/local/php/lib/php/extensions/no-debug-zts-20090626/vehicle.so"

然后重启你的apache,在phpinfo()中就可以看到已经成功启动扩展vehicle了,如下图所示。

至此,最基本的框架已经搭起来了。接下来要做的工作就是如何使用php调用c/c 编写的so文件。先从简单的直接调用开始

四. 编写具体的php扩展内容

1.  编写car.h头文件

把头文件与源码文件分开是一个不错的习惯,特别是在别人不想知道你的具体实现的时候。这里我们也采取这种方式。

#ifndef vehicle_car_h
#define vehicle_car_h
// a very simple car class
class car {
public:
    car(int maxgear);
    void shift(int gear);
    void accelerate();
    void brake();
    int getcurrentspeed();
    int getcurrentgear();
private:
    int maxgear;
    int currentgear;
    int speed;
};
#endif /* vehicle_car_h */

如上代码所示,我们先定义一个car类,然后定义几个public的方法以及私有成员变量。然后在car.cc中实现它

#include "car.h"
car::car(int maxgear) {
    this->maxgear = maxgear;
    this->currentgear = 1;
    this->speed = 0;
}
void car::shift(int gear) {
    if (gear < 1 || gear > maxgear) {
        return;
    }
    currentgear = gear;
}
void car::accelerate() {
    speed  = (5 * this->getcurrentgear());
}
void car::brake() {
    speed -= (5 * this->getcurrentgear());
}
int car::getcurrentspeed() {
    return speed;
}
int car::getcurrentgear() {
    return currentgear;
}

现在我们已经定义好了car类,那么如何使其暴露给php用户空间,让php能够调用这些方法呢。

首先你需要定义一个包含有function_entry 表的php类来调用car,这个function_entry 表里就包含了每一个你想暴露给php的c 方法。这里所指的这个php类就是前面提到的vehicle.cc。更新一下vehicle.cc的内容,如 下所示。

#include "php_vehicle.h"
zend_class_entry *car_ce;
php_method(car, __construct)
{
}
php_method(car, p_shift)
{
}
php_method(car, p_accelerate)
{
}
php_method(car, p_brake)
{
}
php_method(car, p_getcurrentspeed)
{
}
php_method(car, p_getcurrentgear)
{
}
function_entry car_methods[] = {
    php_me(car,  __construct,     null, zend_acc_public | zend_acc_ctor)
    php_me(car,  p_shift,           null, zend_acc_public)
    php_me(car,  p_accelerate,      null, zend_acc_public)
    php_me(car,  p_brake,           null, zend_acc_public)
    php_me(car,  p_getcurrentspeed, null, zend_acc_public)
    php_me(car,  p_getcurrentgear,  null, zend_acc_public)
    {null, null, null}
};
php_minit_function(vehicle)
{
    zend_class_entry ce;
    init_class_entry(ce, "car", car_methods);
    car_ce = zend_register_internal_class(&ce tsrmls_cc);
    return success;
}
zend_module_entry vehicle_module_entry = {
#if zend_module_api_no >= 20010901
    standard_module_header,
#endif
    php_vehicle_extname,
    null,        /* functions */
    php_minit(vehicle),        /* minit */
    null,        /* mshutdown */
    null,        /* rinit */
    null,        /* rshutdown */
    null,        /* minfo */
#if zend_module_api_no >= 20010901
    php_vehicle_extver,
#endif
    standard_module_properties
};
#ifdef compile_dl_vehicle
extern "c" {
zend_get_module(vehicle)
}
#endif

可以看到在php_method中的函数名称与car.cc类里写的函数名称并不需要一样,你可以定义任意自己觉得合适的函数名,为了表示方便,我这里一律添加前缀"p_"。这里每一个函数都还没有具体实现。

前面提到的function_entry表在上诉代码中可以看到由很多php_me组成,每一个都代表了想要暴露给php用户空间的方法,最后一定以{null,null,null}表示结束。

现在我们已经有了c 的类,也有了php类,那么如何把两者联系起来呢?

首先你需要做的是定义一个zend_object_hander。然后定义一个结构,该结构包含了这个hander和c 的类。在php5中一个object其实就是一个hander,可以如下定义。

zend_object_handlers car_object_handlers;
struct car_object {
    zend_object std;
    car *car;
};

该结构就会把c 的对象与zend的对象联系起来,然后你需要把下列代码添加到你的vehicle.cc文件中去,在php_method方法之前。

void car_free_storage(void *object tsrmls_dc)
{
    car_object *obj = (car_object *)object;
    delete obj->car; 
    zend_hash_destroy(obj->std.properties);
    free_hashtable(obj->std.properties);
    efree(obj);
}
zend_object_value car_create_handler(zend_class_entry *type tsrmls_dc)
{
    zval *tmp;
    zend_object_value retval;
    car_object *obj = (car_object *)emalloc(sizeof(car_object));
    memset(obj, 0, sizeof(car_object));
    obj->std.ce = type;
    alloc_hashtable(obj->std.properties);
    zend_hash_init(obj->std.properties, 0, null, zval_ptr_dtor, 0);
    zend_hash_copy(obj->std.properties, &type->default_properties,
        (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *));
    retval.handle = zend_objects_store_put(obj, null,
        car_free_storage, null tsrmls_cc);
    retval.handlers = &car_object_handlers;
    return retval;
}

然后更新一下php_minit_function,这个函数前面提到过,就是php的扩展的模块初始化函数,如下所示:

php_minit_function(vehicle)
{
    zend_class_entry ce;
    init_class_entry(ce, "car", car_methods);
    car_ce = zend_register_internal_class(&ce tsrmls_cc);
    car_ce->create_object = car_create_handler;
    memcpy(&car_object_handlers,
        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
    car_object_handlers.clone_obj = null;
    return success;
}

可能这里对“tsrmls_dc”这个宏比较疑惑,它其实是 “ , void ***tsrm_ls”

然后编写一下构造函数

php_method(car, __construct)
{
    long maxgear;
    car *car = null;
    zval *object = getthis();
    if (zend_parse_parameters(zend_num_args() tsrmls_cc, "l", &maxgear) == failure) {
        return_null();
    }
    car = new car(maxgear);
    car_object *obj = (car_object *)zend_object_store_get_object(object tsrmls_cc);
    obj->car = car;
}

接着实现一下前面定义好的,但没有写内容的php_method函数。为了演示的简洁,我们就实现两个函数。

php_method(car, p_accelerate)
{
    car *car;
    car_object *obj = (car_object *)zend_object_store_get_object(
        getthis() tsrmls_cc);
    car = obj->car;
    if (car != null) {
        car->accelerate();
    }
}
php_method(car, p_getcurrentspeed)
{
    car *car;
    car_object *obj = (car_object *)zend_object_store_get_object(
        getthis() tsrmls_cc);
    car = obj->car;
    if (car != null) {
        return_long(car->getcurrentspeed());
    }
    return_null();
}

记得添加所需要的头文件car.h。

然后重新 make & make install 吧,做完以后重启apache服务器。

五. 在php脚本中调用扩展暴露出来的方法

编写一个php脚本,去测试是否成功。测试代码如下

1
2
3
4
5
6
7
// create a 5 gear car
 $car = new car(5);
 print $car->p_getcurrentspeed(); // prints '0'
 $car->p_accelerate();
 print $car->p_getcurrentspeed(); // prints '5'
?>

运行结果如果是0 和 5就说明成功了。

自此一个简单的php调用c 类已经实现了。

六. 在扩展中调用c/c 写的so库

假设现在我们有一个冒泡排序的.so库文件,现在我们想在php扩展中调用这个库文件里的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extern "c" void bubble_sort(int *arr, int len)
{
    int tmp;
 
    for(int i = 0; i < len - 1; i )
        for(int j = i 1; j < len; j )
        {
            if(arr[i] > arr[j])
            {
                tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
        }
}

1. 更新car.h文件以及car.cc文件,添加调用so的代码,如下图所示。

car.cc 中代码如下图所示。

2. 更新vehicle.cc文件

在function_entry car_methods添加

php_me(car, p_bubble_sort_so, null, zend_acc_public)

然后添加一个php_method(car, p_bubble_sort_so),代码如下

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
php_method(car, p_bubble_sort_so)
{
    zval *arr;
    zval *len;
    if (zend_parse_parameters(zend_num_args() tsrmls_cc, "zz", &arr, &len) == failure)
    {
        return;
    }
    switch (z_type_p(arr)) {
        case is_null:
            php_printf("null\n");
            break;
        case is_bool:
            php_printf("boolean: %s\n", z_lval_p(arr) ? "true" : "false");
            break;
        case is_long:
            php_printf("long: %ld\n", z_lval_p(arr));
            break;
        case is_double:
            php_printf("double: %f\n", z_dval_p(arr));
            break;
        case is_string:
            php_printf("string: ");
            phpwrite(z_strval_p(arr), z_strlen_p(arr));
            php_printf("\n");
            break;
        case is_resource:
            php_printf("resource\n");
            break;
        case is_array:
            php_printf("type is array\n");
            break;
        case is_object:
            php_printf("object\n");
            break;
        default:
            php_printf("unknown\n");
    }
 
    hashtable *arr_hash;
    hashposition pointer;
    int array_count;
    zval **data;
 
    arr_hash = z_arrval_p(arr);
    array_count = zend_hash_num_elements(arr_hash);
    php_printf("the array passed contains %d elements\n", array_count);
 
    // pass to the so
    int *arr_so = (int*)malloc(sizeof(int) * array_count);
    int len_so = array_count;
    int i= 0;
 
    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_long)
        {
            php_printf("%ld\t%ld\n", z_lval_pp(data), (**data).value.lval);
            arr_so[i ] = z_lval_pp(data);
        }
    }
 
    car *car;
    car_object *obj = (car_object*)zend_object_store_get_object(
            getthis() tsrmls_cc);
    car = obj->car;
    if (car != null)
    {
        car->bubble_sort(arr_so, len_so);
    }
    for(i = 0; i < len_so; i )
    {
        php_printf("%d\n", arr_so[i]);
    }
}

这段代码还是比较容易懂的,可能会对zval这个变量不熟悉。其实所有用户定义的变量在php中都是用zval类型来表示的,它的内部表示如下

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
typedef pval zval;
 
typedef struct _zval_struct zval;
 
typedef union _zvalue_value {
    long lval; /* long value */
    double dval; /* double value */
    struct {
        char *val;
        int len;
    } str;
    hashtable *ht; /* hash table value */
    struct {
        zend_class_entry *ce;
        hashtable *properties;
    } obj;
} zvalue_value;
 
struct _zval_struct {
    /* variable information */
    zvalue_value value; /* value */
    unsigned char type; /* active type */
    unsigned char is_ref;
    short refcount;
};

然后再看上面那段代码。

switch里面是写给大家的说明程序,为了展示出如何判断传入的参数类型,可以不写略过。下面这句printf代码也是为了向大家展示 z_lval_pp的用法,它其实就是对指针的指针取值,你也可以写成后面一种形式,即使**data的形式。不过建议使用第一种。

php_printf("%ld\t%ld\n", z_lval_pp(data), (**data).value.lval);

接着看代码。首先我们用zend_parse_parameters接收穿过来的参数,这里我们传递的是数组。php中数组在 zval里都是以hash表的形式储存的,所以我们这里声明一个hash表来接收这个参数。通过zend_hash_num_elements()则可以 得到hash表元素的个数,也就是数组的个数。

然后我们将传递过来的参数通过for循环复制给我们的数组,最后通过调用car的bubble_sort函数进行排序。而bubble_sort调用的就是写好的so库函数了。

3. 编译、安装、测试

重新 make & make install,然后写一个php脚本测试一下,可以如下写:

然后运行该php脚本,如果浏览器里出现了排序后的数列0,1,2,3,4,5,则代表成功了。

that's all,

enjoy!

阅读(1219) | 评论(0) | 转发(0) |
0

上一篇:

下一篇:为php开发c语言扩展

给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图