编写php的extension实例

来源:互联网 发布:英菲克网络机顶盒好吗 编辑:程序博客网 时间:2024/06/13 14:57
 

一、说明

前端时间因为客户的原因折腾了一下asp的扩展,在ATL的帮助下写一个asp的模块还是很容易的。不巧的时刚刚折腾完asp的COM就碰到另一个客户的问题。客户想给系统集成ICBC的接口,但是用ICBC的接口需要用他们的提供的库函数去
 
1. sign对发送的数据进行签名
2. getcertid获取用户证书的版本
3. verifySign对签名后的数据进行验证
 
问题是ICBC只给了现成的COM组件,意味在只能在Win的主机上使用。俺们公司只有linux的主机,在*nix上就要自己想办法调用ICBC给的静态库了。对此我们有两个想法
 
1.用ICBC的静态库做一个独立的执行文件,用PHP的系统调用函数来执行这个独立的执行文件
 
2.将ICBC的静态库做出一个PHP的扩展
 
方法一应该比较简单,但是远不如方法二的灵活。搞成PHP的扩展,只要服务器编译一次,服务器上的所有客户都可以用的。
 
有ASP的前科,俺觉得搞个PHP的也不是什么难事。操起google搜了一通,结果发现Zend已经写了一个如何编写php extension的教程:
 
http://devzone.zend.com/article/1021-Extension-Writing-Part-I-Introduction-to-PHP-and-Zend
 
 
浏览完牛人的大作,更是信心十足,php的扩展其实很简单,分七步走:
 
1. 制作编译配置文件:config.m4
 
2. 执行phpize生成扩展的框架
 
3. 在生成的php_xxx.h中声明自己写的函数
 
4. 在xxx.c中实现自己的函数
 
5. 编译自己的扩展
 
6. 将生成的xxx.so拷贝到php.ini中指定的extensions_dir
 
7. 在php.ini中打开xxx.so的调用
 
 
此问题问题唯一搞的地方就是在config.m4中折腾出正确的Makefile,因为Zend的教程中没有提到,俺自己也折腾了好久,才搞出来。
 

二、实际操作

1.建立工作环境
 
将php源码包解开,我的版本的php-4.4.4,转到源码包中的ext目录建立一个新的目录叫icbc,然后在icbc目录下touch三个文件config.m4、php_icbc.h、icbc.c
 
2.建立config.m4 内容如下:
 

PHP_ARG_ENABLE(icbc, whether to enable ICBC support,
[ --enable-icbc Enable ICBC support])

if test "$PHP_ICBC" = "yes"; then
  AC_DEFINE(HAVE_ICBC, 1, [Whether you have ICBC])

  if test -f ./libicbcapi.a; then
      PHP_ADD_LIBRARY_WITH_PATH(icbcapi, ./, ICBCAPI_SHARED_LIBADD)
      PHP_SUBST(ICBCAPI_SHARED_LIBADD)
      AC_MSG_RESULT(checking for libicbcapi.a is OK)
  else
      AC_MSG_RESULT(libicbcapi.a not found)
      AC_MSG_ERROR(Please make sure the libicbcapi.a is in current directory)
  fi

  PHP_NEW_EXTENSION(icbc, icbc.c, $ext_shared)
fi

第三行判断是否要启用icbc扩展,

第五行判断ICBC的静态库是否在当前目录(phpdir/ext/icbc)下

第六、七行将ICBC的静态库加入到编译环境中

3.在php_icbc.h中声明我们要导出的函数icbc_sign、icbc_vsign、icbc_getCertID
 

#ifndef PHP_ICBC_H
#define PHP_ICBC_H
extern zend_module_entry icbc_module_entry;
#define phpext_icbc_ptr &icbc_module_entry
#ifdef PHP_WIN32
#define PHP_ICBC_API __declspec(dllexport)
#else
#define PHP_ICBC_API
#endif
#ifdef ZTS
#include "TSRM.h"
#endif

PHP_MINIT_FUNCTION(icbc);
PHP_MSHUTDOWN_FUNCTION(icbc);
PHP_RINIT_FUNCTION(icbc);
PHP_RSHUTDOWN_FUNCTION(icbc);
PHP_MINFO_FUNCTION(icbc);
/*Modify youself here*/
PHP_FUNCTION(icbc_sign);
PHP_FUNCTION(icbc_vsign);
PHP_FUNCTION(icbc_getCertID);
/****End of Self control section***/


#ifdef ZTS
#define ICBC_G(v) TSRMG(icbc_globals_id, zend_icbc_globals *, v)
#else
#define ICBC_G(v) (icbc_globals.v)
#endif

#endif /* PHP_ICBC_H */

涉及到我们也就

PHP_FUNCTION(icbc_sign);
PHP_FUNCTION(icbc_vsign);
PHP_FUNCTION(icbc_getCertID);

其他的都是PHP各个状态的入口函数声明

4.编写这三个函数的实现:

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

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_icbc.h"
#include "icbcapi.h"
static int le_icbc;
zend_function_entry icbc_functions[] = {
    PHP_FE(icbc_sign,NULL)
    PHP_FE(icbc_vsign,NULL)
    PHP_FE(icbc_getCertID,NULL)
    {NULL, NULL, NULL} /* Must be the last line in icbc_functions[] */
};
zend_module_entry icbc_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    "icbc",
    icbc_functions,
    PHP_MINIT(icbc),
    PHP_MSHUTDOWN(icbc),
    PHP_RINIT(icbc), /* Replace with NULL if there's nothing to do at request start */
    PHP_RSHUTDOWN(icbc), /* Replace with NULL if there's nothing to do at request end */
    PHP_MINFO(icbc),
#if ZEND_MODULE_API_NO >= 20010901
    "0.1", /* Replace with version number for your extension */
#endif
    STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_ICBC
ZEND_GET_MODULE(icbc)
#endif
PHP_MINIT_FUNCTION(icbc)
{
    return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(icbc)
{
    return SUCCESS;
}
PHP_RINIT_FUNCTION(icbc)
{
    return SUCCESS;
}
PHP_RSHUTDOWN_FUNCTION(icbc)
{
    return SUCCESS;
}
PHP_MINFO_FUNCTION(icbc)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "icbc support", "enabled");
    php_info_print_table_end();   
}
PHP_FUNCTION(icbc_sign)
{
    char* src;
    int srclen;
    char* pkey;
    int keylen;
    char* keypass;
    int keypasslen;
    char* signedbuf;
    int signedbuflen;
    FILE* fp;
    char key[2000];
    int rcc;

    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"sss",&src,&srclen,&pkey,&keylen,&keypass,&keypasslen) == FAILURE){
        return;
    }

    fp = fopen(pkey,"rb");
    if(fp == NULL)
    {
        return;
    }
    fseek(fp,2,SEEK_SET);
    fread((void*)key,609,1,fp);
    fclose(fp);

    if(rcc = sign(src,srclen,key,607,keypass,&signedbuf,&signedbuflen) >= 0){
        base64enc(signedbuf,signedbuflen,&signedbuf,&signedbuflen);
        src = estrndup(signedbuf,signedbuflen);
        if(signedbuf != NULL) infosec_free(signedbuf);
        RETURN_STRING(src,1);
    }else{
        RETURN_LONG(rcc);
    }
}PHP_FUNCTION(icbc_vsign)
{
    char* src;
    int srclen;
    char* cert;
    int certlen;
    char* vsignedbuf;
    int vsignedbuflen;
    FILE* fp;
    char vcert[2000];
    int rcc;

    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"sss",&src,&srclen,&cert,&certlen,&vsignedbuf,&vsignedbuflen) == FAILURE){
        return;
    }

    fp = fopen(cert,"rb");
    if(fp == NULL)
    {
        return;
    }
    fread((void*)vcert,1525,1,fp);
    fclose(fp);

    base64dec(vsignedbuf,vsignedbuflen,&vsignedbuf,&vsignedbuflen);

    if(rcc = verifySign(src,srclen,vcert,1525,vsignedbuf,vsignedbuflen) >= 0){
        if(vsignedbuf != NULL) infosec_free(vsignedbuf);
        RETURN_TRUE;
    }else{
        if(vsignedbuf != NULL) infosec_free(vsignedbuf);
        RETURN_LONG(rcc);
    }
}
PHP_FUNCTION(icbc_getCertID)
{
    char* arg;
    char* certid;
    int arg_len,certidlen;
    FILE* fp;
    char cert[2000];

    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s", &arg,&arg_len) == FAILURE){
        return;
    }

    fp = fopen(arg,"rb");
    if(fp == NULL)
    {
        return;
    }
    fread((void*)cert,1525,1,fp);
    fclose(fp);

    if(!getCertID(cert,1525,&certid,&certidlen))
    {
        arg = estrndup(certid,certidlen);
        if(certid != NULL) infosec_free(certid);
        RETURN_STRING(arg,1);
    }else{
        return;
    }
}

 
先在zend_function_entry icbc_functions[]数组中放入我们的要实现的函数名,然后是一堆php各个状态入口函数,详情请看Zend的教程。最后是在PHP_FUNCTION宏定义中放我们声明函数的具体实现。具体实现时难点也就是参数的传入和结果传出,还好PHP已经为我们做了很好的抽象。在Zend的教程中也有详尽的说明,俺就不啰嗦了。关键代码照搬icbc的test.c就行了。
 
5.编译安装我们的库
 
先将ICBC的头文件考到当前目录,重命名为icbcapi.php,将静态库也cp过来,重命名为*nix的标准形式libicbcapi.a,然后运行
phpize
生成configure,运行
./configure --enable-icbc
生成Makefile,这里有一个很搞的地方,在生成的Makefile中最后一句中指定ICBC静态库的地方错了,正确的应该是(红色标记地方):
 

$(LIBTOOL) --mode=link $(CC) $(COMMON_FLAGS) $(CFLAGS_CLEAN) $(EXTRA_CFLAGS) $(LDFLAGS) -o $@ -export-dynamic -avoid-version -prefer-pic -module -rpath $(phplibdir) $(EXTRA_LDFLAGS) $(shared_objects_icbc) $(ICBCAPI_SHARED_LIBADD)

改好Makefile后就可以执行

make

如果一切顺利的话会在modules中得到我们的icbc.so,将我们的icbc.so拷贝到/usr/local/lib/php/extensions目录下,然后在php.ini中确认extensions_dir的值是/usr/local/lib/php/extensions,然后加入这句话

extension=icbc.so
重启apache后,就可以在php中直接调用这三个函数了

6.测试程序,要将测试的证书和key文件放到php测试文件的当前目录

<?php
$realpath = dirname(__FILE__);
$key = $realpath."/user.key";
$cert = $realpath."/user.crt";
$src = "zzz";
$passwd = "12345678";

echo "The Cert file information is ";
echo icbc_getCertID($cert);
echo "<br>";
$b64sdata = icbc_sign($src,$key,$passwd);

echo "The string ".$src." encrypt by icbc api is ".$b64sdata."(base64 encoded)<br>";
echo "Now we check it weather is correct....<br>";

if(icbc_vsign("zzz",$cert,$b64sdata)){
   echo "The signtrue to ".$src." is right!!<br>";
}else{
   echo "The signtrue to ".$src." is wrong!!<br>";
   exit();
}
?>

 

 
原创粉丝点击