備註:2017/8/12更新
時隔一段時間,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);
2.2:實現頭文件定義的函數:hello.cpp
#include "hello.h"
std::string hello_joint(std::string a, std::string b)
{
std::string str = a+b;
return str;
}
2.3:生成hello.o文件
g++ -c hello.cpp
2.4:生成靜態庫libhello.a文件
ar -r libhello.a hello.o
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;
}
2.6:編譯
g++ test.cpp libhello.a
2.7:運行
./a.out
如果終端輸出“Hello World!”,說明你的C++靜態庫製作成功!
3:生成PHP擴展骨架
3.1:cd到源碼壓縮包文件夾,分別執行以下命令:
tar -zxvf php-5.5.38.tar.gz
cd php-5.5.38/ext/
3.2:使用ext下的ext_skel文件生成PHP擴展骨架
./ext_skel --extname=baozou
命名爲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])
②找到如下的代碼,並在它的下面加上相應代碼,以支持c++調用(:set nu後大約在20~21行)。
if test "$PHP_BAOZOU" != "no"; then
dnl Write more examples of tests here...
要加上的相應代碼,注意其中的參數名。
說明:
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)
③在該函數的最後一行,把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
②在兩處地方加上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"
第二處加BEGIN_EXTERN_C()和END_EXTERN_C() (:set nu後,約在70多行左右):
#ifdef COMPILE_DL_BAOZOU
BEGIN_EXTERN_C() ==>添加的
ZEND_GET_MODULE(baozou)
END_EXTERN_C() ==>添加的
#endif
③在如下的地方,加入函數名稱,可以理解爲php要調用的函數的聲明(:set nu後,約在48行左右;如果是PHP5.6則位置可能不同):
PHP_FE(myfunc, NULL)
說明:這裏定義的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);
}
簡單的說明下個人理解(小白求不噴):
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);
該文件主要是聲明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:如果在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
備註2:如果中間修改了程序代碼什麼的,記得要從第一個phpize重新執行來過。
如果生成成功,則會提示你成功生成在XXX目錄,複製目錄路徑cd過去看一下有沒有生成baozou.so文件
④修改php.ini配置文件
如果不知道php.ini文件在哪的話,你可以在php程序中輸出(echo phpinfo())。
找到如下內容:
Loaded Configuration File => /usr/local/etc/php/5.6/php.ini
打開php.ini,找到enable_dl,改爲On;
enable_dl = On --在php.ini 中開啓php動態加載dl
再找到有很多extension=xxxxxx.so的地方,添加下面句子,加載動態庫,當然也可以在程序中手動加載。
extension=baozou.so
⑤在php程序中測試:
記得重啓下php:
/etc/init.d/php-fpm stop
/etc/init.d/php-fpm start
在php文件中編輯以下代碼:
<?php
echo myfunc("hello ", "world!");
網頁輸出:
⑥如果在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
重點是看第一行錯誤:
can not be used when making a shared object; recompile with -fPIC
雖然該錯誤的解決方法,百度給的是重新編譯一個新的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
再cd到之前編譯PHP擴展的文件夾,將.so文件拷貝到lib文件夾下,並且刪除lib文件夾下之前編譯過的.a文件,然後重新執行如下編譯代碼(注意執行路徑要在編譯擴展的文件夾下,不是lib文件夾下)
phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make
make install
執行make install之後出現以下提示:
[root@localhost baozou]# make install
Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20121212/
cd到上面路徑,執行“ll”查詢即可看到生成了擴展文件.so,然後進行復制到extension文件夾下:
cp baozou.so ../../extension/
回到上面的④步驟,直到⑤步驟頁面輸出“hello world”,說明PHP開發擴展成功
小結:
總算寫完了,差不多花了一天,並且自己親手測試整篇文章的可行性,終於可以生成可用的簡單的PHP擴展了
這玩意坑比較多,比如網上(以及本文裏)生成靜態庫文件,然後編譯PHP擴展時,一直報錯(我覺得理論上來說靜態庫也是可以生成擴展的,如果有人能按照上面方法用靜態庫編譯出PHP擴展的請告知下,評估下錯誤原因,謝謝了)。自己也不會C++,所以在編譯C++的時候撞了很多坑,最後得感謝C++的新入職同事幫我編譯過靜態庫文件,測試發現不行,最後用動態庫文件瞎貓碰上死耗子,通過了,由衷感謝C++的同事的幫忙。
相關文章:
Linux下g++編譯與使用靜態庫和動態庫:http://blog.csdn.net/u013256622/article/details/51811072
PHP擴展調用C++靜態庫:http://www.cnblogs.com/fall12/p/6344219.html