• 首页   /  職場   /  
  • 【相關學習推薦:php編程(視頻)】

    PHP函數的註冊和使用

    PHP擴展的主要目標是為用戶註冊新的PHP函數,PHP函數非常複雜,很難完全理解它們與Zend引擎密切相關的機制,但幸運的是, 我們在本章中不需要這些知識,因為PHP擴展機制提供了許多方法來抽象如此複雜的內容。

    在擴展中註冊並使用一個新的PHP函數是一個簡單的步驟. 然而,要深刻理解整體情況,則要複雜得多。zend_functiоn章節的第一步 可能會有所幫助.

    顯然,你需要掌握類型, 特別是 zendValues 和 內存管理. 當然, 了解你的鉤子.


    zend_functiоn_entry 結構

    不要和 zend_functiоn 結構混淆,zend_functiоn_entry 是用在擴展中針對引擎註冊函數的。看這裡:

    #define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value
    
    typedef struct _zend_functiоn_entry {
            const char *fname;
            void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
            const struct _zend_internal_arg_info *arg_info;
            uint32_t num_args;
            uint32_t flags;
    } zend_functiоn_entry;

    你會發現該結構並不複雜,這就是聲明和註冊新功能所需要的。讓我們一起詳細介紹:

    函數的名字:fname。沒什麼好補充的,你知道它的用途對吧?只需注意是 const char * 類型。這不適用於引擎。此 fname是一個模型,引擎將會從 內部的 zend_string 創建。

    然後來看 handler。這是指向 C 代碼的函數指針,它將會是函數的主體。這裡,我們將使用宏來簡化其聲明(等等會看到)。進入此函數,我們能夠解析函數接收的參數,並且生成一個返回值,就像任何 PHP 用戶區的函數。注意,這個返回值作為參數傳遞到我們的處理程序。

    爭論。arg_info 變量是關於聲明我們的函數將接收的 API 參數。同樣,這部分可能很難深入理解,但我們不需要理解太深,我們再次使用宏進行抽象和簡化參數聲明。你要知道的是,在這裡你不需要聲明任何參數即可使用該函數,但是我們強烈建議你這麼做。我們將回到這裡。參數是一組 arg_info,因此它的大小作為 num_args 傳遞。

    然後是 flags。在這章我們不詳細說明它。這些是內部使用的,你可在 zend_functiоn 章節了解詳細信息。

    註冊 PHP 函數

    當加載擴展時,PHP 函數會被註冊到 ZEND 引擎當中。一個擴展可以在擴展結構中聲明一個函數向量。被擴展聲明的函數被稱為 核心 函數,與 用戶 函數(在PHP用戶中被聲明和使用的函數)相反,它們在當前的請求結束時不會被註銷:可以一直使用。

    提醒一下,以下是為了方便可讀性對 PHP 擴展結構的簡寫

    struct _zend_module_entry {
            unsigned short size;
            unsigned int zend_api;
            unsigned char zend_debug;
            unsigned char zts;
            const struct _zend_ini_entry *ini_entry;
            const struct _zend_module_dep *deps;
            const char *name;
            const struct _zend_functiоn_entry *functiоns;     /* 函數聲明向量 */
            int (*module_startup_func)(INIT_FUNC_ARGS);
            int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
        /* ... */
    };

    您將向函數向量傳遞一個已聲明的函數向量。讓我們一起來看一個簡單的例子:

    /* pib.c 頭文件*/
    PHP_FUNCTION(fahrenheit_to_celsius)
    {
    
    }
    
    static const zend_functiоn_entry pib_functiоns[] =
    {
        PHP_FE(fahrenheit_to_celsius, NULL)
    };
    
    zend_module_entry pib_module_entry = {
        STANDARD_MODULE_HEADER,
        "pib",
        pib_functiоns,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "0.1",
        STANDARD_MODULE_PROPERTIES
    };

    我們來試試一個簡單的函數 fahrenheit_to_celsius() (名字告訴了我們它的作用)

    通過使用 PHP_FUNCTION() 宏來定義一個函數。後者將傳遞它的參數並擴展成正確的結構。然後,我們把函數符號匯總並將其添加到 pib_functiоns 向量中。這就是通過 zend_module_entry 符號延伸的 zend_functiоn_entry * 類型。在此向量中,我們通過 PHP_FE 宏添加我們的 PHP 函數。後者需要 PHP 函數名稱,以及我們傳遞 NULL 值時的一個參數向量。

    php_pib.h 頭文件中,我們應該像 C 語言一樣在這裡聲明我們的函數:

    /* pib.h 頭文件*/
    PHP_FUNCTION(fahrenheit_to_celsius);

    如你所見,聲明函數確實很容易。宏為我們幹完了所有難活。以下是和上文相同的代碼,但是卻擴展了宏,因此你可以看下它們是如何運行的:

    /* pib.c */
    void zif_fahrenheit_to_celsius(zend_execute_data *execute_data, zval *return_value)
    {
    
    }
    
    static const zend_functiоn_entry pib_functiоns[] =
    {
        { "fahrenheit_to_celsius", zif_fahrenheit_to_celsius, ((void *)0),
            (uint32_t) (sizeof(((void *)0))/sizeof(struct _zend_internal_arg_info)-1), 0 },
    }

    請注意 PHP_FUNCTION() 是如何以 zif_ 開頭擴展為 C 符號的。‘zif’ 被添加到你的函數名稱中,以防止PHP 及其模塊在編譯中造成符號名稱衝突。因此,我們的 fahrenheit_to_celsius() PHP 函數使用了 zif_fahrenheit_to_celsius() 的處理程序。它幾乎和每個 PHP 函數一樣。如果你搜索 zif_var_dump,就可以閱讀PHP var_dump() 的源碼函數等。

    聲明函數參數

    到目前為止,如果 「你編譯」 擴展並將其加載到PHP中,你可以看見函數呈現的反射機制:

    > ~/php/bin/php -dextension=pib.so --re pib
    Extension [ <persistent> extension #37 pib version 0.1 ] {
    
      - Functions {
        Function [ <internal:pib> functiоn fahrenheit_to_celsius ] {
        }
    }

    但是它缺少參數。如果我們發布一個 fahrenheit_to_celsius($fahrenheit) 函數簽名,則需要一個強制參數。

    你必須了解,函數聲明和函數內部的運行無關。這意味着即便沒有聲明參數,我們現在編寫函數也可能會起作用。

    注意

    聲明參數雖然不是強制性的,但是我們強烈推薦使用。反射 API 可通過使用參數獲取函數的信息。Zend 引擎也用到參數,尤其是當我們談及引用傳參或者返回引用的函數時。

    要聲明參數,我們必須要熟悉 zend_internal_arg_info 結構:

    typedef struct _zend_internal_arg_info {
            const char *name;
            const char *class_name;
            zend_uchar type_hint;
            zend_uchar pass_by_reference;
            zend_bool allow_null;
            zend_bool is_variadic;
    } zend_internal_arg_info;

    沒必要詳細說明每個字段,但是想要理解參數卻比這種單獨結構複雜得多。幸運的是,我們再次為你提供了一些宏來抽象這艱巨的工作。

    ZEND_BEGIN_ARG_INFO_EX(arginfo_fahrenheit_to_celsius, 0, 0, 1)
        ZEND_ARG_INFO(0, fahrenheit)
    ZEND_END_ARG_INFO()

    上面的代碼詳細的說明了如何創建參數,但當我們擴展宏時,我們會感到有些困難:

    static const zend_internal_arg_info arginfo_fahrenheit_to_celsius[] = {
                { (const char*)(zend_uintptr_t)(1), ((void *)0), 0, 0, 0, 0 },
                { "fahrenheit", ((void *)0), 0, 0, 0, 0 },
        };

    正如我們所見,宏創建了一個 zend_internal_arg_info 結構。如果你閱讀過這類宏的 API,那麼對我們來說一切都變得清楚了:

    /* API only */
    #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
    #define ZEND_ARG_INFO(pass_by_ref, name)
    #define ZEND_ARG_OВJ_INFO(pass_by_ref, name, classname, allow_null)
    #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
    #define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null)
    #define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null)
    #define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name)

    這一系列的宏可以讓你處理每個用例。
    This bunch of macros allow you to deal with every use-case.

    • ZEND_BEGIN_ARG_INFO_EX() 允許你聲明你的函數能接收多少個必要參數。它還允許你聲明一個 &return_by_ref() 函數。
    • 那麼你每個參數都需要 ZEND_ARG_***_INFO() 之一。使用它你可以判斷參數是否為 &$passed_by_ref 以及是否需要類型提示。

    注意

    如果你不知道怎樣去命名參數向量符號,則一種做法是使用 ‘arginfo_[functiоn name]’ 模式。

    所以回到我們的 fahrenheit_to_celsius() 函數,我們這裡申明一個簡單的按值返回函數(非常經典的用例),其中一個參數稱為 fahrenheit ,且未通過引用傳遞(又一次的傳統用例)。

    這就創建了類型 zend_internal_arg_info[] (一個向量, 或一個數組, 都相同) 的 arginfo_fahrenheit_to_celsius 符號,現在我們必須要使用該符號回到函數聲明中來添加給它一些參數。

    PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius)

    至此我們完成了,現在反射可以看見參數了,並會告知引擎在引用不匹配的情況下該怎麼做。太棒了!

    注意

    還有其他宏。ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX() f.e. 你可以在 Zend/zend_api.h 的源代碼中找到所有這些文件。

    C 語言的 PHP 函數結構和 API

    好的。下面是一個 PHP 函數。你可以使用它,並用 PHP 語言聲明它(用戶區):

    functiоn fahrenheit_to_celsius($fahrenheit)
    {
        return 5/9 * ($fahrenheit - 32);
    }

    這是一個簡單的函數,以便你可以理解它。這是用 C 編程時的樣子:

    PHP_FUNCTION(fahrenheit_to_celsius)
    {
        /* code to go here */
    }

    宏展開後,將得到:

    void zif_fahrenheit_to_celsius(zend_execute_data *execute_data, zval *return_value)
    {
        /* code to go here */
    }

    休息一下,考慮一下主要差異。

    首先奇怪的是,在 C 中,該函數不會返回任何東西。那是一個 void 聲明的函數,你不可以在這裡返回任何東西。但是我們注意到我們接收了一個 zval *類型的return_value參數,看起來很不錯。用 C 編寫 PHP 函數時,你將得到一個指向 zval 的返回值 ,希望你們能玩一玩。這有更多關於 zval 的資源.

    注意

    在 C 擴展中編寫 PHP 函數時,你接收作為參數的返回值,並且你不會從 C 函數返回任何東西。

    好的,第一點解釋了。第二點你可能已經猜到了:PHP 函數的參數在哪裡?$fahreinheit在哪裡?很難解釋完全,事實上,這很難。

    但是我們不需要在這裡了解細節。讓我們解釋下關鍵的概念:

    • 參數已經通過引擎推入堆棧中。它們都在內存的某個地方挨着堆放。
    • 如果你的函數被調用,這意味着沒有阻塞錯誤,因此你可以瀏覽參數堆棧,並讀取運行時傳遞的參數。不僅是你聲明的那些,還包括那些在調用函數時傳遞給函數的。引擎會為你處理一切。
    • 為了讀取參數,你需要一個函數或者宏,並且需要知道有多少參數已經推入堆棧中,以便知道什麼時候應該停止讀取它們。
    • 一切都按照你接收的作為參數的zend_execute_data *execute_data。但是現在我們不詳細說明。

    解析參數:zend_parse_parameters()

    要讀取參數,歡迎使用 zend_parse_parameters() API (稱為 ‘zpp’).

    注意

    當在 C 擴展中編寫 PHP 函數時,多虧了zend_parse_parameters() 函數和它的朋友,你接收到 PHP 函數的參數。

    zend_parse_parameters() 是一個函數,它將為你到 Zend 引擎的堆棧中讀取參數。你要告訴它要讀取多少個參數,以及想要它為你提供哪種類型。該函數將根據 PHP 類型轉換規則(如果需要,並且有可能的話)將參數轉換為你要的類型。如果你需要一個整型,但給了一個浮點型,如果沒有嚴格的類型提示規則被阻塞,則引擎會將浮點型轉換為整型,然後給你。

    讓我們來看看這個函數:

    PHP_FUNCTION(fahrenheit_to_celsius)
    {
        double f;
    
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &f) == FAILURE) {
            return;
        }
    
        /* continue */
    }

    我們希望在 f 變量上得到一個 double 類型。然後我們調用zend_parse_parameters()

    第一個參數是運行時已給定的參數數目。ZEND_NUM_ARGS() 是一個宏,它會告訴我們,然後我們用它去告知 zpp() 需要讀取多少個參數。

    然後我們傳遞一個const char *類型的 “d” 字符串。在這裡,要求你為每一個接收的參數寫一個字母,除了一些未在這裡講述的特殊情況。一個簡單的 “d” 表示 “如果需要的話,我想要第一個接收的參數轉換為 float (double)”

    然後,在該字符串之後傳遞 C 真正需要的參數,以滿足第二個參數。一個 “d” 表示 “一個 double”,然後你現在傳遞 double 的 地址,引擎將會填充其值。

    注意

    你總是將一個指針傳遞給要填充的數據。

    你可以在 PHP 源代碼的 README.PARAMETER_PARSING_API文件中找到關於 zpp() 的字符串格式的最新幫助。仔細閱讀,因為這是你可能搞錯並造成程序崩潰的一步。始終檢查你的參數,始終根據你提供的格式字符串傳遞相同數量的參數變量,以及你要求的類型相同。要合乎邏輯。

    同樣注意一下參數解析的正常過程。zend_parse_parameters()函數在成功時應返回 SUCCESS或者在失敗時應返回FAILURE。失敗可能表示你沒有使用ZEND_NUM_ARGS()值,而是手動提供一個值(壞主意)。或者在參數解析時做錯了什麼。如果是這樣,那麼是時候 return 了,終止當前函數(你應該從 C 函數中返回 void,所以只要 return)。

    到目前為止,我們接收了一個 double。讓我們執行數學運算並返回結果:

    static double php_fahrenheit_to_celsius(double f)
    {
        return ((double)5/9) * (double)(f - 32);
    }
    
    PHP_FUNCTION(fahrenheit_to_celsius)
    {
        double f;
    
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &f) == FAILURE) {
            return;
        }
    
        RETURN_DOUBLE(php_fahrenheit_to_celsius(f));
    }

    如你所知的zval 的工作原理,返回值對你來說應該很容易。你必須填寫 return_value

    一些 RETURN_***() 宏以及一些RETVAL_***()宏都是專門用來這麼做的。這兩個僅設置return_value zval 的類型和值,但是RETURN_***()宏後面會跟着一個從當前函數返回的 Creturn

    或者,API 提供了一系列去處理和解析參數的宏。如果你對 python 樣式說明符困惑的話,那麼它更具有可讀性。

    你需要使用以下宏來開始和結束函數參數解析:

    ZEND_PARSE_PARAMETERS_START(min_argument_count, max_argument_count) /* 需要兩個參數 */
    /* 這裡我們將使用參數列表 */
    ZEND_PARSE_PARAMETERS_END();

    可用的參數宏可以列出如下:

    Z_PARAM_ARRAY()                /* old "a" */
    Z_PARAM_ARRAY_OR_OВJECT()      /* old "A" */
    Z_PARAM_BOOL()                 /* old "b" */
    Z_PARAM_CLASS()                /* old "C" */
    Z_PARAM_DOUBLE()               /* old "d" */
    Z_PARAM_FUNC()                 /* old "f" */
    Z_PARAM_ARRAY_HT()             /* old "h" */
    Z_PARAM_ARRAY_OR_OВJECT_HT()   /* old "H" */
    Z_PARAM_LONG()                 /* old "l" */
    Z_PARAM_STRICT_LONG()          /* old "L" */
    Z_PARAM_OВJECT()               /* old "o" */
    Z_PARAM_OВJECT_OF_CLASS()      /* old "O" */
    Z_PARAM_PATH()                 /* old "p" */
    Z_PARAM_PATH_STR()             /* old "P" */
    Z_PARAM_RESOURCE()             /* old "r" */
    Z_PARAM_STRING()               /* old "s" */
    Z_PARAM_STR()                  /* old "S" */
    Z_PARAM_ZVAL()                 /* old "z" */
    Z_PARAM_VARIADIC()             /* old "+" and "*" */

    為了添加一個參數作為可選參數,我們使用以下宏:

    Z_PARAM_OPTIONAL              /* old "|" */

    這是基於宏的參數解析樣式的示例:

    PHP_FUNCTION(fahrenheit_to_celsius)
    {
        double f;
    
        ZEND_PARSE_PARAMETERS_START(1, 1)
            Z_PARAM_DOUBLE(f);
        ZEND_PARSE_PARAMETERS_END();
    
        RETURN_DOUBLE(php_fahrenheit_to_celsius(f));
    }

    添加測試

    如果你已閱讀有關測試的章節(看使用 .phpt 文件測試),現在你應該編寫一個簡單的例子:

    --TEST--
    Test fahrenheit_to_celsius
    --SKIPIF--
    <?php if (!extension_loaded("pib")) print "skip"; ?>
    --FILE--
    <?php
    printf("%.2f", fahrenheit_to_celsius(70));
    ?>
    --EXPECTF--
    21.11

    並啟動make test

    玩轉常量

    讓我們來看一個高級的例子。我們來添加相反的函數:celsius_to_fahrenheit($celsius):

    ZEND_BEGIN_ARG_INFO_EX(arginfo_celsius_to_fahrenheit, 0, 0, 1)
        ZEND_ARG_INFO(0, celsius)
    ZEND_END_ARG_INFO();
    
    static double php_celsius_to_fahrenheit(double c)
    {
        return (((double)9/5) * c) + 32 ;
    }
    
    PHP_FUNCTION(celsius_to_fahrenheit)
    {
        double c;
    
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &c) == FAILURE) {
            return;
        }
    
        RETURN_DOUBLE(php_celsius_to_fahrenheit(c));
    }
    
    static const zend_functiоn_entry pib_functiоns[] =
    {
        PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius) /* Done above */
        PHP_FE(celsius_to_fahrenheit,arginfo_celsius_to_fahrenheit) /* just added */
        PHP_FE_END
    };

    現在是一個更複雜的用例,在將它作為 C 擴展實現之前,在 PHP 中展示它:

    const TEMP_CONVERTER_TO_CELSIUS     = 1;
    const TEMP_CONVERTER_TO_FAHREINHEIT = 2;
    
    functiоn temperature_converter($temp, $type = TEMP_CONVERTER_TO_CELSIUS)
    {
        switch ($type) {
            case TEMP_CONVERTER_TO_CELSIUS:
                return sprintf("%.2f degrees fahrenheit gives %.2f degrees celsius", $temp,
                                fahrenheit_to_celsius($temp));
            case TEMP_CONVERTER_TO_FAHREINHEIT:
                return sprintf("%.2f degrees celsius gives %.2f degrees fahrenheit, $temp,
                                celsius_to_fahrenheit($temp));
            default:
                trigger_error("Invalid mode provided, accepted values are 1 or 2", E_USER_WARNING);
            break;
        }
    }

    這個例子有助於我們介紹常量

    常量在擴展中很容易管理,就像它們在用戶區一樣。常量通常是持久性的,意味着它們應該在請求之間保持其值不變。如果你知道 PHP 的生命周期,則應該猜到 MINIT()是向引擎註冊常量的正確階段。

    在內部,這有個常量,一個zend_constant 結構:

    typedef struct _zend_constant {
        zval value;
        zend_string *name;
        int flags;
        int module_number;
    } zend_constant;

    真的是一個簡單的結構(如果你深入了解常量是如何管理到引擎中,那可能會是一場噩夢)。你聲明了namevalue,一些flags(不是很多),並且module_number自動設置為你的擴展編號(不用注意它)。

    要註冊常量,同樣的,這一點都不難,一堆宏可以幫你完成:

    #define TEMP_CONVERTER_TO_FAHRENHEIT 2
    #define TEMP_CONVERTER_TO_CELSIUS 1
    
    PHP_MINIT_FUNCTION(pib)
    {
        REGISTER_LONG_CONSTANT("TEMP_CONVERTER_TO_CELSIUS", TEMP_CONVERTER_TO_CELSIUS, CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("TEMP_CONVERTER_TO_FAHRENHEIT", TEMP_CONVERTER_TO_FAHRENHEIT, CONST_CS|CONST_PERSISTENT);
    
        return SUCCESS;
    }

    注意

    給出 C 宏的 PHP 常量值是一個很好的實踐。事情變得容易了,這就是我們做的。

    根據你的常量類型,你將使用 REGISTER_LONG_CONSTANT()REGISTER_DOUBLE_CONSTANT()等等。API 和宏位於 Zend/zend_constants.h中。

    flag 在CONST_CS (case-sensitive constant 大小寫敏感常量,我們想要的)和CONST_PERSISTENT(持久性常量,在請求中也是我們想要的)之間是混合的 OR 操作。

    現在在 C 中的temperature_converter($temp, $type = TEMP_CONVERTER_TO_CELSIUS)函數:

    ZEND_BEGIN_ARG_INFO_EX(arginfo_temperature_converter, 0, 0, 1)
        ZEND_ARG_INFO(0, temperature)
        ZEND_ARG_INFO(0, mode)
    ZEND_END_ARG_INFO();

    我們得到了一個必須的參數,兩個中的一個。那就是我們聲明的。其默認值不是一個參數聲明可以解決的,那將在一秒鐘內完成。

    然後我們將我們的新函數添加到函數註冊向量:

    static const zend_functiоn_entry pib_functiоns[] =
    {
        PHP_FE(fahrenheit_to_celsius,arginfo_fahrenheit_to_celsius) /* seen above */
        PHP_FE(celsius_to_fahrenheit,arginfo_celsius_to_fahrenheit) /* seen above */
        PHP_FE(temperature_converter, arginfo_temperature_converter) /* our new functiоn */
    }

    函數主體:

    PHP_FUNCTION(temperature_converter)
    {
        double t;
        zend_long mode = TEMP_CONVERTER_TO_CELSIUS;
        zend_string *result;
    
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "d|l", &t, &mode) == FAILURE) {
            return;
        }
    
        switch (mode)
        {
            case TEMP_CONVERTER_TO_CELSIUS:
                result = strpprintf(0, "%.2f degrees fahrenheit gives %.2f degrees celsius", t, php_fahrenheit_to_celsius(t));
                RETURN_STR(result);
            case TEMP_CONVERTER_TO_FAHRENHEIT:
                result = strpprintf(0, "%.2f degrees celsius gives %.2f degrees fahrenheit", t, php_celsius_to_fahrenheit(t));
                RETURN_STR(result);
            default:
                php_error(E_WARNING, "Invalid mode provided, accepted values are 1 or 2");
        }
    }

    記得好好看 README.PARAMETER_PARSING_API。它不是一個很難的 API,你必須熟悉它。

    我們使用 “d|l” 作為 zend_parse_parameters()的參數。一個 double、或(管道“|”)、一個 long。注意,如果在運行時不提供可選參數(提醒一下,ZEND_NUM_ARGS()是什麼),則 &mode不會被 zpp() 觸及。這就是為什麼我們提供了一個TEMP_CONVERTER_TO_CELSIUS默認值給該變量。

    然後我們使用 strpprintf() 去構建一個 zend_string,並且使用 RETURN_STR() 返回它到 return_value zval。

    注意

    strpprintf() 和它的朋友們在打印函數章節有解釋過。

    使用 Hashtable (PHP 數組)

    現在讓我們來玩一下PHP 數組並設計:

    functiоn multiple_fahrenheit_to_celsius(array $temperatures)
    {
        foreach ($temperatures as $temp) {
            $return[] = fahreinheit_to_celsius($temp);
        }
    
        return $return;
    }

    所以在 C 語言實現的時候,我們需要zend_parse_parameters()並請求一個數組,遍歷它,進行數學運算,並將結果作為數組添加到 return_value

    ZEND_BEGIN_ARG_INFO_EX(arginfo_multiple_fahrenheit_to_celsius, 0, 0, 1)
        ZEND_ARG_ARRAY_INFO(0, temperatures, 0)
    ZEND_END_ARG_INFO();
    
    static const zend_functiоn_entry pib_functiоns[] =
    {
            /* ... */
        PHP_FE(multiple_fahrenheit_to_celsius, arginfo_multiple_fahrenheit_to_celsius)
        PHP_FE_END
    };
    
    PHP_FUNCTION(multiple_fahrenheit_to_celsius)
    {
        HashTable *temperatures;
        zval *data;
    
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &temperatures) == FAILURE) {
            return;
        }
        if (zend_hash_num_elements(temperatures) == 0) {
            return;
        }
    
        array_init_size(return_value, zend_hash_num_elements(temperatures));
    
        ZEND_HASH_FOREACH_VAL(temperatures, data)
            zval dup;
            ZVAL_COPY_VALUE(&dup, data);
            convert_to_double(&dup);
        add_next_index_double(return_value, php_fahrenheit_to_celsius(Z_DVAL(dup)));
        ZEND_HASH_FOREACH_END();
    }

    注意

    你需要知道 Hashtable 的工作原理,並且必讀 zval 章節

    在這裡,C 語言那部分將更快,因為不需要在 C 循環中調用 PHP 函數,但是一個靜態(可能由編輯器內聯的)函數,它的運行速度快了幾個數量級,並且運行低級 CPU 指令所需的時間也更少。這並不是說這個小小的演示函數在代碼性能方面需要如此多的關注,只要記住為什麼我們有時會使用 C 語言代替 PHP。

    管理引用

    現在讓我們開始玩 PHP 引用。您已經從 zval 章節 了解到引用是在引擎中使用的一種特殊技巧。作為提醒,引用(我們指的是&$php_reference)是分配給 zval的,存儲在 zval 的容器中。

    所以,只要記住引用是什麼以及它們的設計目的,就不難將它們處理成 PHP 函數。

    如果你的函數接受一個參數作為引用,你必須在參數簽名中聲明,並從你的 zend_parse_parameter() 調用中傳遞一個引用。

    讓我們像往常一樣,首先使用 PHP 示例:因此,現在C中,首先我們必須更改 arg_info

    ZEND_BEGIN_ARG_INFO_EX(arginfo_fahrenheit_to_celsius, 0, 0, 1)
        ZEND_ARG_INFO(1, fahrenheit)
    ZEND_END_ARG_INFO();

    " 1 ",中傳遞的 ZEND_ARG_INFO() 宏告訴引擎必須通過引用傳遞參數。

    然後,當我們接收到參數時,我們使用 z 參數類型,以告訴我們希望將它作為一個 zval 給出。當我們向引擎提示它應該向我們傳遞一個引用這一事實時,我們將獲得對該 zval 的引用,也就是它的類型為is_reference時,我們只需要解引用它(即獲取存儲到 zval中的 zval),並按原樣修改它,因為引用的預期行為是您必須修改引用所攜帶的值:

    PHP_FUNCTION(fahrenheit_to_celsius)
    {
        double result;
        zval *param;
    
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &param) == FAILURE) {
            return;
        }
    
        ZVAL_DEREF(param);
        convert_to_double(param);
    
        ZVAL_DOUBLE(param, php_fahrenheit_to_celsius(Z_DVAL_P(param)));
    }

    完成。

    注意

    默認 return_value 值為 NULL。如果我們不碰它,函數將返回PHP的 NULL

    想了解更多編程學習,敬請關注php培訓欄目!

    以上就是小知識大學問的註冊 PHP 函數的詳細內容!


    ————————————————