據說google的protobuf效率很高,我們決定嘗試一下,使用到php項目中,作為還原序列化的協議,調用.net的soa介面。
由於protobuf的源碼只有java、c++、Python,我選用了c++作為開發語言,進行PHP擴充的開發。
首先,注意到與用c做PHP擴充的幾點不同,主要集中在config.m4中:
PHP_ARG_ENABLE(protophp, whether to enable protophp support,
[ --enable-protophp Enable protophp support])
if test "$PHP_PROTOPHP" != "no"; then
PHP_REQUIRE_CXX()
PHP_ADD_LIBRARY(stdc++, "", EXTRA_LDFLAGS)
PHP_ADD_LIBRARY(protobuf, "", EXTRA_LDFLAGS)
AC_DEFINE(HAVE_PROTOPHPLIB,1,[ ])
CPPFILE="protophp.cpp addressbook.pb.cpp"
PHP_NEW_EXTENSION(protophp, $CPPFILE, $ext_shared)
fi
其中PHP_REQUIRE_CXX()指明了使用c++作為開發語言,所以也就是用g++作為編譯器。
PHP_ADD_LIBRARY將c++標準庫和protobuf的庫包含進來。
同時,由於根據protobuf協議,針對每個data類,都需要有一個.h和.cpp檔案,所以CPPFILE不但要包含我們的PHP擴充源碼,也要包含對應的data類代碼。
另外,我們將.c檔案重新命名為.cpp檔案。這裡,可以看到我們的PHP擴充模組名為:PROTOPHP。
然後,在protophp.cpp中,調用protobuf的parse方法:
tutorial::AddressBook address_book;
address_book.ParseFromString(str);
這時,address_book對象已經產生,但是要使其能被php代碼使用,還要針對每個類,寫一個object to array的方法。
或許還有別的方法?
那麼,這時我們看到,如果使用這種方法,每當添加 一個新的data類,我們需要修改以下4處:
1、添加class.h檔案
2、添加class.cpp 檔案
3、在config.m4的CPPFILE中添加class.cpp
4、在protophp.cpp開頭include <class.h>
應該還有別的更簡便的方法,我繼續學習。
------------------------------------------------------------
繼續更改,希望能夠通過字串類名動態產生c++的類,但是由於C++不支援這樣的動態編譯,所以需要折中修改。
首先,在PHP擴充中維持一個全域數組:
#include "addressbook.pb.h"
// 類名與相應初始函數的映射
typedef void (*cyFactoryParser_Ptr)(string protostr, zval** return_value);
typedef struct {
char * class_name; // 類名
cyFactoryParser_Ptr parser; // 對象初始化+轉化為數組
} TclassNameToFunc;
static TclassNameToFunc classInfo[] = { // 如果有新增類,修改這裡
{"AddressBook", tutorial::AddressBook::cyparser}
};
其中由某個類的字串類名,映射到具體的處理函數。該處理函數需要在每次添加新的data類的時候,添加到該全域數組中。也就是說,除了通過protoc工具根據proto檔案產生c++的.h和.cc檔案之外,還要在.h或者.cc檔案中添加自己的parser函數。
從而,在PHP擴充的函數主題部分,其實就很簡單了:
// 尋找合適的處理函數
for(classInfoIdx=0; classInfoIdx < classInfoLen; ++classInfoIdx){
cla_func = classInfo[classInfoIdx];
if (!strcmp(cla_func.class_name, class_name)){
break;
}
}
// 沒找到
if (classInfoIdx == classInfoLen){
cerr << "can't find!" << endl;
RETURN_FALSE;
}
// 初始化傳回值:數組
array_init(return_value);
// 處理
cla_func.parser(str, &return_value);
這裡,為了能夠直接將C++的對象,變成傳回值返回給php代碼,所以需要在parser函數的定義處 include "php.h"檔案。
但是這裡,就出現了一個error:
/usr/local/include/google/protobuf/descriptor.pb.h:1165:64: error: macro "add_method" requires 3 arguments, but only 1 given
/usr/local/include/google/protobuf/descriptor.pb.h:3316:86: error: macro "add_method" requires 3 arguments, but only 1 given
經過排查protobuf和php的源碼,發現Zend裡有一個宏也叫add_method,所以衝突了。修改protobuf的函數名稱為cyadd_method,然後重新編譯protobuf,重新make,就ok了。