内容


用 SWIG 构建 PHP 扩展

Comments

尽管编写一个 PHP 扩展并不是很困难,而 SWIG 的确进一步简化了这项任务,这主要是因为它自动化了结合 PHP 与 C 或 C++ 所需的工作。若给定对一个函数的描绘 — 函数的名称及其形参 — SWIG 就会生成一个包装程序来将 PHP 与低层代码连接起来。

SWIG 需要一些前提条件。SWIG 的一些最新版本需要 PHP 的版本是 V5。此外,还需要有一个 C/C++ 编译器,比如 GNU Compiler Collection (GCC),以及 PHP Module Development Kit (MDK)。特别是您还要有与 PHP 安装相关的头文件。如果您使用的是 Ubuntu Linux ® 或一个 Debian 的变体并且已经从一个包存储库安装了 PHP V5,那么一般而言您就可以使用 Advanced Packaging Tool (APT) 添加 MDK 了。例如,在 Ubuntu 内核 9.10 上,键入 apt-get install sudo apt-get install --install-recommends --yes php5-dev

截止到 2009 年底,SWIG 的最新版本是 V1.3.40(参见 参考资料)。下载 tarball (一个由 gzip 压缩了的 TAR 文件),将它解压缩,然后针对您的系统配置这些代码,构建并安装这个软件。(要想找到所有的配置选项,运行 ./configure --help)。清单 1 提供了下载、解压缩和安装 SWIG 所需的命令。

清单 1. 下载、解压缩和安装 SWIG
$ wget http://prdownloads.sourceforge.net/swig/swig-1.3.40.tar.gz
$ tar xzf swig-1.3.40.tar.gz 
$ cd swig-1.3.40
$ ./configure 
$ make
$ sudo make install 
$ which swig
/usr/local/bin/swig

构建一个扩展

让我们构建一个扩展来用 Linux mcrypt 库加密和解密消息。PHP 提供了一个 mcrypt 库,但它不过是对此库的 C 版本稍作修饰后的结果。现在,让我们构建两个更为简洁的方法:一个用来加密字符串,另一个用来解密字符串。

在 Ubuntu 或与其相似的系统上,您可以用 APT 安装恰当的 mcrypt 库和头文件:$ sudo apt-get install libmcrypt-dev libmcrypt4 mcrypt libmhash2

若您宁愿从头开始构建,或者您的分布版内不包括 mcrypt,那么可以从它的主页上下载源代码(参见 参考资料)。替代了 cryptmcrypt 实用程序也依赖于 libmhash,因此必须在编译 mcrypt 之前构建 libmhash。清单 2 给出了构建 libmhash 所需的代码。

清单 2. 构建 libmhash
$ # libmhash
$ wget http://sourceforge.net/projects/mhash/files/mhash/0.9.9.9/\
  mhash-0.9.9.9.tar.bz2/download
$ tar xfj mhash-0.9.9.9.tar.bz2
$ cd mhash-0.9.9.9
$ ./configure
$ make
$ sudo make install 

# libmcrypt
$ wget ftp://mcrypt.hellug.gr/pub/crypto/mcrypt/libmcrypt/\
  libmcrypt-2.5.7.tar.gz
$ tar xfz libmcrypt-2.5.7.tar.gz
$ cd libmcrypt-2.5.7
$ ./configure
$ make
$ sudo make install

$ # mcrypt
$ wget wget http://sourceforge.net/projects/mcrypt/files/MCrypt/2.6.8/\
  mcrypt-2.6.8.tar.gz/download
$ tar xfz mcrypt-2.6.8.tar.gz
$ cd mcrypt-2.6.8
$ ./configure
$ make
$ sudo make install

接下来,创建此扩展的 C 代码。代码中最有趣的函数是位于 清单 3 底部的 encode()decode()。二者均具有两个形参 — 一个字符串和一个计数 — 并且均返回字符串。前者加密一个纯文本的字符串,返回其编码;后者解密一个加密了的字符串并返回纯文本。字符串可以是任意长度。

上述代码使用了 Data Encryption Standard-Electronic Codebook (DES-ECB) 算法。秘密密匙可以是八个字符的任意字符串,并可显示为 12345678(只用于演示目的)。如果您要与其他方交换加密了的消息,需要获得交换方的密钥或创建一个新的密钥并共享它。(加密算法独立于架构和语言,不过,发送者和接收者均必须知道秘密密匙。)

清单 3. PHP 扩展的 C 代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mcrypt.h>

char *encode( char *string, int length );
char *decode( char *string, int length );

MCRYPT start() {
  MCRYPT td = mcrypt_module_open( "des", NULL, "ecb", NULL );
  if ( td == MCRYPT_FAILED ) {
    return( MCRYPT_FAILED );
  }

  if ( mcrypt_enc_self_test( td ) != 0 ) {
    return( MCRYPT_FAILED );
  }

  int i;
  char *IV;
  int iv_size = mcrypt_enc_get_iv_size( td );
  if ( iv_size != 0 ) {
    IV = calloc( 1, iv_size );
    for ( i = 0; i < iv_size; i++ ) {
      IV[ i ] = rand();
    }
  }

  int keysize = mcrypt_enc_get_key_size( td );
  char *key = calloc( 1, keysize );
  memcpy(key, "12345678", keysize);

  i = mcrypt_generic_init ( td, key, keysize, IV );
  if ( i < 0 ) {
    mcrypt_perror( i );
    exit(1);
  }

  return( td );
}


void end( MCRYPT td ) {
  mcrypt_generic_deinit( td );
  mcrypt_module_close( td );
}


#define B64_DEF_LINE_SIZE   72
#define B64_MIN_LINE_SIZE    4

/*
** encode 3 8-bit binary bytes as 4 '6-bit' characters
*/
void encodeblock( unsigned char in[3], unsigned char out[4], int len ) {
  static const char 
    cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  out[0] = cb64[ in[0] >> 2 ];
  out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
  out[2] = (unsigned char) (len > 1 ? cb64[ 
    ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
  out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
}


char *base64encode( char *input, int size ) {
  int i, x, len;
  unsigned char in[3], out[4];
  char *target = calloc( 1, ( ( size + 2 ) / 3 ) * 4 + 1 );
  char *t = target;

  for ( x = 0; x < size; ) {
    len = 0;

    for( i = 0; i < 3; i++ ) {
      if ( x < size ) {
        len++;
        in[i] = input[x++];
      }
      else {
        in[i] = 0;
      }
    }

    if( len ) {
      encodeblock( in, out, len );
      for( i = 0; i < 4; i++ ) {
        *t++ = out[i];
      }
    }
  }

  return( target );
}


char *encode( char *string, int length ) {
  MCRYPT td = start();
  int blocksize = mcrypt_enc_get_block_size( td );
  int cryptsize = ( ( length  + blocksize - 1 ) / blocksize ) * blocksize;
  char *target = calloc( 1,  cryptsize );

  memcpy( target, string, length );

  if ( mcrypt_generic( td, target, cryptsize ) != 0 ) {
    fprintf( stderr, "Code failing" );
  }

  end( td );

  char* result = base64encode( target, cryptsize );

  free( target );
  return result;
}


char *decode( char *string, int length ) {
  MCRYPT td = start();
  int blocksize = mcrypt_enc_get_block_size( td );
  char *block_buffer = calloc( 1, blocksize );
  int decryptlength = (length + blocksize - 1) / blocksize * blocksize;
  char *target = calloc( 1, decryptlength );

  memcpy(target, string, length);

  mdecrypt_generic( td, target, decryptlength );

  end( td );

  free(block_buffer);
  return( target );
}

将清单 3 内的代码复制并粘贴到一个名为 secret.c 的新文件。下一个任务是使用 SWIG 自身的语法描述此扩展的 API。

SWIG 文件

在目前的开发阶段,可以在 secret.c 基础上手动构建一个扩展。但 SWIG 可以帮您完成这一艰苦工作 — 并且只需采用少量的伪代码。清单 4 所示的 secret.i 就是这个新扩展的 SWIG 模板。

清单 4. secret.i
%module secret

%{
  extern char *encode( char *string, int length );
  extern char *decode( char *string, int length );
%}

extern char *encode( char *string, int length );
extern char *decode( char *string, int length );

对 SWIG 语法和选项的全面解读超出了本文的讨论范围。完整的文档,可以在网上找到(参见 参考资料)。简单来讲,SWIG 文件一般在第 1 行声明扩展的名称。文件的其他部分声明入口点。就是这些内容。编译需要几个步骤:第一步是生成代码的包装程序:$ swig -php secret.i

SWIG 将 secret.i 转变为 secret_wrap.c。接下来的几个步骤是构建和链接这些包装程序代码、这个扩展以及 mcrypt 库。请务必用 -fpic 选项构建每个 C 源文件,因该选项可以生成独立于位置的代码,这非常适合于共享库。

$ cc -fpic -c secret.c
$ gcc `php-config --includes` -fpic -c secret_wrap.c
$ gcc -shared *.o -o secret.so -lmcrypt
$ sudo cp secret.so `php-config --extension-dir`

前两个命令构建 C 源代码。第三个命令构建 PHP 扩展。-lmcrypt 选项解析此扩展内入口点在 mcrypt 库内的那些调用。第四个命令会将这个新的 PHP 扩展放入合适的目录以便它可被 PHP 加载。

在开始编写 PHP 代码之前,最后一步是加载这个扩展。打开适当的 php.ini 文件 — 或者针对 Apache,或者针对 PHP 的命令行变体 — 并添加一行代码:extension=secret.so

如果不确定该编辑哪个 php.ini 文件,可以查询 PHP 本身。创建如下所示的这个共三行代码的程序并使用浏览器或交互解释器运行它:

<?php
  phpinfo();
?>

查找以 Loaded Configuration File 开头的一行代码。例如,在本文使用的测试平台上,此程序生成了输出 Loaded Configuration File => /etc/php5/cli/php.ini。因此,要编辑的文件是 /etc/php5/cli/php.ini。

编写 PHP 代码

有了这个出色的新扩展后,就可以开始编写 PHP 了。清单 5 显示了 code.php。

清单 5. code.php
<?php
  include("secret.php");

  $string = "Double secret probation";
  $base64encode = secret::encode($string, strlen($string));
  $base64decode = base64_decode($base64encode);
  $decode = secret::decode( $base64decode, strlen($base64decode));

  echo $decode . "\n";
?>

行 1 加载此扩展。行 4 编码字符串 Double secret probation 并使用 Base64 将加密了的这个字符串转变为可打印的字符,以便于用电子邮件等程序传输。行 5 对 Base64 编码进行解码以生成原始字符,行 6 将加密消息解密成原始文本。

假设将这些代码保存在 coder.php 内,并在系统的 /usr/local/lib 下安装了 mcrypt 库,用 PHP CLI 命令就可以运行这些示例代码了:

$ LD_LIBRARY_PATH=/usr/local/lib php ./code.php 
Double secret probation

结束语

SWIG 是重用现有代码的一种极好的方式。用 SWIG 包装 C 或 C++ 库,并将结果集成到您的下一个 Web 或系统应用程序。而更妙的是,SWIG 还可以从相同的 .i 文件生成面向其他脚本语言的包装程序。您只需编写一次扩展,之后,就可以与 PHP、Perl、Python、Ruby 和其他开发人员共享它了。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=470174
ArticleTitle=用 SWIG 构建 PHP 扩展
publish-date=03012010