PHP扩展开发:C语言编写PHP模块

好的,各位观众老爷们,欢迎来到“PHP扩展开发:C语言编写PHP模块”专场!我是你们的老朋友,人称“代码界的段子手”的程序员老王。今天咱们不聊鸡汤,不谈人生,就撸起袖子,好好扒一扒这PHP扩展开发的那些事儿。

开场白:PHP与C的爱恨情仇

话说这PHP啊,就像一位身怀绝技的武林高手,轻量灵活,开发效率高,深受广大码农的喜爱。但是,当遇到一些性能瓶颈,或者需要调用底层系统资源的时候,这PHP就有点力不从心了。这时候,就需要我们的C语言老大哥出马了。

C语言呢,就像一位内功深厚的隐士高手,性能强大,可以直接操作硬件,但是开发效率嘛,emmm… 只能说“稳重”!

所以,PHP和C语言,就像一对欢喜冤家,一个负责貌美如花,一个负责赚钱养家。PHP扩展开发,就是让这对CP强强联合,优势互补,共同打造更强大的Web应用。

第一章:磨刀不误砍柴工 – 准备工作

正所谓“工欲善其事,必先利其器”,要想编写PHP扩展,咱们得先准备好工具:

  1. PHP开发环境: 这还用说?没有PHP,哪来的PHP扩展?确保你的PHP版本是5.3以上,最好是7.0+,因为新版本性能更好,特性更多。
  2. C语言编译器: GCC是首选,当然,如果你是Windows用户,也可以选择Visual Studio。
  3. PHP开发包: 这个很重要!里面包含了PHP扩展开发的头文件和库文件。安装方式如下:
    • Debian/Ubuntu: sudo apt-get install php-dev
    • CentOS/RHEL: sudo yum install php-devel
    • macOS: 一般通过Homebrew安装PHP时,会包含开发包。如果没有,可以尝试 brew install php
  4. 文本编辑器/IDE: 选择你喜欢的就好,Vim、Emacs、Sublime Text、VS Code、PHPStorm都行,萝卜青菜,各有所爱。

第二章:Hello World – PHP扩展的初体验

万事开头难,咱们先从一个最简单的“Hello World”扩展开始,感受一下PHP扩展开发的流程。

  1. 生成扩展骨架:

    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的目录,里面包含了扩展的基本文件。

  2. 目录结构分析:

    生成的目录结构大概是这样的:

    my_extension/
    ├── config.m4      // 扩展配置,类似于Makefile
    ├── my_extension.c // 扩展的主要代码文件
    ├── php_my_extension.h // 扩展的头文件
    └── ...
    • config.m4:这个文件是扩展的灵魂,用于配置编译选项,比如依赖的库、包含的头文件等等。
    • my_extension.c:这里是扩展的核心代码,我们将在里面编写PHP函数。
    • php_my_extension.h:这个头文件定义了扩展的版本信息、函数声明等等。
  3. 修改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[] */
    };
  4. 修改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)
    ])
  5. 编译安装扩展:

    在扩展目录下执行以下命令:

    phpize
    ./configure
    make
    sudo make install
    • phpize:用于准备PHP扩展的编译环境。
    • ./configure:用于配置编译选项,比如安装目录、依赖的库等等。
    • make:用于编译扩展。
    • sudo make install:用于安装扩展到PHP的扩展目录。
  6. 启用扩展:

    编辑php.ini文件,找到extension=开头的行,添加一行:

    extension=my_extension.so

    重启Web服务器(如Apache或Nginx)。

  7. 验证扩展:

    创建一个PHP文件,比如test.php,写入以下代码:

    <?php
    echo my_hello();
    ?>

    在浏览器中访问test.php,如果看到“Hello World from my_extension!”,就说明扩展安装成功了!🎉

第三章:深入虎穴 – PHP扩展的API

PHP提供了一系列的API,用于在C语言中操作PHP的数据类型、变量、函数等等。这些API是编写PHP扩展的基础。

  1. 数据类型:

    PHP的数据类型在C语言中都有对应的表示:

    PHP数据类型 C语言表示
    NULL NULL
    bool zend_bool
    int zend_long
    float double
    string zend_string *
    array HashTable *
    object zend_object *
    resource zend_resource *
    callable zend_fcall_info / zend_fcall_info_cache

    这些数据类型都定义在zend.h头文件中。

  2. 变量操作:

    • 创建变量: 可以使用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数组。
  3. 函数操作:

    • 注册函数: 通过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函数。
  4. 内存管理:

    PHP使用引用计数机制来管理内存。在C语言中,需要使用ALLOC_ZVAL(), ZVAL_PTR_DTOR()等宏来分配和释放内存,避免内存泄漏。

第四章:实战演练 – 一个简单的字符串处理扩展

为了更好地理解PHP扩展的开发,咱们来做一个简单的字符串处理扩展,包含以下功能:

  1. 字符串反转: 将输入的字符串反转。

  2. 字符串转大写: 将输入的字符串转换为大写。

  3. 字符串转小写: 将输入的字符串转换为小写。

  4. 创建扩展骨架:

    cd /path/to/php/source/ext
    ./ext_skel --extname=string_utils
  5. 修改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);
    }
  6. 修改config.m4

    打开config.m4,找到PHP_ARG_WITH(string_utils,for string_utils support,这行,把前面的注释符号dnl去掉。

  7. 编译安装扩展:

    phpize
    ./configure
    make
    sudo make install
  8. 启用扩展:

    编辑php.ini文件,找到extension=开头的行,添加一行:

    extension=string_utils.so

    重启Web服务器。

  9. 验证扩展:

    创建一个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永不相见!🙏

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注