探索 PHP 源码(一)——创建扩展

有时候还是得逼自己一把,自从写了下玩具编译器后,就对语言底层原理产生了浓厚的兴趣。但是,C语言本身呢,看起来似乎语言本身的内容不多,但真的太灵活了,一个宏就能让人晕头转向;还有各种贴近硬件的类型,未定义行为,指针,想写好真的不简单。PHP解释器是用C实现的,因此也容易让我产生畏难情绪。

其实学C语言的时间也不短了,却一直没用它写过像样的程序。最近又翻了下一本C语言书,有了些许收获,也许会让我能沉下心来钻进PHP解释器中。同时,PHP基金会的成立也让我对这门语言越来越有了接近之意,关注了很多社区中的大牛,也许受了感染吧,感觉就挺有意思。

最重要的就是想深入一门语言,探究其本质,对其它语言的使用也有帮助。语言不计其数,究其源头是相通的,不给自己限定为XX语言的开发者,对语言少些敬畏,减少其带来的束缚。

下面直接点吧,先给PHP加上自己想要的函数,就 https://github.com/phpinternalsbook/PHP-Internals-Book 书中简单的dump函数以及自己加的helloworld函数。

PHP的库函数基本都位于ext目录中,其中standard又是最小化安装时仅剩的几个扩展之一,因此我决定将这两个函数放在这里,该目录中又以basic_functions.c最得我心,就放这了,在ext/standard/basic_functions.c末尾加上以下代码:

// ext/standard/basic_functions.c
PHP_FUNCTION(dump)
{
    zval *zv_ptr;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv_ptr) == FAILURE) {
        return;
    }

try_again:
    switch (Z_TYPE_P(zv_ptr)) {
        case IS_NULL:
            php_printf("NULL: null\n");
            break;
        case IS_TRUE:
            php_printf("BOOL: true\n");
            break;
        case IS_FALSE:
            php_printf("BOOL: false\n");
            break;
        case IS_LONG:
            php_printf("LONG: %ld\n", Z_LVAL_P(zv_ptr));
            break;
        case IS_DOUBLE:
            php_printf("DOUBLE: %g\n", Z_DVAL_P(zv_ptr));
            break;
        case IS_STRING:
            php_printf("STRING: value=\"");
            PHPWRITE(Z_STRVAL_P(zv_ptr), Z_STRLEN_P(zv_ptr));
            php_printf("\", length=%zd\n", Z_STRLEN_P(zv_ptr));
            break;
        case IS_RESOURCE:
            php_printf("RESOURCE: id=%d\n", Z_RES_HANDLE_P(zv_ptr));
            break;
        case IS_ARRAY:
            php_printf("ARRAY: hashtable=%p\n", Z_ARRVAL_P(zv_ptr));
            break;
        case IS_OBJECT:
            php_printf("OBJECT: object=%p\n", Z_OBJ_P(zv_ptr));
            break;
        case IS_REFERENCE:
            // For references, remove the reference wrapper and try again.
            // Yes, you are allowed to use goto for this purpose!
            php_printf("REFERENCE: ");
            zv_ptr = Z_REFVAL_P(zv_ptr);
            goto try_again;
        EMPTY_SWITCH_DEFAULT_CASE() // Assert that all types are handled.
    }
}

PHP_FUNCTION(helloworld)
{
    printf("Hello world\n");
}

可以发现,想要给PHP添加新函数,只需要用PHP_FUNCTION宏包装一下即可。helloworld最为简单,深得我心。目前PHP 8.2.6除了定义函数外,还需要在ext/standard/basic_functions.stub.php中加上函数原型,姑且叫它函数原型吧,就看它像C语言的函数原型。至于PHP 8.2.6之前的版本先不管,再次在文件末尾加上以下代码:

// ext/standard/basic_functions.stub.php
function dump(mixed $value): void {}
function helloworld(): void {}

至此,我们的代码任务告一段落。假设之前已经手工配置编译安装了PHP,现在只需要重新编译安装即可:

$ make && make install

此时,可以试验自己添加的功能了:

$ php -r 'dump("Hello world");helloworld();'
STRING: value="Hello world", length=11
Hello world

有点意思,顿时来了兴趣,希望以后能更深入地了解PHP底层原理。

Search

    Table of Contents