jimthunderbird/php-to-c-extension

一个命令行工具,用于使用 PHP 和 C 构建基于 Zend 的 PHP 扩展

dev-master 2017-01-21 06:31 UTC

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 构建在这些优秀的东西之上

PHP-TO-C-EXT 在 Fedora Linux 21 和 Mac OS Yosemite 上从 PHP 5.4 到 5.6 进行了测试。

##安装

  1. 安装 composer
  2. git clone https://github.com/jimthunderbird/php-to-c-extension.git
  3. cd php-to-c-extension
  4. 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]

##示例

###示例 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,并且它们很好地交互以解决问题。