2015年11月20日 星期五

[PHP] PHP 也有 Day #21 - c9s 主講:從 C 語言到 PHP Extension 學習筆記

這是個很硬的題目:從 C 語言到 PHP Extension

萬事起頭難
我覺得因為有 c9s 整理重點、地雷和 skeleton 才會看起來比較簡單,但其實...這很不簡單!!
上次寫 c 大概在 7 年前了~
所以需要沉澱一下...
進入正題

一些觀念

關於變數 (http://php.net/manual/en/internals2.variables.intro.php)

"PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting."

所以有幾個重要的觀念要會
1. copy on write
2. reference counting
3. php 變數是沒有型態的,但實際在 c 語言中是使用 zval 這個 struct
typedef struct _zval_struct {
    zvalue_value value;        /* variable value */
    zend_uint refcount__gc;    /* reference counter */
    zend_uchar type;           /* value type */
    zend_uchar is_ref__gc;     /* reference flag */
} zval;
其中 zvalue_value 是下列 sturct
typedef union _zvalue_value {
    long lval;                 /* long value */
    double dval;               /* double value */
    struct {
        char *val;
        int len;               /* this will always be set for strings */
    } str;                     /* string (always has length) */
    HashTable *ht;             /* an array */
    zend_object_value obj;     /* stores an object store handle, and handlers */
} zvalue_value;

關於檔案結構 (http://php.net/manual/en/internals2.structure.files.php)

config.m4:configure 用的
// 這裡紅字部份就是在 ./configure --enable-foo 是相同的
PHP_ARG_ENABLE(foo, whether to enable foo extension support,
  [--enable-foo Enable foo extension support])

// php_foo.c 是實作的檔案
if test $PHP_FOO != "no"; then
    PHP_NEW_EXTENSION(foo, php_foo.c, $ext_shared)
fi

php_counter.h:自己的 extension header
php_counter.c:自己的 extension 主程式 (說明是寫 counter.c 改成跟 h 同名,檔案多時比較知道是什麼)
config.w32:windows 用的,先忽略

PHP  Life Cycle
https://fntlnz.github.io/php-extensions-development/#/3
http://www.phpxs.com/post/1039


第一個 Hello World

剛好最近在學習 Larvel 直接拿 homested 做環境 (什麼都不用安裝就能編譯)
(是 vagrant,可以參考 http://laravel.tw/docs/5.1/homestead 自行安裝)

1.  ssh 進 homested 後

2. 下載 c9s 整理好的 skeleton
git clone https://github.com/c9s/php-ext-skeleton
3. 改一下程式
vi php_foo.c
改這裡,改成
PHP_FUNCTION(foo_hello) {
    RETURN_STRING("Hello World", 1);
}
4. 編譯
phpize
./configure --enable-foo
make 
make install
編好的會在 php-ext-skeleton/modules 目錄下
install 完的 .so 檔案會在 php default extension 目錄下 (這環境在 /usr/lib/php5/20131226/)
加入 php.ini 加入 extension
例如
echo "extension=foo.so" >  /etc/php5/cli/conf.d/20-foo.ini 
(這裡只有 cli 才會 work,請依自已的環境調整)

5.執行看看
php -r "echo foo_hello();"
應該會顯示 Hello World


寫測試

(https://qa.php.net/write-test.php)
mkdir tests
vi tests/foo.phpt
寫入
--TEST--
Check foo_hello
--FILE--
<?php
echo foo_hello();
--EXPECT--
Hello World
--TEST--
測試名稱
--FILE--
寫 php code
--EXPECT--
比對 echo 的資料
make test
如果有錯誤,在 tests 目錄下會會產生該測試的結果檔 (log, diff, exp, out),可以由這些檔案去看原因

GDB debug
(https://github.com/rcousens/packer-php7-dev/blob/master/doc/02-debug-php-extension.md)

實戰寫一個自己的 extension

(http://php.net/manual/en/internals2.funcs.php)

實作一個 swap function
可以用 c9s 提的指令快速換內容
perl -i -pe 's/foo_hello/swap/g' config.m4 *.h *.c
perl -i -pe 's/foo/swap/g' config.m4 *.h *.c
perl -i -pe 's/Foo/Swap/g' config.m4 *.h *.c
perl -i -pe 's/FOO/SWAP/g' config.m4 *.h *.c
mv foo_hello.c swap.c
mv foo_hello.h swap.h
一般在 php 寫應該像這樣
function swap(&$a, &$b) {
    $tmp = $a;
    $a = $b;
    $b = $tmp;
}
C 應該是像這樣
void swap(int *a, int *b) {
    int tmp=*a;
    *a=*b;
    *b=tmp;
}
開始寫 swap.c 主要是改以下內容
PHP_FUNCTION(swap) {
    zval *za = NULL;
    zval *zb = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &za, &zb) == FAILURE) {
        RETURN_FALSE;
    }
    zval tmp = *za;
    *za = *zb;
    *zb = tmp;
    RETURN_TRUE;
}

1. zval 就是  php 的變數型態
因為要實作任何型態都能 swap 的 function 所以我使用 zval 宣告兩個指標變數 *za、*zb

2. PHP_FUNCTION(swap) 跟在程式開頭寫下面這段有關
static const zend_function_entry swap_functions[] = {
  PHP_FE(swap, NULL)
  PHP_FE(f1, NULL)
  PHP_FE(f2, NULL)
  PHP_FE_END
};
PHP_FE 是註冊在 php 時,call function name
PHP_FUNCTION(swap) 的 swap 就是指在實作 swap 這個 function
如果是實作 f1那就是 PHP_FUNCTION(f1)

3. zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &za, &zb)
這行就是取得 php call function 時的參數
這裡跟 scanf 很像就是用 za 和 zb 接下來
然後 "zz" 就是接幾個參數和該參數型態,這裡用 zval

4. 取得參數後就是 swap 的主邏輯了

完整 Source:https://github.com/ffbli666/php-extension-examples

延伸思考和學習(研究ing)

Reference:


沒有留言:

張貼留言