PHP扩展开发教程2 - 编写第一个扩展 hello world

PHP扩展是高级PHP程序员必须了解的技能之一,对于一个初入门的PHP扩展开发者,怎么才能开发一个成熟的扩展,进入PHP开发的高级领域呢?本系列开发教程将手把手带您从入门进入高级阶段。
本教程系列在linux下面开发(推荐使用centos),php版本用的是5.6,并假设您有一定的linux、git操作经验和c/c++基础。
有问题需要沟通的朋友请加QQ技术交流群32550793和我沟通。

我们使用容易上手的PHP-CPP框架来开发PHP扩展,如果您有一定的linux操作经验和c++基础,按照下面的步骤,相信用不了10分钟就能做出属于你自己的第一个扩展出来。
以下示范的操作都是在linux centos系统上完成的,并且已经事先安装了php5.6系列。

一、下载并安装 PHP-CPP

要想使用PHP-CPP编译属于您自己的php扩展,需要先下载PHP-CPP的源码并编译安装。
PHP-CPP有两个框架源码,分别叫 PHP-CPP(新版) 和 PHP-CPP-LEGACY。
PHP-CPP(新版)适合开发PHP-7的扩展,PHP-CPP-LEGACY则适合开发5.X系列的扩展,两套框架的接口一样,学会了其中一个就很容易做出兼容的PHP各版本的扩展出来。
下面我们的操作都以PHP-CPP-LEGACY为例。

如果你会git命令,可以直接在终端命令行敲入以下git命令即可。

# git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP-LEGACY.git

如果不会git也没关系,可以直接用浏览器打开该源码的github仓库网址,下载源码压缩包并解压即可,仓库网址是
https://github.com/CopernicaM...

下载完成后,进入PHP-CPP-LEGACY的源码目录,敲入make命令编译源码,编译完成后会生成开发扩展所需要的相关类库。

# make

接着运行make install命令,把生成的类库和相关开发的头文件安装到linux系统里面去,一会儿编译扩展的时候就可以不用配置头文件和类库目录也能自动连接上了。

# sudo make install

二、下载第一个扩展 helloworld

第一个扩展 helloworld 的源码已经在github上准备好了,直接用git命令克隆,或者手工下载都可以。

# git clone https://github.com/elvisszhang/phpcpp_helloworld.git

进入helloworld源码目录,打开main.cpp,可以看到如下代码结构,已经都给加了中文注释。
其中最重要的就是 get_module 函数,它是扩展的入口函数。

#include <phpcpp.h>
#include <iostream>

//这是PHP里面可以调用的接口函数
void say_hello()
{
    //输出一段欢迎
    Php::out << "hello world from my first extension" << std::endl;
}

/**
 *  告诉编译器get_module是个纯C函数
 */
extern "C" {
    
    /**
     *  本函数在PHP进程一打开就会被访问,并返回一个描述扩展信息的PHP结构指针
     */
    PHPCPP_EXPORT void *get_module() 
    {
        // 必须是static类型,因为扩展对象需要在PHP进程内常驻内存
        static Php::Extension extension("helloworld", "1.0.0");
        
        //这里可以添加你要暴露给PHP调用的函数
        extension.add<say_hello>("say_hello");
        
        // 返回扩展对象指针
        return extension;
    }
}

test.php则是扩展测试用的一段php代码。

<?php
say_hello();

三、编译第一个扩展 helloworld

编译这个扩展很简单,在终端命令行下输入make命令即可。

# make
g++ -Wall -c -O2 -std=c++11 -fpic -o main.o main.cpp
g++ -shared -o helloworld.so main.o -lphpcpp

不出意料的话,就会在源码目录下看到 helloworld.so 这个扩展文件了,可以发现这个文件很小,才14K而已。
不过现在如果你敲一下命令 php -m ,发现php的模块中并没有 helloworld 这个扩展,因为我们还没有把它安装到php的运行环境里。

四、安装第一个扩展 helloworld

我们这里暂时介绍手工安装扩展的方式。

  • 第一步: 先用php-config命令确定一下扩展存放的位置
# php-config --extension-dir
/usr/local/php56/lib/php/extensions/no-debug-non-zts-20131226

上面显示的是我服务器上扩展安装的位置,各人的服务器可能配置不一样。

  • 第二步:然后把 helloworld.so 拷贝到扩展存放目录下。
# cp helloworld.so /usr/local/php56/lib/php/extensions/no-debug-non-zts-20131226/
  • 第三步:修改 php.ini 文件,启用 helloworld 扩展

打开 php.ini文件,加上以下配置项,在php.ini的任意地方新加一行即可。

extension = helloworld.so
  • 第四步:确认 helloworld扩展已经安装成功

使用php -m命令可以查看php目前已经安装的所有扩展。

# php -m | grep helloworld
helloworld

从上面命令行的响应看,helloworld扩展已经安装成功了。

  • 第五步:运行 test.php 确认注册函数能使用

还是在扩展的源码目录,运行以下命令

# php test.php
hello world from my first extension

从上面命令行的响应看,我们通过扩展向php注册的say_hello函数已经成功运行了,是不是感觉很简单,但现在的扩展只会打个招呼,还干不了什么正儿八经的事,我们后面给他完善一下,让他能做更多的事情。

参考文献

PHP-CPP安装以及hello world
PHP-CPP官网教程

如果用PHP不能再满足你的需求,最好的办法就是开发PHP扩展。这有一些好处: 1、增加自己的特殊功能。 2、保护自己的专利代码。 这是几年前的一篇英文文章,现在已被翻译成中文版的。 作者应该是hshq_cn。 链接是:http://bbs3.chinaunix.net/thread-1028798-1-1.html。 现我将此转变为PDF文件,仅有兴趣者参阅。同时非常感谢 原作者及hshq_cn,给我们带来的这么好的资料。里面还有一个幻灯片的,也是很有帮助的文档。另外,再提供一篇相关的文章(http://www.programbbs.com/doc/4083.htm): 编写php的extension实例的方法 所属类别:JSP 推荐指数:★★☆ 文档人气:161 本周人气:1 发布日期:2008-7-3 一、说明 前端时间因为客户的原因折腾了一下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) [Page] 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 */ [Page] 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); [Page] }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); [Page] 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>\"; [Page] }else{ echo \"The signtrue to \".$src.\" is wrong!!<br>\"; exit(); } ?>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值