好的,各位观众老爷们,欢迎来到“PHP扩展开发:C语言编写PHP模块”专场!我是你们的老朋友,人称“代码界的段子手”的程序员老王。今天咱们不聊鸡汤,不谈人生,就撸起袖子,好好扒一扒这PHP扩展开发的那些事儿。
开场白:PHP与C的爱恨情仇
话说这PHP啊,就像一位身怀绝技的武林高手,轻量灵活,开发效率高,深受广大码农的喜爱。但是,当遇到一些性能瓶颈,或者需要调用底层系统资源的时候,这PHP就有点力不从心了。这时候,就需要我们的C语言老大哥出马了。
C语言呢,就像一位内功深厚的隐士高手,性能强大,可以直接操作硬件,但是开发效率嘛,emmm… 只能说“稳重”!
所以,PHP和C语言,就像一对欢喜冤家,一个负责貌美如花,一个负责赚钱养家。PHP扩展开发,就是让这对CP强强联合,优势互补,共同打造更强大的Web应用。
第一章:磨刀不误砍柴工 – 准备工作
正所谓“工欲善其事,必先利其器”,要想编写PHP扩展,咱们得先准备好工具:
- PHP开发环境: 这还用说?没有PHP,哪来的PHP扩展?确保你的PHP版本是5.3以上,最好是7.0+,因为新版本性能更好,特性更多。
- C语言编译器: GCC是首选,当然,如果你是Windows用户,也可以选择Visual Studio。
- PHP开发包: 这个很重要!里面包含了PHP扩展开发的头文件和库文件。安装方式如下:
- Debian/Ubuntu:
sudo apt-get install php-dev - CentOS/RHEL:
sudo yum install php-devel - macOS: 一般通过Homebrew安装PHP时,会包含开发包。如果没有,可以尝试
brew install php
- Debian/Ubuntu:
- 文本编辑器/IDE: 选择你喜欢的就好,Vim、Emacs、Sublime Text、VS Code、PHPStorm都行,萝卜青菜,各有所爱。
第二章:Hello World – PHP扩展的初体验
万事开头难,咱们先从一个最简单的“Hello World”扩展开始,感受一下PHP扩展开发的流程。
-
生成扩展骨架:
PHP提供了一个叫做
ext_skel的工具,可以自动生成扩展的基本框架。在终端中执行以下命令:cd /path/to/php/source/ext ./ext_skel --extname=my_extension/path/to/php/source/ext替换为你PHP源码的ext目录。my_extension是你扩展的名字,可以自定义。
执行成功后,会生成一个名为
my_extension的目录,里面包含了扩展的基本文件。 -
目录结构分析:
生成的目录结构大概是这样的:
my_extension/ ├── config.m4 // 扩展配置,类似于Makefile ├── my_extension.c // 扩展的主要代码文件 ├── php_my_extension.h // 扩展的头文件 └── ...config.m4:这个文件是扩展的灵魂,用于配置编译选项,比如依赖的库、包含的头文件等等。my_extension.c:这里是扩展的核心代码,我们将在里面编写PHP函数。php_my_extension.h:这个头文件定义了扩展的版本信息、函数声明等等。
-
修改
my_extension.c:打开
my_extension.c,找到PHP_FUNCTION(confirm_my_extension_compiled)这个函数,把里面的代码改成:PHP_FUNCTION(confirm_my_extension_compiled) { char *arg = NULL; size_t arg_len, len; zend_string *strg; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(arg, arg_len) ZEND_PARSE_PARAMETERS_END(); strg = strpprintf(0, "Congratulations! You have successfully installed %s v%s!", arg, PHP_MY_EXTENSION_VERSION); RETURN_STR(strg); }这个函数接受一个字符串参数,然后返回一个包含扩展名称和版本信息的字符串。
再找到
PHP_FE(confirm_my_extension_compiled, NULL) /* For testing, remove later. */这行,把它改成:PHP_FE(my_hello, NULL)并添加一个新的函数:
PHP_FUNCTION(my_hello) { RETURN_STRING("Hello World from my_extension!"); }修改后的
PHP_FUNCTION_ENTRY数组如下:static const zend_function_entry my_extension_functions[] = { PHP_FE(my_hello, NULL) /* For testing, remove later. */ PHP_FE_END /* Must be the last line in my_extension_functions[] */ }; -
修改
config.m4:打开
config.m4,找到PHP_ARG_WITH(my_extension,for my_extension support,这行,把前面的注释符号dnl去掉。PHP_ARG_WITH(my_extension,for my_extension support, [ --with-my_extension[=DIR] Include my_extension support], [ PHP_ADD_INCLUDE($PHP_MY_EXTENSION_DIR/include) PHP_ADD_LIBPATH($PHP_MY_EXTENSION_DIR/lib) PHP_ADD_LIBRARY(my_extension) PHP_NEW_EXTENSION(my_extension, my_extension.c, $ext_shared) ]) -
编译安装扩展:
在扩展目录下执行以下命令:
phpize ./configure make sudo make installphpize:用于准备PHP扩展的编译环境。./configure:用于配置编译选项,比如安装目录、依赖的库等等。make:用于编译扩展。sudo make install:用于安装扩展到PHP的扩展目录。
-
启用扩展:
编辑
php.ini文件,找到extension=开头的行,添加一行:extension=my_extension.so重启Web服务器(如Apache或Nginx)。
-
验证扩展:
创建一个PHP文件,比如
test.php,写入以下代码:<?php echo my_hello(); ?>在浏览器中访问
test.php,如果看到“Hello World from my_extension!”,就说明扩展安装成功了!🎉
第三章:深入虎穴 – PHP扩展的API
PHP提供了一系列的API,用于在C语言中操作PHP的数据类型、变量、函数等等。这些API是编写PHP扩展的基础。
-
数据类型:
PHP的数据类型在C语言中都有对应的表示:
PHP数据类型 C语言表示 NULL NULLbool zend_boolint zend_longfloat doublestring zend_string *array HashTable *object zend_object *resource zend_resource *callable zend_fcall_info/zend_fcall_info_cache这些数据类型都定义在
zend.h头文件中。 -
变量操作:
- 创建变量: 可以使用
zval结构体来表示PHP变量。 - 赋值: 可以使用
ZVAL_NULL(),ZVAL_BOOL(),ZVAL_LONG(),ZVAL_DOUBLE(),ZVAL_STRING(),ZVAL_ARR(),ZVAL_OBJ()等宏来给zval赋值。 - 读取变量: 可以使用
Z_TYPE(),Z_LVAL(),Z_DVAL(),Z_STRVAL()等宏来读取zval的值。 - 操作数组: 可以使用
zend_hash_*系列的函数来操作PHP数组。
- 创建变量: 可以使用
-
函数操作:
- 注册函数: 通过
PHP_FUNCTION宏定义函数,并通过PHP_FE宏将函数注册到PHP的函数表中。 - 参数解析: 可以使用
ZEND_PARSE_PARAMETERS_START()和ZEND_PARSE_PARAMETERS_END()宏来解析PHP函数的参数。 - 返回值: 可以使用
RETURN_NULL(),RETURN_BOOL(),RETURN_LONG(),RETURN_DOUBLE(),RETURN_STRING(),RETURN_ARR(),RETURN_OBJ()等宏来返回PHP函数的结果。 - 调用PHP函数: 可以使用
call_user_function()函数来调用PHP函数。
- 注册函数: 通过
-
内存管理:
PHP使用引用计数机制来管理内存。在C语言中,需要使用
ALLOC_ZVAL(),ZVAL_PTR_DTOR()等宏来分配和释放内存,避免内存泄漏。
第四章:实战演练 – 一个简单的字符串处理扩展
为了更好地理解PHP扩展的开发,咱们来做一个简单的字符串处理扩展,包含以下功能:
-
字符串反转: 将输入的字符串反转。
-
字符串转大写: 将输入的字符串转换为大写。
-
字符串转小写: 将输入的字符串转换为小写。
-
创建扩展骨架:
cd /path/to/php/source/ext ./ext_skel --extname=string_utils -
修改
string_utils.c:#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "php_string_utils.h" /* If you declare any globals in php_string_utils.h uncomment this: ZEND_DECLARE_MODULE_GLOBALS(string_utils) */ /* True global resources - no need for thread safety here */ static int le_string_utils; /* {{{ PHP_INI */ /* Remove comments and fill if you need to have entries in php.ini PHP_INI_BEGIN() STD_PHP_INI_ENTRY("string_utils.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_string_utils_globals, string_utils_globals) STD_PHP_INI_ENTRY("string_utils.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_string_utils_globals, string_utils_globals) PHP_INI_END() */ /* }}} */ /* {{{ php_string_utils_init_globals */ /* Uncomment this function if you have INI entries static void php_string_utils_init_globals(zend_string_utils_globals *string_utils_globals) { string_utils_globals->global_value = 0; string_utils_globals->global_string = NULL; } */ /* }}} */ /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(string_utils) { /* If you have INI entries, uncomment these lines REGISTER_INI_ENTRIES(); */ return SUCCESS; } /* }}} */ /* {{{ PHP_MSHUTDOWN_FUNCTION */ PHP_MSHUTDOWN_FUNCTION(string_utils) { /* uncomment this line if you have INI entries UNREGISTER_INI_ENTRIES(); */ return SUCCESS; } /* }}} */ /* {{{ PHP_RINIT_FUNCTION */ PHP_RINIT_FUNCTION(string_utils) { #if defined(COMPILE_DL_STRING_UTILS) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif return SUCCESS; } /* }}} */ /* {{{ PHP_RSHUTDOWN_FUNCTION */ PHP_RSHUTDOWN_FUNCTION(string_utils) { return SUCCESS; } /* }}} */ /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(string_utils) { php_info_print_table_start(); php_info_print_table_header(2, "string_utils support", "enabled"); php_info_print_table_end(); /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */ } /* }}} */ /* {{{ string_utils_functions[] * * Every user visible function must have an entry in string_utils_functions[]. */ const zend_function_entry string_utils_functions[] = { PHP_FE(string_reverse, NULL) PHP_FE(string_to_upper, NULL) PHP_FE(string_to_lower, NULL) PHP_FE_END /* Must be the last line in string_utils_functions[] */ }; /* }}} */ /* {{{ string_utils_module_entry */ zend_module_entry string_utils_module_entry = { STANDARD_MODULE_HEADER, "string_utils", string_utils_functions, PHP_MINIT(string_utils), PHP_MSHUTDOWN(string_utils), PHP_RINIT(string_utils), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(string_utils), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(string_utils), PHP_STRING_UTILS_VERSION, STANDARD_MODULE_PROPERTIES }; /* }}} */ #ifdef COMPILE_DL_STRING_UTILS #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE() #endif ZEND_GET_MODULE(string_utils) #endif /* * string_reverse function */ PHP_FUNCTION(string_reverse) { char *str = NULL; size_t str_len; zend_string *result; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(str, str_len) ZEND_PARSE_PARAMETERS_END(); result = zend_string_init(str, str_len, 0); zend_string_tolower(result); // Convert to lowercase for consistent reversal php_strrev(ZSTR_VAL(result), str_len); RETURN_STR(result); } /* * string_to_upper function */ PHP_FUNCTION(string_to_upper) { char *str = NULL; size_t str_len; zend_string *result; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(str, str_len) ZEND_PARSE_PARAMETERS_END(); result = zend_string_init(str, str_len, 0); zend_string_toupper(result); RETURN_STR(result); } /* * string_to_lower function */ PHP_FUNCTION(string_to_lower) { char *str = NULL; size_t str_len; zend_string *result; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(str, str_len) ZEND_PARSE_PARAMETERS_END(); result = zend_string_init(str, str_len, 0); zend_string_tolower(result); RETURN_STR(result); } -
修改
config.m4:打开
config.m4,找到PHP_ARG_WITH(string_utils,for string_utils support,这行,把前面的注释符号dnl去掉。 -
编译安装扩展:
phpize ./configure make sudo make install -
启用扩展:
编辑
php.ini文件,找到extension=开头的行,添加一行:extension=string_utils.so重启Web服务器。
-
验证扩展:
创建一个PHP文件,比如
test.php,写入以下代码:<?php echo string_reverse("Hello World!"); // !dlroW olleH echo string_to_upper("Hello World!"); // HELLO WORLD! echo string_to_lower("Hello World!"); // hello world! ?>在浏览器中访问
test.php,如果看到正确的结果,就说明扩展安装成功了!
第五章:进阶之路 – 更多可能性
PHP扩展开发的可能性是无限的,你可以:
- 访问底层系统资源: 比如文件系统、网络接口、操作系统API等等。
- 实现高性能算法: 将一些耗时的计算任务用C语言实现,提高性能。
- 集成第三方库: 将一些现有的C语言库集成到PHP中,扩展PHP的功能。
- 创建自定义数据类型: 定义自己的数据类型,并在PHP中使用。
- 编写SAPI模块: SAPI(Server Application Programming Interface)是PHP与Web服务器之间的接口,你可以编写自己的SAPI模块,实现不同的Web服务器支持。
结尾:路漫漫其修远兮
PHP扩展开发是一门需要不断学习和实践的技术。希望这篇文章能帮助你入门PHP扩展开发,开启你的C语言和PHP的完美结合之旅。记住,编程的乐趣在于探索和创造,撸起袖子,加油干吧!💪
最后的彩蛋:
- 调试技巧: 可以使用GDB来调试PHP扩展,设置断点、查看变量等等。
- 代码规范: 遵循PSR规范,编写清晰易懂的代码。
- 社区资源: PHP官方文档、Stack Overflow、GitHub等等,都是你学习和解决问题的宝贵资源。
祝各位码农们编码愉快,Bug永不相见!🙏