2011
Nov
17

網頁好讀版

編譯 PHP Extension 基本指令

PHP Extension 是什麼呢?很多寫PHP的工程師,或許不知道 PHP Extension是怎麼做出來的,其實 PHP 的最底層是使用 c 語言,每一句 PHP 的語法,都是透過 c 語言來實現,所以我們可以藉由 c ,去加強 PHP 的功能,讓PHP 更方便使用,一個正常的PHP Extension製作時,要先編寫 config.m4 這個檔案,雖然這個檔案不難寫,不過他與 Makefile的格式落差很大,這裡就介紹如何使用 Makefile 來編譯 PHP Extension ,先介紹一下正常的 PHP Extension編譯 流程,phpize會建立 Makefile檔案, ./configure 這個指令會自動檢查相關package 是否安裝

  • 建立 c code 與 config.m4
  • phpize (下 Linux 指令 phpize)
  • ./configure
  • make
  • mv modules/xxx.so /extension : 將編譯出來的so檔,搬到extension目錄下,這樣就算完成一個 PHP Extension

製作 php extension Makefile

基實 phpize的行為就是建立 Makefile, libtool 等等工具,./configure就是檢查一些設定是否正常,以及路徑設定,即然我都要自已寫 php extension了,那就跳過這兩個過程吧,自已寫 Makefile。

  • 首先我們先使用phpize ,自動生出一個 Makefile,然後再把他改成我們想要的格式。
  • phpize & mv Makefile Makefile.global : 把Makefile改成Makefile.global

修改 Makefile.global 成我們要的樣子 Makefile.global範例

  • 加入php 安裝的目錄 PHP_DIR = /home/program/php (路徑自已修改吧)
  • 將部分變數的值改到 PHP_DIR ,如 prefix = $(PHP_DIR),phpincludedir = $(PHP_DIR)/include/php
  • phplibdir = $(SRC_PATH)/modules :指定編譯完成後,so檔的路徑
  • PHP_PECL_EXTENSION = extension name //(注意名稱不要重覆)
  • srcdir , builddir , top_srcdir , top_builddir ,修改至當前目錄,要用絕對目錄

簡化過後的 Makefile

Makefile.global弄半天,最後終於簡化完成,以後要編譯php extension就方便多了,正式寫一個要用來編譯程式的 Makefile ,你看! 下面的 PHP Makefile 多麼簡短。

Example
  1. SRC_PATH = $(shell pwd)
  2. LDEF = -DCOMPILE_DL_MyExtension
  3. CXXFILE =myClass.cc extension.cc
  4. EXTRA_CXXFLAGS=
  5. CXXOUTPUT = MyExtension
  6. include Makefile.global
  7. cp:
  8. sudo cp ./modules/$(CXXOUTPUT).so /home/php_extension/

make 的結果

Example
  1. create myClass.lo
  2. create myClass.o
  3. create extension.lo
  4. create extension.o
  5. create MyExtension.la
  6. create MyExtension.so
  7. //最後産生 MyExtension.so 成功

php zend_module_entry 說明

php extension的程序入口,在這裡需要指定 Extension 名稱,program Functions ,init function ,shutdown function等等

Example
  1. zend_module_entry myClass_module_entry = {
  2. #if ZEND_MODULE_API_NO >= 20010901
  3. STANDARD_MODULE_HEADER, //如果是php 5.3以上版本才會用到
  4. #endif
  5. PHP_MY_CLASS_EXTNAME, // 我的Extension名稱
  6. my_function, /* Function的定義,要先指定有那些function可以給php呼叫 */
  7. PHP_MINIT(myClass),
  8. NULL, /* MSHUTDOWN */
  9. NULL, /* RINIT */
  10. NULL, /* RSHUTDOWN */
  11. PHP_MINFO(myClass), /* MINFO */
  12. #if ZEND_MODULE_API_NO >= 20010901
  13. PHP_MY_CLASS_EXTVER,
  14. #endif
  15. STANDARD_MODULE_PROPERTIES
  16. };

php zend_function_entry 說明

上一步第二個參數(my_function),定義了 function entry 的名稱,所以我在這裡要去定義 function entry 的內容,指定給 php 有哪些 function 可以呼叫,範例中我加入兩個 function ,分別是 simple 與 add , 定義一個 function 要使用 PHP_FE,若是要定義一個 class 的 method ,則必須使用 PHP_ME。

Example
  1. zend_function_entry my_function[] = {
  2. PHP_FE(simple, NULL)
  3. PHP_FE(add,NULL)
  4. {NULL, NULL, NULL}
  5. };

php extension 基本function說明

  • PHP_MINIT_FUNCTION : 每一次執行php時,會先載入 extension,這時須要執行的程式寫在這裡。
  • PHP_MSHUTDOWN_FUNCTION : PHP執行結束時,會執行的程式。
  • PHP_MINFO_FUNCTION : extension說明資料,會出現在 phpinfo
Example
  1.  
  2. PHP_MINFO_FUNCTION(MyExtension)
  3. {
  4. php_info_print_table_start();
  5. php_info_print_table_header(2,"jk function , remove the same value in array", "enabled");
  6. php_info_print_table_row(2, "first line", "Available");
  7. php_info_print_table_end();
  8. }
  9.  
  10.  

接收php 傳進來的變數的方式,使用 zend_parse的function,有下列兩種,其中 [sal,lsl]代表要接放的變數型態。

  • zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"sal",xxx,xx)
  • zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,num_args TSRMLS_CC,"lsl", xx,xx,xx,xx)
    • l : 代表 long
    • s : string
    • a : array
    • b : boolean
    • d : double
  • 回傳result 給php的方式有下列幾種
    • RETURN_LONG : 回傳 long
    • RETURN_STRING : 回傳字串
    • RETURN_DOUBLE : 回傳倍精數
    • RETURN_BOOL : 回傳 boolean (true or false)

建立一個 php extension function

要寫一個 extension function ,就是使用 PHP_FUNCTION 這個 function ,而傳進去的第一個參數,就是 function 名稱,寫好之後,就可以在 php 裡使用 simple("xxx");

Example
  1. PHP_FUNCTION(simple)
  2. {
  3. char* str = NULL;
  4. char* tmp = new char[50];
  5. string result = "";
  6. int str_len = 0;
  7. if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
  8. "s",
  9. &str,&str_len) == FAILURE) {
  10. RETURN_NULL();
  11. }
  12. sprintf(tmp , "Your input string is [%s]" , str);
  13. result = tmp;
  14.  
  15. delete[] tmp;
  16. RETURN_STRING(const_cast<char*>(result.c_str()),1);
  17.  
  18. }

第二個範例是 function entry 中定義的 add ,主要功能是將第一個參數與第二個參數做加總的功能,在 php extension 中,所有的數字回僨都是使用 RETURN_LONG,不需要依 int ,long,float 去判斷回傳值,這是由於 php 變數型態很自由,不用區分得這麼細。

Example
  1. PHP_FUNCTION(add)
  2. {
  3. int num_args = ZEND_NUM_ARGS();
  4. if (num_args != 2) {
  5. RETURN_LONG(0);
  6. }
  7. int result = 0,int1,int2;
  8. if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
  9. "ll",
  10. &int1,&int2) == FAILURE) {
  11. RETURN_NULL();
  12. }
  13. result = int1 + int2;
  14.  
  15.  
  16. RETURN_LONG(result);
  17.  
  18. }
  19.  

相關問題

warning: deprecated conversion from string constant to 'char*'

我升級 gcc 4.2.0 後,編譯就會出現這個 Warning,使用 gcc 3.4.6 就沒事了。

第二招,碰到 Warning不要停掉,在 Makefile檔案中,將 -Werror 這個屬性拿掉吧

undefined symbol:__gxx_personality_v0

因為你使用 c++ 語法,但是卻沒有載入 c++ 的 library

解法:將 Makefile 的 CC 加上 -lstdc++ ,加上這句後,編譯時,會自動載入 /usr/lib/libstdc++.so。

Example
  1. CC = cc -lstdc++

錯誤: Invalid library (maybe not a PHP library)的處理

這代表,php在載入 so檔時,找不到程序入口,c & c++的程序入口是 main ,而 php extension的程序入口是 zend_module_entry ,但是因為我有使用到c++ 語法,c++有個特性,在編譯時,會自動亂改變數及function名稱。

Example
  1. zend_module_entry MyExtension_module_entry

像這句語法,經過 c++ 編譯後,就可能變成 MyExtension_module_entryii,結果造成找不到程序入口而報錯。

解法就是多加一句 extern "C"

Example
  1. zend_module_entry MyExtension_module_entry = {
  2. .
  3. .
  4. .
  5. }
  6.  
  7. extern "C" {
  8. ZEND_GET_MODULE(MyExtension)
  9. }

為何要加 extern "C" 呢?

我們知道C++ 有 overloading 的功能,一個 function 可以有多種不同的參數數量,可是 c++ 是怎麼辦到的呢,其實他在編譯程式的時候,就會自動做 function name 的 mapping ,例如下面的例子,可是單純的 c 語言並不懂這個東西,所以當 c 語言要去讀取 c++ 的function 時,就必需加上 extern "C" ,強迫 c++ 不要亂改名稱,這樣 c語言才能正確的執行 function 。

Example
  1. int test(int a){}
  2. int test(char b,char c){}
  3.  
  4. //編譯後
  5. test(int a) => test_1(int a)
  6. test(char b , char c) => test_2(char b , char c)

網頁好讀版