Linux下調用C++源碼寫PHP擴展

備註: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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章