自己動手用 C 擴充 PHP

來源:互聯網
上載者:User
自己動手用 C 擴充 PHP

關鍵詞: PHP擴充                                          

環境要求: Linux, GCC

首先,我們應該選擇如何去構建我們的擴充模組, 有三種方式:
1. External Modules
外部模組,也就是編譯成共用庫,用dl()函數動態載入。
好處: (1)不需要重新編譯 PHP (2)PHP體積小,因為不需要編譯進PHP
缺點: (1)每次*.php指令碼執行都需要用 dl()去載入,效率較低
(2)每次都要調用dl()
2. Built-in Modules
編譯進PHP
好處: (1)不需要動態載入,模組在php指令碼裡面可以直接使用.
(2)不需要將模組編譯成.so共用庫,因為直接編譯進PHP。
缺點: (1)對模組的改變都需要重新編譯PHP
(2)因為編譯進PHP,所以PHP二進位檔案較大,而且多佔點記憶體
3. The Zend Engine
Zend 核心裡實現 (略 ... 有興趣的話可以看 Zend API)

Note: 本人推薦用 第 2 種方式,直接編譯進PHP, 但是在下面樣本裡,我們編譯成外部模組
,因為,外部模組不需要重新編譯 PHP,所以在測試階段先編譯成共用庫,然後用dl()
載入(不過記得在php.ini裡將安全模式設為 Off),測試完後再用第2種方式重新編譯進 PHP.

1. 首先下載 PHP 原始碼, 解壓。 然後我們開始構建代碼結構,可以利用 PHP 原始碼包ext目錄裡提供的 ext_skel 程式去產生我們需要的結構.

[ext]#./ext_skel --extname=haosoft_php_module

這樣會在ext下產生一個 haosoft_php_module 目錄。
目錄裡有 config.m4, haosoft_php_module.h, haosoft_php_module.c 等幾個檔案。

2. haosoft_php_module.h 為模組的標頭檔(熟悉C語言的應該瞭解), 用 VI 開啟,刪除其中的一行:
PHP_FUNCTION(confirm_MyExt_compiled); /* For testing, remove later. */
改為:
PHP_FUNCTION(haosoft_test); /* For testing, remove later. */
說明:
PHP_FUNCTION() 是一個 Zend Macro,作用是聲名一個 C 函數,使他在 PHP 指令碼裡可用, 參數 是函數的名字,我們這裡的樣本函數為 haosoft_test.

3. haosoft_php_module.c 為主檔案,裡麵包含了實現, VI 開啟,我們刪除掉
PHP_FUNCTION(confirm_MyExt_compiled)
{
char *arg = NULL;
int arg_len, len;
char string[256];

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
return;
}
len = sprintf(string, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "haosoft_php_module", arg);
RETURN_STRINGL(string, len, 1);
}
增加:
PHP_FUNCTION(haosoft_test) {
zend_printf("This is my php module !");
}

4. 現在,我們開始編譯我們的模組,因為要編譯成 so,我們選擇手工編譯。
進入 ext 目錄:

開始編譯,編譯成*.o對象檔案,注意這個"-DCOMPILE_DL_HAOSOFT_PHP_MODULE",

在 haosoft_php_module.c 裡你可以找到下面的語句

#ifdef COMPILE_DL_HAOSOFT_PHP_MODULE
ZEND_GET_MODULE(haosoft_php_module)
#endif

HAOSOFT_PHP_MODULE 是大寫模組名,你可以換成其他名,但是必須保證這個預先處理常量與
上面的語句裡一致,至於上面語句的 ZEND_GET_MODULE(haosoft_php_module) 我們將在下一篇文
章講解它的意思.

[ext]# cc -fpic -DCOMPILE_DL_HAOSOFT_PHP_MODULE=1 -I/usr/local/include -I. -I../main -I.. -I../TSRM -I../Zend -c -o haosoft_php_module/haosoft_php_module.o haosoft_php_module/haosoft_php_module.c

執行完之後會在 目錄下產生一個haosoft_php_module.o檔案,接下來 串連:
[ext]# cc -shared -L/usr/local/lib -rdynamic -o haosoft_php_module/haosoft_php_module.so haosoft_php_module/haosoft_php_module.o

這樣會在目錄下產生了我們需要的 haosoft_php_module.so 檔案。
然後我們要建立一個目錄
[ext]# mkdir -p /usr/local/php/lib/php/extensions/no-debug-non-zts-20041030
上面的 20041030 的號碼是Zend內部模組API號碼,如果你不知道的話,你先建立一個php文
件:
test.php

dl("abc.so");
?>

在瀏覽器裡運行該檔案會出現錯誤資訊, 可以在錯誤資訊裡得到這個號碼,請根據自己情況根據這個號碼與你的PHP安裝目錄更改路徑。

建立完目錄後接下來,我們把haosoft_php_module.so移動到剛剛的目錄裡去
[ext]# mv haosoft_php_module/haosoft_php_module.so /usr/local/php/lib/php/extensions/no-debug-non-zts-20041030

OK! 我們可以測試剛建立的模組了,目前模組裡只有一個 haosoft_test() 的無參數方法。

建立 php 檔案:

test.php

// 載入剛建立的模組
dl("haosoft_php_module.so");
// 調用函數
haosoft_test();
?>

執行。 看到顯示 "This is my module !" 了嗎?

5. 好了,模組編譯,並測試無誤後,我們可以將他編譯進 PHP 了,回到 PHP原始碼包根目錄
執行:
[php-5.0.6]#./buildconf --force
[php-5.0.6]#./configure --enable-haosoft_php_module ... 也許你還有其他選項
[php-5.0.6]#make
[php-5.0.6]#make install
[php-5.0.6]#sh /etc/init.d/httpd restart ... 這裡是重啟 apache

---------------------------------------------------------------
本樣本在我 PHP5.0.6 上編譯通過, 下一章我們再詳細分析 haosoft_php_module.h, haosoft_php_module.c 這兩個檔案裡的內容,以及有參數,有傳回值函數的定義及使用.
4 點了,偶該睡覺了! 有時間再寫下一章。 希望以上內容對大家會有所協助!

在 上一章 自己動手用 C 擴充 PHP(一)裡我們介紹了
如何編寫自己的函數,接下來,我們開始編寫一個有參數
有傳回值的擴充函數。

因為 Zend 沒有正式的函數調用語法檢查支援,所以我們在編寫擴充函數的時候必須得“小心”, 也就是我們必須得嚴格地進行調用合法性檢查,首先,我們寫一個函數:

#include "string.h"

PHP_FUNCTION(haosoft_strcat) {
    char* pc_arg_one = NULL;
    char* pc_arg_tow = NULL;
    long arg_one_len;
    long arg_tow_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &pc_arg_one, &arg_one_len, &pc_arg_tow, &arg_tow_len) == FAILURE) {
        return;
    }
    RETURN_STRINGL(strcat(pc_arg_one, pc_arg_tow), (arg_one_len + arg_tow_len), 1);
}

我們來分析這個函數:
1.
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &pc_arg_one, &arg_one_len, &pc_arg_tow, &arg_tow_len) == FAILURE) {
        return;
    }
這句就是進行參數檢查與取得參數的語句,zend_parse_parameters() 是 Zend 提供的 API 函數,第一個參數是參數的數量,可以通過 Zend 提供的 ZEND_NUM_ARGS() 來取得,TSRMLS_CC 是必須的, 第三個參數 "ss" 指明參數的類型(s - 字串),這個函數有2個字串參數,所以為 "ss", 之後的參數就是你要取得的參數值了,注意 如果參數類型為 "s" 的話, 參數值後面還要傳一個 long 來取得字串的長度. zend_parse_parameters 函數成功的話會返回
SUCCESS, 失敗則返回 FAILURE ,並且輸出錯誤資訊.

2.
    RETURN_STRINGL(strcat(pc_arg_one, pc_arg_tow), (arg_one_len + arg_tow_len), 1);
這句的作用是返回一個值,RETURN_STRINGL() 為返回字串,詳細資料可以參閱 Zend API 參考手冊。這句作用是串連兩個字串參數並返回.

----------------------------------------------------------
編譯好擴充模組後,現在我們可以在 php 裡調用這個函數
<?php
$s_result = haosoft_strcat("a", "b");
echo $s_result;
?>
輸出 : ab
---------------------------------------------------

說明:

函數參數類型指定字元表,和一些返回函數傳回值的函數可以參閱 Zend 文檔,這裡就不列出了

因為Zend API 說明文檔不完全,許多 Macro 及大多數 Zend API 函數都沒有詳細說明, 呵呵,所以如有錯誤地方,歡迎指正.

今天查閱了些Zend的資料, 用 C++ 寫了一下PHP的擴充,覺得真的非常麻煩,一個
只有一個 MyClass::GetString()方法 的類,要實現在php裡
<?php
$obj = new MyClass();
$string = $obj->GetString();
?>
寫了好幾百行代碼,對於習慣物件導向形式編程的人簡直是噩夢,希望Zend能在這方面改善一下了,等有時間我會再寫寫如何用C++編寫PHP的擴充。

待續 ...

在上兩章裡講述了擴充模組的基本用法,相信讀者已經能夠構建自己的擴充模組了,接下來,我們來看看幾個重要的函數:

1. PHP_MINIT_FUNCTION(MyModule);
當模組被Zend Engine(ZE)載入後,例如Apache啟動,載入了PHP模組,ZE會對每一個擴充模組調用此函數(如果有的話),可以在該函數裡進行一些初始化操作.

2. PHP_RINIT_FUNCTION(MyModule);
對於每一個"使用"該模組的PHP指令碼請求前,都執行該函數(如果有的話),最好的例子: Session 擴充模組,如果在一個PHP指令碼裡執行session.start(), Session模組的PHP_RINIT_FUNCTION()將被調用. 詳情可以看看 session模組 的原始碼

3. PHP_RSHUTDOWN_FUNCTION(MyModule);
與PHP_RINIT_FUNCTION()相反,該函數是在一個 PHP 指令碼執行完畢後執行.

4. PHP_MSHUTDOWN_FUNCTION(MyModule);
當ZE收到shutdown訊號後,例如apache卸載PHP模組,ZE 對每一個模組調用此函數,最後關閉自己的核心子系統.

-------------------------------------------------------
如何使用以上的函數?

首先,在標頭檔("mymodule.h")裡聲明:

PHP_MINIT_FUNCTION(MyModule);
PHP_MSHUTDOWN_FUNCTION(MyModule);
PHP_RINIT_FUNCTION(MyModule);
PHP_RSHUTDOWN_FUNCTION(MyModule);

然後在你的 "mymodule.c" 裡實現zend_module_entry結構:

zend_module_entry MyModule_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"MyModule",
MyModule_functions,
PHP_MINIT(MyModule),
PHP_MSHUTDOWN(MyModule),
PHP_RINIT(MyModule),
PHP_RSHUTDOWN(MyModule),
NULL,
#if ZEND_MODULE_API_NO >= 20010901
"0.1",
#endif
STANDARD_MODULE_PROPERTIES
};

// 實現 PHP_MINIT_FUNCTION() 等等 ...
PHP_MINIT_FUNCTION(MyModule)
{
// 你的實現代碼 ...
return SUCCESS;
}

其實 ext 目錄下的 ext_skel 程式建立擴充模組代碼架構的時候都自動化幫你實現了這些結構,依照說明填充需要的代碼即可.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.