Just for fun——PHP7擴展編寫中的宏

原文連接php

PHP內核架構

圖片描述

  • SAPI是PHP的最上層,它是PHP的應用接口層,對於源碼目錄爲sapi
  • main是PHP的主要代碼,主要是輸入/輸出,Web通訊,以及PHP框架的初始化操做,對於源碼目錄爲main
  • ZendVM是PHP解釋器的主要實現,即ZendVM,對於源碼目錄爲Zend 截一張php-src的圖,目錄都有對應

clipboard.png

PHP的生命週期

PHP生命週期
PHP根據不一樣SAPI的實現,各階段的執行狀況有些差別。譬如cli模式的話,完整地經歷了這些階段,而Fastcgi模式下則在啓動時執行一次模塊初始化,而後各個請求只經歷請求初始化,執行請求腳本,請求關閉這幾個階段。

PHP擴展

開發者能夠經過C/C++實現自定義的功能,經過擴展嵌入到PHP中。 編寫擴展的步驟:git

  1. 經過ext目錄下ext_skel腳本生成擴展的基本框架./ext_skel --extname=module (module is the name of your extension)
  2. 修改config.m4配置:設置編譯配置參數、設置擴展源文件
  3. 編寫擴展源代碼
  4. 生成configure:寫完後先phpize(在php的bin目錄下)運行一下
  5. 編譯&安裝: ./configure、 make、make install,而後改一下php.ini文件,添加一下.so文件

舉例

操做系統:CentOS Linux release 7.3.1611 PHP版本:PHP 7.1.11github

生成骨架

./ext_skel --extname=my_test --no-help
複製代碼

--no-help是略去註釋代碼(乾淨點) 生成目錄my_test:segmentfault

clipboard.png

查看C文件

my_test.c
/* $Salamander$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_my_test.h"



static int le_my_test;




PHP_MINIT_FUNCTION(my_test)
{
	return SUCCESS;
}


PHP_MSHUTDOWN_FUNCTION(my_test)
{
	return SUCCESS;
}



PHP_RINIT_FUNCTION(my_test)
{
#if defined(COMPILE_DL_MY_TEST) && defined(ZTS)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif
	return SUCCESS;
}



PHP_RSHUTDOWN_FUNCTION(my_test)
{
	return SUCCESS;
}


PHP_MINFO_FUNCTION(my_test)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "my_test support", "enabled");
	php_info_print_table_end();

}


const zend_function_entry my_test_functions[] = {
	PHP_FE_END
};


zend_module_entry my_test_module_entry = {
	STANDARD_MODULE_HEADER,
	"my_test",
	my_test_functions,
	PHP_MINIT(my_test),
	PHP_MSHUTDOWN(my_test),
	PHP_RINIT(my_test),	
	PHP_RSHUTDOWN(my_test),
	PHP_MINFO(my_test),
	PHP_MY_TEST_VERSION,
	STANDARD_MODULE_PROPERTIES
};


#ifdef COMPILE_DL_MY_TEST
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(my_test)
#endif

複製代碼

能夠注意到這裏有一些api

  • PHP_MINIT_FUNCTION
  • PHP_MSHUTDOWN_FUNCTION
  • PHP_RINIT_FUNCTION
  • PHP_RSHUTDOWN_FUNCTION
  • PHP_MINFO_FUNCTION 這些是PHP提供的鉤子函數,PHP執行到不一樣的階段時回調各個擴展定義的鉤子函數,定義完成後,最後設置一下zend_module_entry對應的函數指針便可。 回顧以前的PHP的生命週期,也就是說(=>指對應某個階段): PHP_MINIT_FUNCTION => 模塊初始化階段(M就是module的含義,init就是initial) PHP_MSHUTDOWN_FUNCTION => 模塊關閉階段(M就是module的含義,後面就是shutdown) PHP_RINIT_FUNCTION => 請求初始化(R就是request的含義,init就是initial) PHP_RSHUTDOWN_FUNCTION => 請求關閉階段(R就是request的含義,後面就是shutdown) PHP_MINFO_FUNCTION 指獲取模塊信息 最後,設置zend_module_entry這個結構體
zend_module_entry my_test_module_entry = {
	STANDARD_MODULE_HEADER,
	"my_test",
	my_test_functions,
	PHP_MINIT(my_test),
	PHP_MSHUTDOWN(my_test),
	PHP_RINIT(my_test),	
	PHP_RSHUTDOWN(my_test),
	PHP_MINFO(my_test),
	PHP_MY_TEST_VERSION,
	STANDARD_MODULE_PROPERTIES
};
複製代碼

獲取各個鉤子函數的指針,有對對應的宏PHP_MINIT,PHP_MSHUTDOWN,PHP_RINIT,PHP_RSHUTDOWN,PHP_MINFO數組

註冊函數

分爲兩步:bash

  1. 定義函數,能夠經過PHP_FUNCTION()或ZEND_FUNCTION()宏來完成函數聲明
  2. 註冊函數,PHP提供了zend_function_entry,擴展只需爲每一個內部函數生成這樣一個結構,而後將全部函數的結構數組提供給zend_module_entry->functions便可 For Example:
PHP_FUNCTION(my_func)
{
    // 具體實現
}

複製代碼

展開後php7

void zif_my_func(zend_execute_data *execute_data, zval *return_value)
{
    // ...
}
複製代碼

zend_function_entry能夠經過宏PHP_FE或ZEND_FE生成(FE即function entry)。架構

const zend_function_entry my_test_functions[] = {
    PHP_FE(my_func, NULL)
	PHP_FE_END
};
複製代碼

my_test_functions就是這個擴展註冊的函數數組。 最後,它設置在了zend_module_entry(第三個參數)框架

zend_module_entry my_test_module_entry = {
	STANDARD_MODULE_HEADER,
	"my_test",
	my_test_functions,
	PHP_MINIT(my_test),
	PHP_MSHUTDOWN(my_test),
	PHP_RINIT(my_test),	
	PHP_RSHUTDOWN(my_test),
	PHP_MINFO(my_test),
	PHP_MY_TEST_VERSION,
	STANDARD_MODULE_PROPERTIES
};
複製代碼

函數參數解析

PHP提供了一個方法將zend_execute_data上的參數解析到指定變量上。

//file: Zend/zend_API.h
ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...)
複製代碼
  • num_args:參數數量,用ZEND_NUM_ARGS()能夠獲取
  • type_spec 爲參數解析規則,是一個字符串
  • 最後一個是可變參數,指定要解析到的變量地址 舉例:
PHP_FUNCTION(my_func)
{
    zval *arr;
    if(zend_parse_parameters(ZEND_NUM_ARGS(), "a", &a) == FAILURE) {
        RETURN_FALSE;
    }
    ...
}
複製代碼

若是有多個變量type_spec能夠變爲"la",l表示整型,a表示數組(另外還有b:布爾型,s:字符串型,o:對象) ,後面則改成&a, &b

函數返回值

能夠設置return_value,但PHP提供了設置了設置返回值的宏

#define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
#define RETURN_NULL() { RETVAL_NULL(); return;}
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
#define RETURN_STR(s) { RETVAL_STR(s); return; }
#define RETURN_INTERNED_STR(s) { RETVAL_INTERNED_STR(s); return; }
#define RETURN_NEW_STR(s) { RETVAL_NEW_STR(s); return; }
#define RETURN_STR_COPY(s) { RETVAL_STR_COPY(s); return; }
#define RETURN_STRING(s) { RETVAL_STRING(s); return; }
#define RETURN_STRINGL(s, l) { RETVAL_STRINGL(s, l); return; }
#define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
#define RETURN_RES(r) { RETVAL_RES(r); return; }
#define RETURN_ARR(r) { RETVAL_ARR(r); return; }
#define RETURN_OBJ(r) { RETVAL_OBJ(r); return; }
#define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE { RETVAL_FALSE; return; }
#define RETURN_TRUE { RETVAL_TRUE; return; }
複製代碼

寫個小例子

寫一個兩個整型變量相加的函數

/* $Salamander$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_my_test.h"



static int le_my_test;



PHP_FUNCTION(my_add)
{
	int argc = ZEND_NUM_ARGS();
	zend_long a;
	zend_long b;

	if (zend_parse_parameters(argc, "ll", &a, &b) == FAILURE) 
		RETURN_FALSE;
	RETURN_LONG(a + b);
}



PHP_MINIT_FUNCTION(my_test)
{
	return SUCCESS;
}


PHP_MSHUTDOWN_FUNCTION(my_test)
{
	return SUCCESS;
}



PHP_RINIT_FUNCTION(my_test)
{
#if defined(COMPILE_DL_MY_TEST) && defined(ZTS)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif
	return SUCCESS;
}



PHP_RSHUTDOWN_FUNCTION(my_test)
{
	return SUCCESS;
}


PHP_MINFO_FUNCTION(my_test)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "my_test support", "enabled");
	php_info_print_table_end();

}


const zend_function_entry my_test_functions[] = {
	PHP_FE(my_add, NULL)
	PHP_FE_END
};


zend_module_entry my_test_module_entry = {
	STANDARD_MODULE_HEADER,
	"my_test",
	my_test_functions,
	PHP_MINIT(my_test),
	PHP_MSHUTDOWN(my_test),
	PHP_RINIT(my_test),	
	PHP_RSHUTDOWN(my_test),
	PHP_MINFO(my_test),
	PHP_MY_TEST_VERSION,
	STANDARD_MODULE_PROPERTIES
};


#ifdef COMPILE_DL_MY_TEST
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(my_test)
#endif
複製代碼

config.m4中取消如下注釋(刪除dnl便可)

dnl PHP_ARG_ENABLE(my_test, whether to enable my_test support,
dnl Make sure that the comment is aligned:
dnl [  --enable-my_test           Enable my_test support])
複製代碼

而後在my_test目錄下執行

phpize

./configure --with-php-config=/usr/local/php7.1/bin/php-config
複製代碼

php-config這個腳本是獲取PHP安裝信息的(PHP安裝路徑,PHP版本,PHP源碼的頭文件目錄,LDFLAGS,依賴的外部庫,PHP編譯參數),它在php的安裝路徑的bin目錄下,若是你不指定--with-php-config的話,將到默認的PHP的安裝路徑下搜索(安裝了多個PHP版本時最好指定一下,可能會編譯不經過) 而後

make && make install
複製代碼

獲得

Installing shared extensions:     /usr/local/php7.1/lib/php/extensions/no-debug-zts-20160303/
複製代碼

修改php.ini文件,加入.so

date.timezone = "Asia/Shanghai"
display_errors = On
error_reporting = E_ALL
short_open_tag=Off
upload_max_filesize = 50M
post_max_size = 50M
memory_limit=512M

extension=my_test.so
複製代碼

測試加載

php -m
複製代碼

clipboard.png

測試函數

php -r 'echo my_add(1, 3);'

複製代碼

clipboard.png
函數調用成功。
相關文章
相關標籤/搜索