转载:https://blog.youkuaiyun.com/baozoumingren/article/details/75735780
说明:windows上的头文件和cpp直接那过来 直接
phpize
./configure --with-php-config=/usr/bin/php-config
make(如果用到c++11(如auto),请在上步生成的makefile中g++后面追加-std=c++11)
make install
如果扩展库依赖与别的库和头文件,需要在config.m4中添加
//这里是增加的
PHP_ADD_INCLUDE(./include) //额外头文件路径
PHP_ADD_LIBRARY(stdc++, 1, BAOZOU_SHARED_LIBADD)
PHP_ADD_LIBRARY_WITH_PATH(hello, ./lib, BAOZOU_SHARED_LIBADD) //额外库路径
PHP_REQUIRE_CXX()
PHP_SUBST(BAOZOU_SHARED_LIBADD)
在生成的makefile中可以增加宏定义 和c++11的一些东西
CPPFLAGS = -DHAVE_CONFIG_H -DBOOST_NO_EXCEPTIONS
CXX = g++ -std=c++11
时隔一段时间,PHP版本变更,导致原先生成的静态库不兼容(不同linux环境下编译的c++静态/动态库也可能出现不兼容现象,建议换PHP版本的同时重新编一下c++静态/动态库),经过仔细琢磨发现PHP版本高于5.5,也就是5.6版本,生成扩展骨架有些许出入,总体来说还是一样的!(本文不涉及PHP7版本)
在linux下调用C++源码写PHP扩展:
1:准备
1.1:下载相应版本的PHP源码包,可以上PHP官网或者网上搜索。本文采用的是php-5.5.38,下载地址:http://pan.baidu.com/s/1bo626mj (炸了没更新的话请自行去网上下载)
1.2:linux操作系统,本文采用的是centoOS 64位。
1.3:LAMP/LNMP环境。
2:编写C++静态库
2.1:编写头文件:hello.h
#include <string>
std::string hello_joint(std::string a, std::string b);
1
2
2.2:实现头文件定义的函数:hello.cpp
#include "hello.h"
std::string hello_joint(std::string a, std::string b)
{
std::string str = a+b;
return str;
}
1
2
3
4
5
6
2.3:生成hello.o文件
g++ -c hello.cpp
1
2.4:生成静态库libhello.a文件
ar -r libhello.a hello.o
1
2.5:写个简单的test.cpp测试下:
#include <iostream>
#include "hello.h"
int main()
{
std::string a = std::string("Hello ");
std::string b = std::string("World!");
std::cout<<hello_joint(a, b)<<std::endl;
return 0;
}
1
2
3
4
5
6
7
8
9
2.6:编译
g++ test.cpp libhello.a
1
2.7:运行
./a.out
1
如果终端输出“Hello World!”,说明你的C++静态库制作成功!
3:生成PHP扩展骨架
3.1:cd到源码压缩包文件夹,分别执行以下命令:
tar -zxvf php-5.5.38.tar.gz
cd php-5.5.38/ext/
1
2
3.2:使用ext下的ext_skel文件生成PHP扩展骨架
./ext_skel --extname=baozou
1
命名为baozou没有啥特别意义,这里命名会影响到生成的.so扩展文件,你也可以命名为test,那么生成的扩展文件名就是test.so
4:修改扩展骨架内容,生成PHP扩展
4.1:cd到baozou目录下编辑config.m4,需要改的地方大概归结为三处:
①找到如下三行,并把前面的注释dnl去掉(:set nu后大约在16~18行)。
PHP_ARG_ENABLE(baozou, whether to enable baozou support,
Make sure that the comment is aligned:
[ --enable-baozou Enable baozou support])
1
2
3
②找到如下的代码,并在它的下面加上相应代码,以支持c++调用(:set nu后大约在20~21行)。
if test "$PHP_BAOZOU" != "no"; then
dnl Write more examples of tests here...
1
2
要加上的相应代码,注意其中的参数名。
说明:
BAOZOU_SHARED_LIBADD,不知道啥意思,反正照着改前缀就行了。
PHP_ADD_INCLUDE(./include),头文件路径,”./”啥意思应该不用说明了。。
PHP_ADD_LIBRARY_WITH_PATH(hello, ./lib, BAOZOU_SHARED_LIBADD),这里的第一个参数是静态库/动态库名字,由于make时使用的是-l参数,所以命名应该为:”lib命名.a”或者”lib命名.so”,如上面的”libhello.a”,这里则填写”hello”即可。第二个参数是路径
if test "$PHP_BAOZOU" != "no"; then
dnl Write more examples of tests here...
//这里是增加的
PHP_ADD_INCLUDE(./include)
PHP_ADD_LIBRARY(stdc++, 1, BAOZOU_SHARED_LIBADD)
PHP_ADD_LIBRARY_WITH_PATH(hello, ./lib, BAOZOU_SHARED_LIBADD)
PHP_REQUIRE_CXX()
PHP_SUBST(BAOZOU_SHARED_LIBADD)
1
2
3
4
5
6
7
8
9
③在该函数的最后一行,把baozou.c改为baozou.cpp
PHP_NEW_EXTENSION(baozou, baozou.cpp, $ext_shared)
4.2:修改baozou.cpp文件
①将baozou.c改名为baozou.cpp
mv baozou.c baozou.cpp
1
②在两处地方加上EXTERN “C”标识,这点不要忘了:
第一处加extern “C”{},并在下面加上需要的c++头文件(:set nu后,约在20行后):
extern "C" { --这里是添加的
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_baozou.h"
} --这里也是添加的
//下面也是添加的,拷贝后最好删除这些中文注释
#include <string>
#include "hello.h"
1
2
3
4
5
6
7
8
9
10
11
12
13
第二处加BEGIN_EXTERN_C()和END_EXTERN_C() (:set nu后,约在70多行左右):
#ifdef COMPILE_DL_BAOZOU
BEGIN_EXTERN_C() ==>添加的
ZEND_GET_MODULE(baozou)
END_EXTERN_C() ==>添加的
#endif
1
2
3
4
5
③在如下的地方,加入函数名称,可以理解为php要调用的函数的声明(:set nu后,约在48行左右;如果是PHP5.6则位置可能不同):
PHP_FE(myfunc, NULL)
1
说明:这里定义的myfunc是能被PHP直接调用的函数名,如大家熟悉的fopen(),fclose();生成扩展后,我们可以直接调用名为myfunc()的函数。
④注意到baozou.cpp中,它编写了一个函数例子confirm_baozou_compiled,现在,我们也需要完成myfunc的函数实现,它接收来自php的参数,并在函数中调用c++函数。
PHP_FUNCTION(myfunc)
{
char *arg1 = NULL, *arg2 = NULL;
int arg1_len, arg2_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"ss",&arg1,&arg1_len,&arg2,&arg2_len) == FAILURE)
return;
std::string a = std::string(arg1);
std::string b = std::string(arg2);
std::string res = hello_joint(a,b);
RETURN_STRING(res.c_str(),res.length(),0);
}
1
2
3
4
5
6
7
8
9
10
11
简单的说明下个人理解(小白求不喷):
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “ss”, &arg1, &arg1_len, &arg2, &arg2_len) == FAILURE)
在这个判断前,定义的参数是传入zend_parse_parameters()函数里的,从PHP捕获过来的值,比如我PHP那边写了个myfunc(‘hello’,’world’),则在这里定义2个char *类型参数,这里是C++语法,不是很懂。
在这个判断后,经过zend_parse_parameters()函数处理的值可以用C++的语法获取到,然后传给C++定义的函数”hello_joint()”。
最后RETURN_STRING(res.c_str(), res.length(),0); 指的是你想返回给PHP的数据类型,返回类型可参考:
http://blog.sina.com.cn/s/blog_4ee5c07b0101e1a1.html
关于zend_parse_parameters()函数:
第一二个参数个人暂时无法解释,第三个参数”ss”,指的是2个string类型的参数,如果多个则多个s,第三个参数以后,则是在这函数上面定义的一些参数,如果是”s”类型的参数,还要定义长度(char*, int 前者接收指针,后者接收长度),如果不是,则不需要定义了。参考:http://laravelacademy.org/post/7233.html
4.3:编写php_baozou.h文件,在PHP_FUNCTION(confirm_baozou_compiled)下增加如下代码(:set nu后大约在47行;在PHP5.6中可能没有PHP_FUNCTION(confirm_baozou_compiled),那么直接把下面代码扔在最后一行就行了。):
PHP_FUNCTION(myfunc);
1
该文件主要是声明PHP函数名,如果没有声明则会报错(被坑过)
:4.4:生成PHP扩展
①在当前目录下新建lib文件夹,将之前制作的libhello.a拷贝到lib里;
②在当前目录下新建include文件夹,将hello.h头文件拷贝到这里。
③利用php工具,按顺序运行如下四个命令(注意php-config路径):
phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make
make install
1
2
3
4
备注1:如果在make过程中提示下面错误,请往下拉找到第⑥步解决。
/usr/bin/ld: /home/lnmp1.4-full/src/php-5.5.38/ext/baozou/lib/libhello.a(hello.o): relocation R_X86_64_32 against `__gxx_personality_v0' can not be used when making a shared object; recompile with -fPIC
/home/lnmp1.4-full/src/php-5.5.38/ext/baozou/lib/libhello.a: could not read symbols: Bad value
collect2: ld returned 1 exit status
make: *** [baozou.la] Error 1
1
2
3
4
备注2:如果中间修改了程序代码什么的,记得要从第一个phpize重新执行来过。
如果生成成功,则会提示你成功生成在XXX目录,复制目录路径cd过去看一下有没有生成baozou.so文件
④修改php.ini配置文件
如果不知道php.ini文件在哪的话,你可以在php程序中输出(echo phpinfo())。
找到如下内容:
Loaded Configuration File => /usr/local/etc/php/5.6/php.ini
1
打开php.ini,找到enable_dl,改为On;
enable_dl = On --在php.ini 中开启php动态加载dl
1
再找到有很多extension=xxxxxx.so的地方,添加下面句子,加载动态库,当然也可以在程序中手动加载。
extension=baozou.so
1
⑤在php程序中测试:
记得重启下php:
/etc/init.d/php-fpm stop
/etc/init.d/php-fpm start
1
2
在php文件中编辑以下代码:
<?php
echo myfunc("hello ", "world!");
1
2
网页输出:
测试成功
⑥如果在make的时候出现如下错误(倒数后四行):
/usr/bin/ld: /home/lnmp1.4-full/src/php-5.5.38/ext/baozou/lib/libhello.a(hello.o): relocation R_X86_64_32 against `__gxx_personality_v0' can not be used when making a shared object; recompile with -fPIC
/home/lnmp1.4-full/src/php-5.5.38/ext/baozou/lib/libhello.a: could not read symbols: Bad value
collect2: ld returned 1 exit status
make: *** [baozou.la] Error 1
1
2
3
4
重点是看第一行错误:
can not be used when making a shared object; recompile with -fPIC
1
虽然该错误的解决方法,百度给的是重新编译一个新的libhello.a静态库文件可以解决,但是在我用的php-5.5.38版本中,静态库是无法编译通过的,就算怎么改也是一样(测试了好久只会报这个错误)
个人的解决方法是:使用动态库。也就是说之前生成的libhello.a静态库文件,要替换成动态库文件libhello.so,生成动态库的方法为:
cd到之前的生成hello.o的文件夹下,执行分别如下命令即可生成libhello.so动态库文件:
g++ -c -fPIC hello.cpp
g++ -shared -fPIC -o libhello.so hello.o
1
2
再cd到之前编译PHP扩展的文件夹,将.so文件拷贝到lib文件夹下,并且删除lib文件夹下之前编译过的.a文件,然后重新执行如下编译代码(注意执行路径要在编译扩展的文件夹下,不是lib文件夹下)
phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make
make install
1
2
3
4
执行make install之后出现以下提示:
[root@localhost baozou]# make install
Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20121212/
1
2
cd到上面路径,执行“ll”查询即可看到生成了扩展文件.so,然后进行复制到extension文件夹下:
cp baozou.so ../../extension/
1
回到上面的④步骤,直到⑤步骤页面输出“hello world”,说明PHP开发扩展成功
小结:
总算写完了,差不多花了一天,并且自己亲手测试整篇文章的可行性,终于可以生成可用的简单的PHP扩展了
这玩意坑比较多,比如网上(以及本文里)生成静态库文件,然后编译PHP扩展时,一直报错(我觉得理论上来说静态库也是可以生成扩展的,如果有人能按照上面方法用静态库编译出PHP扩展的请告知下,评估下错误原因,谢谢了)。自己也不会C++,所以在编译C++的时候撞了很多坑,最后得感谢C++的新入职同事帮我编译过静态库文件,测试发现不行,最后用动态库文件瞎猫碰上死耗子,通过了,由衷感谢C++的同事的帮忙。
---------------------
作者:baozoumingren
来源:优快云
原文:https://blog.youkuaiyun.com/baozoumingren/article/details/75735780
版权声明:本文为博主原创文章,转载请附上博文链接!