jimthunderbird / php-to-c-extension
一个命令行工具,用于使用 PHP 和 C 构建基于 Zend 的 PHP 扩展
Requires
- php: >=5.3.0
Requires (Dev)
- fezfez/php-to-zephir: dev-master#5ea8dc05d759b64fa2590580d57c6016e55de833
This package is not auto-updated.
Last update: 2024-09-28 16:41:32 UTC
README
PHP-TO-C-Ext 是一个工具,允许开发者使用 PHP 和 C 一起构建基于 Zend 引擎的 PHP 扩展。
PHP-TO-C-EXT 构建在这些优秀的东西之上
- Zephir (http://zephir-lang.com/)
- PHP 解析器 (https://github.com/nikic/PHP-Parser)
- PHP to Zephir (https://github.com/fezfez/php-to-zephir)
PHP-TO-C-EXT 在 Fedora Linux 21 和 Mac OS Yosemite 上从 PHP 5.4 到 5.6 进行了测试。
##安装
- 安装 composer
- git clone https://github.com/jimthunderbird/php-to-c-extension.git
- cd php-to-c-extension
- composer.phar install
##使用方法
$ php [path/to/php-to-c-extension]/build_extensions.php [php file to convert to c extension]
或
$ php [path/to/php-to-c-extension]/build_extensions.php [directory containing php files to convert to c extension]
##示例
- 一个简单的虚拟扩展
- 一个文件中的一个命名空间和多个类
- 在一个目录中组织多个文件
- 使用 for 循环
- 使用子命名空间 (#example-05)
- 使用接口 (#example-06)
- 使用 trait (#example-07)
- 使用 parent 调用基类中的方法
- 使用 self 关键字
- 使用三元运算符
- 延迟静态绑定
- 性能基准:冒泡排序
- 通过使用 call_c_function 通过原始 C 代码获得更高的速度
- 使用 call_c_function 将 PHP 代码与原始 C 代码结合使用以解决问题
- 使用 call_c_auto 与原始 C 代码一起工作时,代码结构更优,节省开发时间
###示例 01
让我们创建一个名为 Dummy.php 的文件,它看起来像这样
<?php namespace Dummy; class Hello { public function say() { echo "hello"; } }
然后我们可以执行
php [path/to/php-to-c-extension]/build_extensions.php Dummy.php
过了一段时间,我们应该会安装 dummy.so,然后如果我们把以下行添加到 php.ini 中
extension=dummy.so
现在我们将有类 Dummy\Hello 可用于用户代码。
如果我们编写一个名为 test.php 的文件,如下所示
<?php $o = new Dummy\Hello(); $o->say();
然后如果我们用 php -c [path/to/php.ini]/php.ini test.php 运行它,我们应该得到 "hello" 打印出来。
你可能已经注意到了,类 Hello 有命名空间 Dummy,扩展名为 dummy.so。
实际上,为了使用此工具构建 PHP 扩展,所有类都必须有一个 CamelCase 命名空间,扩展名是命名空间的下划线形式。
###示例 02 ####有时,为了方便起见,我们可能想在一个文件中编写一个命名空间和多个类,我们可以这样做。 ####让我们创建一个名为 Dummy.php 的文件,它看起来像下面这样
<?php namespace Dummy; class Hello { public function say() { echo "hello\n"; } } class Greeting { public function greet() { echo "greetings\n"; } }
####然后我们可以执行
php [path/to/php-to-c-extension]/build_extensions.php Dummy.php
一旦我们构建了 dummy.so 并将其添加到 php.ini 中,我们将有 Dummy\Hello 和 Dummy\Greeting 类可用于用户代码。
###示例 03 ####如果我们需要编写更复杂的 PHP 扩展,我们通常需要维护多个源文件,使用 PHP-TO-C-EXT 工具,我们可以编译目标目录中的所有文件。 ####让我们创建一个名为 src/Dummy 的目录,并在其中放置两个文件,Hello.php 和 Greeting.php ####以下是 src/Hello.php 的样子
<?php namespace Dummy; class Hello { public function say() { echo "hello\n"; } }
####以下是 src/Greeting.php 的样子
<?php namespace Dummy; class Greeting { public function greet() { echo "greetings\n"; } }
####然后如果我们执行
php [path/to/php-to-c-extension]/build_extensions.php src/Dummy
####然后我们将构建 dummy.so,此时用户代码将可以使用 Dummy\Hello 和 Dummy\Greeting 类。####请注意,Hello.php 和 Dummy.php 的开头都必须定义命名空间 Dummy。
###示例 04 ####循环是 PHP 中的常见控制结构,这里我们将创建一个 Dummy\SumCalculator 类并将其构建到 dummy.so 扩展中。####让我们创建一个文件 src/Dummy/SumCalculator.php,如下所示
<?php namespace Dummy; class SumCalculator { public function getSum($start, $end) { $sum = 0; for ($i = $start; $i <= $end; $i++) { $sum += $i; } return $sum; } }
####然后如果我们执行
php [path/to/php-to-c-extension]/build_extensions.php src/Dummy
####然后我们将构建 dummy.so,此时用户代码将可以使用 Dummy\SumCalculator。####我们可以在用户代码中这样做
<?php $calculator = new Dummy\SumCalculator(); $calculator->getSum(1,10);
###示例 05 ####我们可以使用子命名空间来更好地管理扩展中的代码。####使用子命名空间时,我们需要确保子命名空间的第一部分与我们的扩展名称匹配。####在这个例子中,让我们创建一个包含以下文件的 dummy 扩展。####1. src/Dummy/Vehicle.php ####2. src/Dummy/Vehicle/Car.php ####src/Dummy/Vehicle.php 看起来像这样
<?php namespace Dummy; class Vehicle { public function say() { echo "I am a vehicle"; } }
####src/Dummy/Vehicle/Car.php 看起来像这样
<?php namespace Dummy\Vehicle; class Car extends \Dummy\Vehicle { }
####然后如果我们执行
php [path/to/php-to-c-extension]/build_extensions.php src/Dummy
####然后我们将构建 dummy.so,然后在我们的用户代码中执行以下操作
$car = new Dummy\Vehicle\Car(); $car->say();
####我们将打印出 "I am a vehicle"。
###示例 06 ####我们可以像在正常 PHP 代码中一样使用接口。####让我们创建以下文件:####1. src/Dummy/MovableInterface.php ####2. src/Dummy/Vehicle.php ####src/Dummy/MovableInterface.php 看起来像这样
<?php namespace Dummy; interface MovableInterface { public function move(); }
####src/Dummy/Vehicle.php 看起来像这样
<?php namespace Dummy; class Vehicle implements MovableInterface { public function move() { echo "I am moving"; } }
####然后如果我们执行
php [path/to/php-to-c-extension]/build_extensions.php src/Dummy
####然后我们将构建 dummy.so,然后在我们的用户代码中执行以下操作
$car = new Dummy\Vehicle(); $car->move();
####我们将打印出 "I am moving"。
###示例 07 ####Trait 是 PHP 5.4 中引入的新功能,允许将功能分组并供单个类重用。####在下面的例子中,我们将使用 trait 来构建一个 dummy PHP 扩展####我们将创建一个文件 src/dummy.php,如下所示
<?php namespace Dummy\Traits; trait MovableTrait { public function move() { echo "I am moving."; } } trait PlayableTrait { public function play() { echo "I am playing."; } } namespace Dummy; class Vehicle { use \Dummy\Traits\MovableTrait; use \Dummy\Traits\PlayableTrait; }
然后如果我们执行
php [path/to/php-to-c-extension]/build_extensions.php src/dummy.php
####一旦构建了扩展 dummy.so,我们将在类 Vehicle 中有方法 move() 和 play() 可用。####如果我们这样做
<?php $vehicle = new Dummy\Vehicle(); $vehicle->play(); $vehicle->move();
####我们将看到以下打印输出####"I am playing.I am moving."
###示例 08 ####当我们需要调用基类中的某些方法时,我们需要使用 parent 关键字,以下是一个示例:####让我们创建一个名为 dummy.php 的文件,如下所示
<?php namespace Dummy; trait StoppableTrait { public function stop() { echo "I am stopping now."; } } class Vehicle { use StoppableTrait; public function __construct() { echo "I am a new vehicle."; } } class Car extends Vehicle { public function __construct() { parent::__construct(); echo "I am also a new car."; } public function stop() { parent::stop(); echo "don't worry i am a car."; } }
####在上面的代码中,我们知道基类 Vehicle 通过 trait 定义了一个名为 stop() 的方法。####现在如果我们这样做
$car = new Dummy\Car(); $car->stop();
####我们应该看到以下打印输出####"I am a new vehicle.I am also a new car.I am stopping now.don't worry i am a car."
###示例 09 ####我们可以像正常的 PHP 一样使用 self 关键字来构建 PHP 扩展,以下是一个示例:####让我们创建一个名为 dummy.php 的文件,如下所示
<?php namespace Dummy; trait StoppableTrait { public function stop() { echo "I am stopping now."; } } class Vehicle { private static $singleton; use StoppableTrait; public function __construct() { echo "I am a new vehicle."; } public static function getInstance() { if (!isset(self::$singleton)) { self::$singleton = new self(); } return self::$singleton; } } class Car extends Vehicle { public function __construct() { parent::__construct(); echo "I am also a new car."; } public function stop() { parent::stop(); echo "don't worry i am a car."; } }
####请注意,在类 Vehicle 中有 getInstance() 方法,我们使用 self 关键字创建了一个单例。####现在一旦构建了 dummy.so,如果我们这样做
<?php $car1 = Dummy\Car::getInstance(); $car2 = Dummy\Car::getInstance(); if ($car1 === $car2) { print "Two cars are identical\n"; } print $car1->stop()."\n";
####我们应该看到以下打印输出####"I am a new vehicle.Two cars are identical ####I am stopping now."
###示例 10 ####我们可以使用三元运算符作为编写条件语句和变量赋值的快捷方式。####让我们创建一个名为 dummy.php 的文件,如下所示
<?php namespace Dummy; class Number { private $number; public function __construct($number) { $this->number = $number; } public function isPositive() { $result = ($this->number > 0)?true:false; return $result; } }
####然后一旦我们构建了 dummy.so,在用户代码中如果我们这样做
<?php $number = new Dummy\Number(10); if ($number->isPositive() === TRUE) { echo "This is a positive number."; }
####然后我们应该在屏幕上打印以下内容。####"这是一个正数"。
###示例 11 ####延迟静态绑定是在 PHP 5.3 中引入的,用于在静态继承的上下文中引用被调用的类。####以下是一个延迟静态绑定的示例####让我们创建一个名为 src/dummy.php 的文件,如下所示
namespace Dummy; class Model { protected static $name = "model"; public static function find() { echo static::$name; } } class Product extends Model { protected static $name = 'Product'; }
####然后一旦我们构建了 dummy.so,如果我们这样做
Dummy\Product::find();
####我们应该在屏幕上打印 "Product"。
###示例 12 ####以下让我们做一个简单的基准测试,看看 PHP 扩展有多快####我们将使用冒泡排序作为示例来展示时间差异。以下是我们的 src/dummy.php 代码
<?php namespace Dummy; class Sorter { public function bubbleSort($arr) { $size = count($arr); for ($i=0; $i<$size; $i++) { for ($j=0; $j<$size-1-$i; $j++) { if ($arr[$j+1] < $arr[$j]) { $tmp = $arr[$j]; $arr[$j] = $arr[$j+1]; $arr[$j+1] = $tmp; } } } return $arr; } }
####如您所见,这是一个非常典型的冒泡排序实现。####然后我们将这样做
$ php [path/to/php-to-c-extension]/build_extensions.php src/dummy.php
####然后我们将构建dummy.so。####现在我们将编写用户空间测试代码test.php
<?php if (!class_exists("Dummy\Sorter")) { require_once "src/dummy.php"; } function microtime_float() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec); } $arr = array(); for ($i = 10000; $i >= 1; $i--) { $arr[] = $i; } $time_start = microtime_float(); $st = new Dummy\Sorter(); $arr = $st->bubbleSort($arr); $time_end = microtime_float(); $time = $time_end - $time_start; print "Time spent on sorting: ".$time." seconds.\n";
####上面的代码相当直接,它首先检测我们是否有定义Dummy\Sorter类,如果已定义,则表示已加载dummy.so扩展,否则,我们只需引入Dummy\Sorter类的纯PHP版本。####然后我们生成一个包含10000个整数的数组,并让Dummy\Sorter对它进行冒泡排序。####这正是我们能够在PHP本身编写扩展的优点,因为我们可以无缝地比较性能。####现在如果我们只是这样做
php test.php
####我们将使用纯PHP版本,在我的英特尔酷睿i3笔记本电脑上运行Fedora 21和PHP 5.6.4,结果显示如下:####排序所用时间:16.802139997482秒。####现在让我们测试PHP扩展,看看它的性能如何。我们首先创建php.ini文件,然后在其中我们有
extension=dummy.so
####然后如果我们执行php -c php.ini test.php,我们将使用dummy.so为我们执行冒泡排序,在我的笔记本电脑上它显示如下:####排序所用时间:3.9628620147705秒。####如您所见,我们构建的PHP扩展dummy.so比纯PHP版本快约3倍。而且我们无缝地使用了相同的Dummy\Sorter类!
###示例13 ####PHP建立在Zend引擎之上,它是用C语言编写的。如果我们能在PHP中使用C代码以提高性能,那就太好了。####在这个工具中,我们可以通过使用call_c_function api来实现这一点。####在下面的示例中,我们将使用call_c_function api从PHP内部调用基于C的冒泡排序实现。####这个示例需要具备对Zend引擎内部数据结构的深入了解。####首先,让我们创建src/dummy.php
<?php namespace Dummy; class Sorter { public function bubbleSort($arr) { $result = call_c_function("sorting.c","bubble_sort",$arr); return $result; } }
####这一点相当简单,我们有一个Sorter类,其中包含一个bubbleSort方法,它接受一个数组$arr,并返回排序后的数组。在方法内部,我们有以下代码
$result = call_c_function("sorting.c","bubble_sort",$arr);
####这意味着我们将调用sorting.c文件中的bubble_sort函数,bubble_sort函数接受$arr作为输入参数。bubble_sort函数调用的结果将存储在$result变量中。####现在让我们创建src/sorting.c文件
static zval * bubble_sort(zval * arr) { HashTable *arr_hash = arr->value.ht; long arr_length = arr_hash->nNumOfElements; long i,j; zval **p; long sorting_arr[arr_length]; long tmp; for (i=0; i < arr_length; i++) { p = (zval **)(arr_hash->arBuckets[i]->pData); sorting_arr[i] = (*p)->value.lval; } //perform bubble sort for (i=0; i< arr_length; i++) { for (j=0; j<arr_length-1-i; j++) { if (sorting_arr[j+1] < sorting_arr[j]) { tmp = sorting_arr[j]; sorting_arr[j] = sorting_arr[j+1]; sorting_arr[j+1] = tmp; } } } zval *result; MAKE_STD_ZVAL(result); array_init(result); for (i=0; i < arr_length; i++) { add_index_long(result, i, sorting_arr[i]); } return result; }
####要理解bubble_sort C函数中发生的事情,需要了解一些关于Zend引擎内部数据结构的知识。####在函数中,我们将接受一个指向zval的指针,它指向我们从PHP传递的数组。然后我们创建一个指向数组hashtable的指针。####然后我们创建一个长整型数组,用于存储数组hashtable中的每个长整数值。####稍后我们执行标准的冒泡排序,最后使用zend_array_init和add_index_long api创建一个新的排序数组并将结果返回。####现在让我们做
$ php [path/to/php-to-c-extension]/build_extensions.php src/dummy.php
####一旦构建了dummy.so,我们可以测试我们的冒泡排序有多快。####让我们重新使用示例12中的test.php脚本。
<?php if (!class_exists("Dummy\Sorter")) { require_once "src/dummy.php"; } function microtime_float() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec); } $arr = array(); for ($i = 10000; $i >= 1; $i--) { $arr[] = $i; } $time_start = microtime_float(); $st = new Dummy\Sorter(); $arr = $st->bubbleSort($arr); $time_end = microtime_float(); $time = $time_end - $time_start; print "Time spent on sorting: ".$time." seconds.\n";
####现在如果我们在php.ini文件中添加extension=dummy.so并执行
$ php -c php.ini test.php
####我们将在屏幕上看到以下内容 ####排序所用时间:0.14397192001343秒。####结果非常令人满意。与示例12中的3.9628620147705秒和纯PHP版本中的16.802139997482秒相比,它要快得多。
###示例14 ####在下面的示例中,我们将使用PHP代码与原始C代码一起获取PI的前800位。####计算PI的算法来自https://crypto.stanford.edu/pbc/notes/pi/code.html ####首先,我们将创建src/dummy.php,它看起来像这样
<?php namespace Dummy; class Math { public function getPI() { $result = call_c_function("math.c","get_pi"); //convert to format like: 3.1415... $resultSplits = str_split($result); $firstNumber = array_shift($resultSplits); $result = $firstNumber.".".implode("",$resultSplits); return $result; } }
#####然后我们将创建src/math.c,它看起来像这样
static zval * get_pi() { zval *result; MAKE_STD_ZVAL(result); int r[2800 + 1]; int i, k; int b, d; int c = 0; char buf[801]; char tmp_buf[4]; for (i = 0; i < 2800; i++) { r[i] = 2000; } int count = 0; for (k = 2800; k > 0; k -= 14) { d = 0; i = k; for (;;) { d += r[i] * 10000; b = 2 * i - 1; r[i] = d % b; d /= b; i--; if (i == 0) break; d *= i; } sprintf(tmp_buf, "%.4d", c + d / 10000); buf[count] = tmp_buf[0]; buf[count+1] = tmp_buf[1]; buf[count+2] = tmp_buf[2]; buf[count+3] = tmp_buf[3]; c = d % 10000; count += 4; } buf[count+1] = '\0'; ZVAL_STRING(result, buf, 1); return result; }
####然后我们将有test.php
<?php $math = new Dummy\Math(); print $math->getPI();
####然后一旦构建了dummy.so,我们执行
$ php -c php.ini test.php
####我们将在屏幕上看到PI的前800位。
###示例 15 ####在上面的示例 13 和 14 中,我们展示了如何使用 PHP 和原生 C 代码解决问题。 ####出现的一个问题是,如果我们在一个大型的扩展代码库中开发,使用 call_c_function 并指定 C 源文件名和 C 函数名会稍微花费一些时间,在某些时候可能会变得繁琐。这就是 API call_c_auto 发挥作用的时候。 ####下面我们将演示如何使用 call_c_auto 来进行冒泡排序。 ####首先,让我们创建文件 src/Dummy/Sorter.php
<?php namespace Dummy; class Sorter { public function bubbleSort($arr) { $result = null; if (count($arr) > 0) { $result = call_c_auto($arr); } return $result; } }
####注意这里的这一行
$result = call_c_auto($arr);
####我们正在做的事情是将 $arr 输入参数传递给 call_c_auto API。在挂钩扩展构建过程中,这个 API 将被转换为
$result = call_c_function("Sorter.c","bubbleSort",$arr);
####可以看到 Sorter.c 的名称与类名 Sorter 相同,我们将要调用的 C 函数名称与 PHP 方法名称 bubbleSort 相同。 ####这种约定有助于我们更好地组织代码并节省一些时间。 ####现在让我们创建 src/Dummy/Sorter.c
static zval * bubbleSort(zval * arr) { HashTable *arr_hash = arr->value.ht; long arr_length = arr_hash->nNumOfElements; long i,j; zval **p; long sorting_arr[arr_length]; long tmp; for (i=0; i < arr_length; i++) { p = (zval **)(arr_hash->arBuckets[i]->pData); sorting_arr[i] = (*p)->value.lval; } //perform bubble sort for (i=0; i< arr_length; i++) { for (j=0; j<arr_length-1-i; j++) { if (sorting_arr[j+1] < sorting_arr[j]) { tmp = sorting_arr[j]; sorting_arr[j] = sorting_arr[j+1]; sorting_arr[j+1] = tmp; } } } zval *result; MAKE_STD_ZVAL(result); array_init(result); for (i=0; i < arr_length; i++) { add_index_long(result, i, sorting_arr[i]); } return result; }
####代码基本上与示例 13 中使用的代码相同,只是 C 函数名称与 PHP 的方法名称相匹配。 ####现在让我们创建 test.php
<?php $st = new Dummy\Sorter(); $arr = [5,1,3,8,9,10,2,4,12]; $arr = $st->bubbleSort($arr); print_r($arr);
####然后,让我们通过以下步骤构建我们的扩展
$ php [path/to/php-to-c-extension]/build_extensions.php src/Dummy
####然后,一旦构建了 dummy.so 并将 extension=dummy.so 添加到 php.ini 中,我们就可以运行我们的测试代码
$php -c php.ini test.php
####我们将看到我们的数组被很好地排序。 ####在这个例子中使用 call_c_auto 的优点是,我们有 src/Dummy/Sorter.php 和 src/Dummy/Sorter.c,并且它们很好地交互以解决问题。