在php程序中需要用到C代碼,應該是下面兩種情況:
- if(!extension_loaded("hello")) {
- dl_local("hello.so");
- }
- function dl_local( $extensionFile ) {
- //make sure that we are ABLE to load libraries
- if( !(bool)ini_get( "enable_dl" ) || (bool)ini_get( "safe_mode" ) ) {
- die( "dh_local(): Loading extensions is not permitted./n" );
- }
- //check to make sure the file exists
- if( !file_exists(dirname(__FILE__) . "/". $extensionFile ) ) {
- die( "dl_local(): File '$extensionFile' does not exist./n" );
- }
- //check the file permissions
- if( !is_executable(dirname(__FILE__) . "/". $extensionFile ) ) {
- die( "dl_local(): File '$extensionFile' is not executable./n" );
- }
- //we figure out the path
- $currentDir = dirname(__FILE__) . "/";
- $currentExtPath = ini_get( "extension_dir" );
- $subDirs = preg_match_all( "////" , $currentExtPath , $matches );
- unset( $matches );
- //lets make sure we extracted a valid extension path
- if( !(bool)$subDirs ) {
- die( "dl_local(): Could not determine a valid extension path [extension_dir]./n" );
- }
- $extPathLastChar = strlen( $currentExtPath ) - 1;
- if( $extPathLastChar == strrpos( $currentExtPath , "/" ) ) {
- $subDirs--;
- }
- $backDirStr = "";
- for( $i = 1; $i <= $subDirs; $i++ ) {
- $backDirStr .= "..";
- if( $i != $subDirs ) {
- $backDirStr .= "/";
- }
- }
- //construct the final path to load
- $finalExtPath = $backDirStr . $currentDir . $extensionFile;
- //now we execute dl() to actually load the module
- if( !dl( $finalExtPath ) ) {
- die();
- }
- //if the module was loaded correctly, we must bow grab the module name
- $loadedExtensions = get_loaded_extensions();
- $thisExtName = $loadedExtensions[ sizeof( $loadedExtensions ) - 1 ];
- //lastly, we return the extension name
- return $thisExtName;
- }//end dl_local()
- PHP_FUNCTION(hello_strdiff)
- {
- char *r1 = NULL, *r2 = NULL;
- int n = 0, m = 0;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &r1, &n, &r2, &m) == FAILURE) {
- return;
- }
- while(n && m && *r1 == *r2) {
- r1++;
- r2++;
- n--;
- m--;
- }
- if(n == 0) RETURN_LONG(m);
- if(m == 0) RETURN_LONG(n);
- int d[n+1][m+1];
- int cost;
- int i,j;
- for(i = 0; i <= n; i++) d[i][0] = i;
- for(j = 0; j <= m; j++) d[0][j] = j;
- for(i = 1; i <= n; i++) {
- for(j = 1; j <= m; j++) {
- if(r1[i-1] == r2[j-1]) cost = 0;
- else cost = 1;
- int a = MIN(d[i-1][j]+1,d[i][j-1]+1);
- a = MIN(a, d[i-1][j-1]+cost);
- d[i][j] = a;
- }
- }
- RETURN_LONG(d[n][m]);
- }
Boolean | b |
zend_bool |
Long | l |
long |
Double | d |
double |
String | s |
char*, int |
Resource | r |
zval* |
Array | a |
zval* |
Object | o |
zval* |
zval | z |
zval* |
Traditional | Non-Persistent | Persistent |
---|---|---|
malloc(count) calloc(count, num) |
emalloc(count) ecalloc(count, num) |
pemalloc(count, 1) *pecalloc(count, num, 1) |
strdup(str) strndup(str, len) |
estrdup(str) estrndup(str, len) |
pestrdup(str, 1) pemalloc() & memcpy() |
free(ptr) |
efree(ptr) |
pefree(ptr, 1) |
realloc(ptr, newsize) |
erealloc(ptr, newsize) |
perealloc(ptr, newsize, 1) |
malloc(count * num + extr) ** |
safe_emalloc(count, num, extr) |
safe_pemalloc(count, num, extr) |
更好的文章:http://www.toplee.com/blog/56.html#pp1
本節沒有介紹關於腳本引擎基本構造的一些知識,而是直接進入擴展的編碼講解中,因此不要擔心你無法立刻獲得對擴展整體把握的感覺。假設你正在開發一個網站,需要一個把字符串重複n次的函數。下面是用PHP寫的例子:
function self_concat($string, $n)
{
$result = "";
for ($i = 0; $i < $n; $i++) {
$result .= $string;
}
return $result;
}
self_concat("One", 3) returns "OneOneOne".
self_concat("One", 1) returns "One".
假設由於一些奇怪的原因,你需要時常調用這個函數,而且還要傳給函數很長的字符串和大值n。這意味着在腳本里有相當巨大的字符串連接量和內存重新分配過程,以至顯著地降低腳本執行速度。如果有一個函數能夠更快地分配大量且足夠的內存來存放結果字符串,然後把$string重複n次,就不需要在每次循環迭代中分配內存。
爲擴展建立函數的第一步是寫一個函數定義文件,該函數定義文件定義了擴展對外提供的函數原形。該例中,定義函數只有一行函數原形self_concat() :
string self_concat(string str, int n)
函數定義文件的一般格式是一個函數一行。你可以定義可選參數和使用大量的PHP類型,包括: bool, float, int, array等。
保存爲myfunctions.def文件至PHP原代碼目錄樹下。
該是通過擴展骨架(skeleton)構造器運行函數定義文件的時機了。該構造器腳本叫ext_skel,放在PHP原代碼目錄樹的ext/目錄下(PHP原碼主目錄下的README.EXT_SKEL提供了更多的信息)。假設你把函數定義保存在一個叫做myfunctions.def的文件裏,而且你希望把擴展取名爲myfunctions,運行下面的命令來建立擴展骨架
./ext_skel --extname=myfunctions --proto=myfunctions.def
這個命令在ext/目錄下建立了一個myfunctions/目錄。你要做的第一件事情也許就是編譯該骨架,以便編寫和測試實際的C代碼。編譯擴展有兩種方法:
☞ 作爲一個可裝載模塊或者DSO(動態共享對象)
☞ 靜態編譯到PHP
因爲第二種方法比較容易上手,所以本章採用靜態編譯。如果你對編譯可裝載擴展模塊感興趣,可以閱讀PHP原代碼根目錄下的README.SELF-CONTAINED_EXTENSIONS文件。爲了使擴展能夠被編譯,需要修改擴展目錄ext/myfunctions/下的config.m4文件。擴展沒有包裹任何外部的C庫,你需要添加支持--enable-myfunctions配置開關到PHP編譯系統裏(–with-extension 開關用於那些需要用戶指定相關C庫路徑的擴展)。可以去掉自動生成的下面兩行的註釋來開啓這個配置。
PHP_ARG_ENABLE(myfunctions, whether to enable myfunctions support,
[ --enable-myfunctions Include myfunctions support])
現在剩下的事情就是在PHP原代碼樹根目錄下運行./buildconf,該命令會生成一個新的配置腳本。通過查看./configure --help輸出信息,可以檢查新的配置選項是否被包含到配置文件中。現在,打開你喜好的配置選項開關和--enable-myfunctions重新配置一下PHP。最後的但不是最次要的是,用make來重新編譯PHP。
ext_skel應該把兩個PHP函數添加到你的擴展骨架了:打算實現的self_concat()函數和用於檢測myfunctions 是否編譯到PHP的confirm_myfunctions_compiled()函數。完成PHP的擴展開發後,可以把後者去掉。
<?php
print confirm_myfunctions_compiled("myextension");
?>
運行這個腳本會出現類似下面的輸出:
"Congratulations! You have successfully modified ext/myfunctions
config.m4. Module myfunctions is now compiled into PHP."
另外,ext_skel腳本生成一個叫myfunctions.php的腳本,你也可以利用它來驗證擴展是否被成功地編譯到PHP。它會列出該擴展所支持的所有函數。
現在你學會如何編譯擴展了,該是真正地研究self_concat()函數的時候了。
下面就是ext_skel腳本生成的骨架結構:
/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
}
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;
long n;
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
php_error(E_WARNING, "self_concat: not yet implemented");
}
/* }}} */
自動生成的PHP函數週圍包含了一些註釋,這些註釋用於自動生成代碼文檔和vi、Emacs等編輯器的代碼摺疊。函數自身的定義使用了宏PHP_FUNCTION(),該宏可以生成一個適合於Zend引擎的函數原型。邏輯本身分成語義各部分,取得調用函數的參數和邏輯本身。
爲了獲得函數傳遞的參數,可以使用zend_parse_parameters()API函數。下面是該函數的原型:
zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, …);
第一個參數是傳遞給函數的參數個數。通常的做法是傳給它ZEND_NUM_ARGS()。(ZEND_NUM_ARGS() 來表示對傳入的參數“有多少要多少”)這是一個表示傳遞給函數參數總個數的宏。第二個參數是爲了線程安全,總是傳遞TSRMLS_CC宏,後面會講到。第三個參數是一個字符串,指定了函數期望的參數類型,後面緊跟着需要隨參數值更新的變量列表。因爲PHP採用鬆散的變量定義和動態的類型判斷,這樣做就使得把不同類型的參數轉化爲期望的類型成爲可能。例如,如果用戶傳遞一個整數變量,可函數需要一個浮點數,那麼zend_parse_parameters()就會自動地把整數轉換爲相應的浮點數。如果實際值無法轉換成期望類型(比如整形到數組形),會觸發一個警告。
下表列出了可能指定的類型。我們從完整性考慮也列出了一些沒有討論到的類型。
類型指定符 |
對應的C類型 |
描述 |
l |
long |
符號整數 |
d |
double |
浮點數 |
s |
char *, int |
二進制字符串,長度 |
b |
zend_bool |
邏輯型(1或0) |
r |
zval * |
資源(文件指針,數據庫連接等) |
a |
zval * |
聯合數組 |
o |
zval * |
任何類型的對象 |
O |
zval * |
指定類型的對象。需要提供目標對象的類類型 |
z |
zval * |
無任何操作的zval |
爲了容易地理解最後幾個選項的含義,你需要知道zval是Zend引擎的值容器[1]。無論這個變量是布爾型,字符串型或者其他任何類型,其信息總會包含在一個zval聯合體中。本章中我們不直接存取zval,而是通過一些附加的宏來操作。下面的是或多或少在C中的zval, 以便我們能更好地理解接下來的代碼。
typedef union _zval {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zval;
在我們的例子中,我們用基本類型調用zend_parse_parameters(),以本地C類型的方式取得函數參數的值,而不是用zval容器。
爲了讓zend_parse_parameters()能夠改變傳遞給它的參數的值,並返回這個改變值,需要傳遞一個引用。仔細查看一下self_concat():
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
注意到自動生成的代碼會檢測函數的返回值FAILUER(成功即SUCCESS)來判斷是否成功。如果沒有成功則立即返回,並且由zend_parse_parameters()負責觸發警告信息。因爲函數打算接收一個字符串l和一個整數n,所以指定 ”sl” 作爲其類型指示符。s需要兩個參數,所以我們傳遞參考char * 和 int (str 和 str_len)給zend_parse_parameters()函數。無論什麼時候,記得總是在代碼中使用字符串長度str_len來確保函數工作在二進制安全的環境中。不要使用strlen()和strcpy(),除非你不介意函數在二進制字符串下不能工作。二進制字符串是包含有nulls的字符串。二進制格式包括圖象文件,壓縮文件,可執行文件和更多的其他文件。”l” 只需要一個參數,所以我們傳遞給它n的引用。儘管爲了清晰起見,骨架腳本生成的C變量名與在函數原型定義文件中的參數名一樣;這樣做不是必須的,儘管在實踐中鼓勵這樣做。
回到轉換規則中來。下面三個對self_concat()函數的調用使str, str_len和n得到同樣的值:
self_concat("321", 5);
self_concat(321, "5");
self_concat("321", "5");
str points to the string "321", str_len equals 3, and n equals 5.
str 指向字符串"321",str_len等於3,n等於5。
在我們編寫代碼來實現連接字符串返回給PHP的函數前,還得談談兩個重要的話題:內存管理、從PHP內部返回函數值所使用的API!!
用於從堆中分配內存的PHP API幾乎和標準C API一樣。在編寫擴展的時候,使用下面與C對應(因此不必再解釋)的API函數:
emalloc(size_t size);
efree(void *ptr);
ecalloc(size_t nmemb, size_t size);
erealloc(void *ptr, size_t size);
estrdup(const char *s);
estrndup(const char *s, unsigned int length);
在這一點上,任何一位有經驗的C程序員應該象這樣思考一下:“什麼?標準C沒有strndup()?”是的,這是正確的,因爲GNU擴展通常在Linux下可用。estrndup()只是PHP下的一個特殊函數。它的行爲與estrdup()相似,但是可以指定字符串重複的次數(不需要結束空字符),同時是二進制安全的。這是推薦使用estrndup()而不是estrdup()的原因。
在幾乎所有的情況下,你應該使用這些內存分配函數。有一些情況,即擴展需要分配在請求中永久存在的內存,從而不得不使用malloc(),但是除非你知道你在做什麼,你應該始終使用以上的函數。如果沒有使用這些內存函數,而相反使用標準C函數分配的內存返回給腳本引擎,那麼PHP會崩潰。
這些函數的優點是:任何分配的內存在偶然情況下如果沒有被釋放,則會在頁面請求的最後被釋放。因此,真正的內存泄漏不會產生。然而,不要依賴這一機制,從調試和性能兩個原因來考慮,應當確保釋放應該釋放的內存。剩下的優點是在多線程環境下性能的提高,調試模式下檢測內存錯誤等。
還有一個重要的原因,你不需要檢查這些內存分配函數的返回值是否爲null。當內存分配失敗,它們會發出E_ERROR錯誤,從而決不會返回到擴展。
從PHP函數中返回值
擴展API包含豐富的用於從函數中返回值的宏。這些宏有兩種主要風格:第一種是RETVAL_type()形式,它設置了返回值但C代碼繼續執行。這通常使用在把控制交給腳本引擎前還希望做的一些清理工作的時候使用,然後再使用C的返回聲明 ”return” 返回到PHP;後一個宏更加普遍,其形式是RETURN_type(),他設置了返回類型,同時返回控制到PHP。下表解釋了大多數存在的宏。
設置返回值並且結束函數 |
設置返回值 |
宏返回類型和參數 |
RETURN_LONG(l) |
RETVAL_LONG(l) |
整數 |
RETURN_BOOL(b) |
RETVAL_BOOL(b) |
布爾數(1或0) |
RETURN_NULL() |
RETVAL_NULL() |
NULL |
RETURN_DOUBLE(d) |
RETVAL_DOUBLE(d) |
浮點數 |
RETURN_STRING(s, dup) |
RETVAL_STRING(s, dup) |
字符串。如果dup爲1,引擎會調用estrdup()重複s,使用拷貝。如果dup爲0,就使用s |
RETURN_STRINGL(s, l, dup) |
RETVAL_STRINGL(s, l, dup) |
長度爲l的字符串值。與上一個宏一樣,但因爲s的長度被指定,所以速度更快。 |
RETURN_TRUE |
RETVAL_TRUE |
返回布爾值true。注意到這個宏沒有括號。 |
RETURN_FALSE |
RETVAL_FALSE |
返回布爾值false。注意到這個宏沒有括號。 |
RETURN_RESOURCE(r) |
RETVAL_RESOURCE(r) |
資源句柄。 |