cd /Users/didi/PhpstormProjects/c/php-src
$git checkout -b xiazemin/function_record
写完后运行测试文件的脚本
$ls run-tests.php
run-tests.php
查看写扩展的帮助 README.EXT_SKEL
如果仅仅需要深沉框架,自己写函数实现
cd ext
./ext_skel –extname=module_name
$ ./ext_skel –extname=emtyExt
Creating directory emtyExt
Creating basic files: config.m4 config.w32 .gitignore emtyExt.c php_emtyExt.h CREDITS EXPERIMENTAL tests/001.phpt emtyExt.php [done].
To use your new extension, you will have to execute the following steps:
$ ./configure –[with | enable]-emtyExt |
Repeat steps 3-6 until you are satisfied with ext/emtyExt/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.
cd emtyExt
$ls
CREDITS config.m4 emtyExt.c php_emtyExt.h
EXPERIMENTAL config.w32 emtyExt.php tests
$cd ../..
$./buildconf
rebuilding aclocal.m4
rebuilding configure
rebuilding main/php_config.h.in
$cd ext/emptyExt
$phpize
Configuring for:
PHP Api Version: 20151012
Zend Module Api No: 20160303
Zend Extension Api No: 320160303
$./configure –with-php-config=/usr/local/php/man/man1/php-config.1 -with-php-config=/usr/local/bin/php-config
$make
Build complete.
Don’t forget to run ‘make test’.
$make test
Build complete.
Don’t forget to run ‘make test’.
=====================================================================
PHP : /usr/local/bin/php
PHP_SAPI : cli
PHP_VERSION : 7.2.0-dev
ZEND_VERSION: 3.1.0-dev
PHP_OS : Darwin - Darwin localhost 15.0.0 Darwin Kernel Version 15.0.0: Sat Sep 19 15:53:46 PDT 2015; root:xnu-3247.10.11~1/RELEASE_X86_64 x86_64
INI actual : /Users/didi/PhpstormProjects/c/php-src/ext/emtyExt/tmp-php.ini
More .INIs :
CWD : /Users/didi/PhpstormProjects/c/php-src/ext/emtyExt
$make install
Installing shared extensions: /usr/local/lib/php/extensions/debug-non-zts-20160303/
cp: modules/: No such file or directory
make: ** [install-modules] Error 1
$ls modules/
空的
README.EXT_SKEL
Just remove 3 comments in your_module_name/config.m4,
config.m4
dnl Comments in this file start with the string ‘dnl’.
dnl Remove where necessary. This file will not work
dnl without editing.
##指定PHP模块的工作方式,动态编译选项,如果想通过.so的方式接入扩展,请去掉前面的dnl注释
PHP_ARG_WITH(helloworld, for helloworld support,
Make sure that the comment is aligned:
[ –with-helloworld Include helloworld support])
dnl Otherwise use enable:
##指定PHP模块的工作方式,静态编译选项,如果想通过enable的方式来启用,去掉dnl注释
PHP_ARG_ENABLE(helloworld, whether to enable helloworld support,
Make sure that the comment is aligned:
[ –enable-helloworld Enable helloworld support])
修改成这样
PHP_ARG_WITH(emtyExt, for emtyExt support,
Make sure that the comment is aligned:
[ –with-emtyExt Include emtyExt support])
重复上述流程
$make && make install
Build complete.
Don’t forget to run ‘make test’.
Installing shared extensions: /usr/local/lib/php/extensions/debug-non-zts-20160303/
$ls /usr/local/lib/php/extensions/debug-non-zts-20160303/emtyExt.so
/usr/local/lib/php/extensions/debug-non-zts-20160303/emtyExt.so
vi /usr/local/lib/php.ini
extension=/usr/local/lib/php/extensions/debug-non-zts-20160303/emtyExt.so
$php -m |grep emtyExt
emtyExt
如果你清楚知道函数的输入和返回值,你需要定义一个声名文件,生成的时候指定这个文件
–proto=filename.
./ext_skel 生成 ‘module_name.c’ and ‘php_module_name.h’ as main source and header files.
例1
module_name_function
例2
module_name_function(int arg1, int arg2 [, int arg3 [, int arg4]])
例3
bool module_name_drawtext(resource image, string text, resource font, int x, int y [, int color])
cd /Users/didi/PhpstormProjects/c/php-src/ext
$vi myExtIdl/getext.skel
string getext(string str)
$./ext_skel –extname=myText –proto=myExtIdl/getext.skel
Creating directory myText
awk: syntax error at source line 256 source file /Users/didi/PhpstormProjects/c/php-src/ext/skeleton/create_stubs
context is
if (!stubs) print “” > extname »> “/function_warning” «<
awk: illegal statement at source line 257 source file /Users/didi/PhpstormProjects/c/php-src/ext/skeleton/create_stubs
awk: syntax error at source line 267 source file /Users/didi/PhpstormProjects/c/php-src/ext/skeleton/create_stubs
Creating basic files: config.m4 config.w32 .gitignore myText.c php_myText.h CREDITS EXPERIMENTAL tests/001.phpt myText.phprm: function_entries: No such file or directory
rm: function_declarations: No such file or directory
rm: function_stubs: No such file or directory
[done].
To use your new extension, you will have to execute the following steps:
$ ./configure –[with | enable]-myText |
Repeat steps 3-6 until you are satisfied with ext/myText/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.
$ls myText/
CREDITS config.m4 myText.c php_myText.h
EXPERIMENTAL config.w32 myText.php tests
$vi myText/config.m4
PHP_ARG_WITH(myText, for myText support,
Make sure that the comment is aligned:
[ –with-myText Include myText support])
$cd myText/
修改函数体
PHP_FUNCTION(confirm_myText_compiled)
vi myText.c
$phpize
Configuring for:
PHP Api Version: 20151012
Zend Module Api No: 20160303
Zend Extension Api No: 320160303
$./configure –with-php-config=/usr/local/php/man/man1/php-config.1 -with-php-config=/usr/local/bin/php-config
$make & make install
Don’t forget to run ‘make test’.
Installing shared extensions: /usr/local/lib/php/extensions/debug-non-zts-20160303/
[1]+ Done make
$ls /usr/local/lib/php/extensions/debug-non-zts-20160303/myText.so
/usr/local/lib/php/extensions/debug-non-zts-20160303/myText.so
$vi /usr/local/lib/php.ini
extension=/usr/local/lib/php/extensions/debug-non-zts-20160303/myText.so
$php -m |grep myText
myText
$php -r ‘var_dump(getext(“1234”));’
PHP Fatal error: Uncaught Error: Call to undefined function getext() in Command line code:1
$vi myText.c
找到
const zend_function_entry myText_functions[] = {
PHP_FE(confirm_myText_compiled, NULL) /* For testing, remove later. /
PHP_FE_END / Must be the last line in myText_functions[] */
};
修改成
const zend_function_entry myText_functions[] = {
PHP_FE(confirm_myText_compiled, NULL) /* For testing, remove later. /
PHP_FE(getext,NULL)
PHP_FE_END / Must be the last line in myText_functions[] */
};
找到PHP_FUNCTION(confirm_say_compiled),在其上面增加如下代码:
PHP_FUNCTION(getext)
{
zend_string *strg;
strg = strpprintf(0, “xiazemin hello word”);
RETURN_STR(strg);
}
make & make install
$make install
Installing shared extensions: /usr/local/lib/php/extensions/debug-non-zts-20160303/
$php -r ‘var_dump(getext(“1234”));’
string(19) “xiazemin hello word”
成功了
https://www.jb51.net/article/132860.htm
https://www.jb51.net/article/170578.htm
https://blog.csdn.net/u011957758/article/details/72456298
https://blog.csdn.net/u011730334/article/details/80688870
https://blog.csdn.net/21aspnet/article/details/7345650
https://blog.csdn.net/p_Tsui/article/details/51918298
https://blog.csdn.net/u011957758/article/details/72456298
执行./ext_skel –extname=myText –proto=myExtIdl/getext.skel
报错 :
awk: syntax error at source line 256 source file /Users/didi/PhpstormProjects/c/php-src/ext/skeleton/create_stubs
context is
if (!stubs) print “” > extname »> “/function_warning” «<
awk: illegal statement at source line 257 source file /Users/didi/PhpstormProjects/c/php-src/ext/skeleton/create_stubs
awk: syntax error at source line 267 source file /Users/didi/PhpstormProjects/c/php-src/ext/skeleton/create_stubs
文件夹是能生成的 但是生产的文件里并没有C扩展定义的函数 所以这边的报错应该还是造成了影响
https://segmentfault.com/q/1010000004493105/a-1020000004493268
修改一下 ext/skeleton/create_stubs 文件中三处:
256行
把
if (!stubs) print “” > extname “/function_warning”
改为
if (!stubs) print “” > ( extname “/function_warning” )
还有267、268行,相同。
https://stackoverflow.com/questions/26798063/extension-php-with-c-creating-the-first-environment-i-got-awk-syntax-error-usin
问题解决了
$./ext_skel –extname=myTextAwk –proto=myExtIdl/getext.skel
Creating directory myTextAwk
Creating basic files: config.m4 config.w32 .gitignore myTextAwk.c php_myTextAwk.h CREDITS EXPERIMENTAL tests/001.phpt myTextAwk.php [done].
To use your new extension, you will have to execute the following steps:
$ ./configure –[with | enable]-myTextAwk |
Repeat steps 3-6 until you are satisfied with ext/myTextAwk/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.
vi myTextAwk.c发现已经生成了文件定义
/* {{{ proto string getext(string str)
*/
PHP_FUNCTION(getext)
{
char *str = NULL;
int argc = ZEND_NUM_ARGS();
size_t str_len;
if (zend_parse_parameters(argc, "s", &str, &str_len) == FAILURE)
return;
php_error(E_WARNING, "getext: not yet implemented"); } /* }}} */
https://wiki.jikexueyuan.com/project/extending-embedding-php/12.4.html
https://blog.csdn.net/linkaisheng101990/article/details/45625103
https://blog.csdn.net/u011957758/article/details/72456298
https://www.php.net/manual/zh/internals2.buildsys.skeleton.php
https://www.laruence.com/2009/04/28/719.html
为了获得函数传递的参数,可以使用zend_parse_parameters()API函数。
第一个参数是传递给函数的参数个数。通常的做法是传给它ZEND_NUM_ARGS()。这是一个表示传递给函数参数总个数的宏。第二个参数是为了线程安全,总是传递TSRMLS_CC宏,后面会讲到。第三个参数是一个字符串,指定了函数期望的参数类型,后面紧跟着需要随参数值更新的变量列表。因为PHP采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。例如,如果用户传递一个整数变量,可函数需要一个浮点数,那么zend_parse_parameters()就会自动地把整数转换为相应的浮点数。如果实际值无法转换成期望类型(比如整形到数组形),会触发一个警告。
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没有strndup()?”是的,这是正确的,因为GNU扩展通常在Linux下可用。estrndup()只是PHP下的一个特殊函数。它的行为与estrdup()相似,但是可以指定字符串重复的次数(不需要结束空字符),同时是二进制安全的。这是推荐使用estrndup()而不是estrdup()的原因。
有一些情况,即扩展需要分配在请求中永久存在的内存,从而不得不使用malloc(),但是除非你知道你在做什么,你应该始终使用以上的函数。如果没有使用这些内存函数,而相反使用标准C函数分配的内存返回给脚本引擎,那么PHP会崩溃。
这些函数的优点是:任何分配的内存在偶然情况下如果没有被释放,则会在页面请求的最后被释放。因此,真正的内存泄漏不会产生。然而,不要依赖这一机制,从调试和性能两个原因来考虑,应当确保释放应该释放的内存。剩下的优点是在多线程环境下性能的提高,调试模式下检测内存错误等。
从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) 资源句柄。
./ext_skel –extname=myTextAwk –proto=myExtIdl/getext.skel
741 cd myTextAwk/
742 phpize
743 ./configure –with-php-config=/usr/local/php/man/man1/php-config.1 -with-php-config=/usr/local/bin/php-config
744 make & make install
Installing shared extensions: /usr/local/lib/php/extensions/debug-non-zts-20160303/
[1]+ Done
PHP Warning: Function registration failed - duplicate name - getext in Unknown on line 0
Warning: Function registration failed - duplicate name - getext in Unknown on line 0
PHP Warning: myTextAwk: Unable to register functions, unable to load in Unknown on line 0
Warning: myTextAwk: Unable to register functions, unable to load in Unknown on line 0
Segmentation fault: 11
名字冲突了,干掉前面那个扩展
k$php -r ‘var_dump(getext(“1234”,3));’
string(12) “123412341234”
[Wed May 27 20:28:17 2020] Script: ‘-‘
/Users/didi/PhpstormProjects/c/php-src/ext/myTextAwk/myTextAwk.c(96) : Freeing 0x00000001098620f0 (13 bytes), script=-
=== Total 1 memory leaks detected ===
包裹第三方的扩展
本节中你将学到如何编写更有用和更完善的扩展。该节的扩展包裹了一个C库,展示了如何编写一个含有多个互相依赖的PHP函数扩展。
也许最常见的PHP扩展是那些包裹第三方C库的扩展。这些扩展包括MySQL或Oracle的数据库服务库,libxml2的 XML技术库,ImageMagick 或GD的图形操纵库。
在本节中,我们编写一个扩展,同样使用脚本来生成骨架扩展,因为这能节省许多工作量。这个扩展包裹了标准C函数fopen(), fclose(), fread(), fwrite()和 feof().
扩展使用一个被叫做资源的抽象数据类型,用于代表已打开的文件FILE*。你会注意到大多数处理比如数据库连接、文件句柄等的PHP扩展使用了资源类型,这是因为引擎自己无法直接“理解”它们。
以下是PHP风格的API:
resource file_open(string filename, string mode)
file_open() //接收两个字符串(文件名和模式),返回一个文件的资源句柄。
bool file_close(resource filehandle)
file_close() //接收一个资源句柄,返回真/假指示是否操作成功。
我们的函数定义文件——保存为ext/目录下的myfile.def——内容如下:
resource file_open(string filename, string mode)
bool file_close(resource filehandle)
string file_read(resource filehandle, int size)
bool file_write(resource filehandle, string buffer)
bool file_eof(resource filehandle)
$./ext_skel –extname=myFile –proto=myExtIdl/myFile.skel
资源是一个能容纳任何信息的抽象数据结构。正如前面提到的,这个信息通常包括例如文件句柄、数据库连接结构和其他一些复杂类型的数据。
使用资源的主要原因是因为:资源被一个集中的队列所管理,该队列可以在PHP开发人员没有在脚本里面显式地释放时可以自动地被释放。
举个例子,考虑到编写一个脚本,在脚本里调用mysql_connect()打开一个MySQL连接,可是当该数据库连接资源不再使用时却没有调用mysql_close()。在PHP里,资源机制能够检测什么时候这个资源应当被释放,然后在当前请求的结尾或通常情况下更早地释放资源。这就为减少内存泄漏赋予了一个“防弹”机制。如果没有这样一个机制,经过几次web请求后,web服务器也许会潜在地泄漏许多内存资源,从而导致服务器当机或出错。
注册资源类型
如何使用资源?Zend引擎让使用资源变地非常容易。你要做的第一件事就是把资源注册到引擎中去。使用这个API函数:
int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)
这个函数返回一个资源类型id,该id应当被作为全局变量保存在扩展里,以便在必要的时候传递给其他资源API。ld:该资源释放时调用的函数。pld用于在不同请求中始终存在的永久资源,本章不会涉及。type_name是一个具有描述性类型名称的字符串,module_number为引擎内部使用,当我们调用这个函数时,我们只需要传递一个已经定义好的module_number变量
新建和注册新资源 我们准备实现file_open()函数。当我们打开文件得到一个FILE *,我们需要利用资源机制注册它。下面的主要宏实现注册功能:
ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type);
全局变量
你可能希望在扩展里使用全局C变量,无论是独自在内部使用或访问php.ini文件中的INI扩展注册标记(INI在下一节中讨论)。因为PHP是为多线程环境而设计,所以不必定义全局变量。PHP提供了一个创建全局变量的机制,可以同时应用在线程和非线程环境中。我们应当始终利用这个机制,而不要自主地定义全局变量。用一个宏访问这些全局变量,使用起来就像普通全局变量一样。
用于生成myfile工程骨架文件的ext_skel脚本创建了必要的代码来支持全局变量。通过检查php_myfile.h文件,你应当发现类似下面的被注释掉的一节,
ZEND_BEGIN_MODULE_GLOBALS(myfile)
int global_value;
char *global_string;
ZEND_END_MODULE_GLOBALS(myfile)
你可以把这一节的注释去掉,同时添加任何其他全局变量于这两个宏之间。文件后部的几行,骨架脚本自动地定义一个MYFILE_G(v)宏。这个宏应当被用于所有的代码,以便访问这些全局变量。这就确保在多线程环境中,访问的全局变量仅是一个线程的拷贝,而不需要互斥的操作。
为了使全局变量有效,最后需要做的是把myfile.c:
ZEND_DECLARE_MODULE_GLOBALS(myfile)
注释去掉。
你也许希望在每次PHP请求的开始初始化全局变量。另外,做为一个例子,全局变量已指向了一个已分配的内存,在每次PHP请求结束时需要释放内存。为了达到这些目的,全局变量机制提供了一个特殊的宏,用于注册全局变量的构造和析构函数(参考表对宏参数的说明):
ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)
添加自定义INI指令
INI文件(php.ini)的实现使得PHP扩展注册和监听各自的INI条目。如果这些INI条目由php.ini、Apache的htaccess或其他配置方法来赋值,注册的INI变量总是更新到正确的值。整个INI框架有许多不同的选项以实现其灵活性。我们涉及一些基本的(也是个好的开端),借助本章的其他材料,我们就能够应付日常开发工作的需要。
通过在PHP_INI_BEGIN()/PHP_INI_END()宏之间的STD_PHP_INI_ENTRY()宏注册PHP INI指令。例如在我们的例子里,myfile.c中的注册过程应当如下:
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY(“myfile.global_value”, “42”, PHP_INI_ALL, OnUpdateInt, global_value, zend_myfile_globals, myfile_globals)
STD_PHP_INI_ENTRY(“myfile.global_string”, “foobar”, PHP_INI_ALL, OnUpdateString, global_string, zend_myfile_globals, myfile_globals)
PHP_INI_END()
除了STD_PHP_INI_ENTRY()其他宏也能够使用,但这个宏是最常用的,可以满足大多数需要(参看表对宏参数的说明):
STD_PHP_INI_ENTRY(name, default_value, modifiable, on_modify, property_name, struct_type, struct_ptr)
线程安全资源管理宏
现在,你肯定注意到以TSRM(线程安全资源管理器)开头的宏随处使用。这些宏提供给扩展拥有独自的全局变量的可能,正如前面提到的。
当编写PHP扩展时,无论是在多进程或多线程环境中,都是依靠这一机制访问扩展自己的全局变量。如果使用全局变量访问宏(例如MYFILE_G()宏),需要确保TSRM上下文信息出现在当前函数中。基于性能的原因,Zend引擎试图把这个上下文信息作为参数传递到更多的地方,包括PHP_FUNCTION()的定义。正因为这样,在PHP_FUNCTION()内当编写的代码使用访问宏(例如MYFILE_G()宏)时,不需要做任何特殊的声明。然而,如果PHP函数调用其他需要访问全局变量的C函数,要么把上下文作为一个额外的参数传递给C函数,要么提取上下文(要慢点)。
在需要访问全局变量的代码块开头使用TSRMLS_FETCH()来提取上下文。例如:
void myfunc(){
TSRMLS_FETCH();
MYFILE_G(myglobal) = 2;
}
https://www.php.net/manual/en/internals2.ze1.zendapi.php