C++ 開發 PHP 7 擴展之原生函數定義

在上一篇中咱們在hellozapi擴展中咱們定義了幾個常量,可是一個有用的擴展,必須得有函數,沒有函數的擴展啥用沒有,若是您以爲定義函數很難的話,您又錯了,zendAPI就是爲了讓您生活變得美好而生的,而不會讓事情變得複雜。
說到函數,我們就不得不說函數最重要的兩個組成部分,一個是函數的參數,另外一個是函數的返回值。由於C++是靜態語言,因此我們的函數的類型必須在編譯時就要肯定,不像PHP語言中那麼靈活。
zendAPI主要支持以下幾種函數原型:php

  1. 有返回值, 無參數
  2. 有返回值, 有參數
  3. 有返回值, 可變參數
  4. 無返回值, 無參數
  5. 無返回值, 有參數
  6. 無返回值, 可變參數

說明:zendAPI支持引用類型的參數傳遞html

考慮到咱們是新手學堂,在本篇中咱們就不介紹可變參數和引用傳參了,這部分咱們放在咱們的高級教程部分講。
咱們會在hellozapi中定義如下PHP原型的函數:(PHP 語言描述)ios

  1. print_project_name($prefix);
  2. print_develop_team();
  3. get_version();
  4. add_two_num($num1, $num2);

下面咱們聲明這幾個PHP函數對應的C++函數原型api

C++ Code

using zapi::ds::Variant;
using zapi::ds::NumericVaraint;
using zapi::ds::StringVariant;

void print_project_name(const StringVariant &prefix);
void print_develop_team();
Variant get_version();
Variant add_two_num(const NumericVariant &num1, const NumericVariant num2);

背景知識學習

在上面的C++函數的原型聲明中出現兩個陌生的類VariantNumericVariant, 不要擔憂,如今咱們簡單介紹一下這兩個類。函數

zapi::ds::Variant

zendAPI中,zapi::ds::Variant類的一個對象就表明PHP的一個變量,您能夠將zapi::ds::Variant想象成一個容器,它將常見的C++類型包裝成一個zapi::ds::Variant對象,方便跟zend engine整合。
您能夠用這個類去包裝以下類型:學習

  1. 常見的整形 (int, std::int8_t, std::int16_t, std::int32_t, long ... )
  2. 浮點型 (float, double)
  3. 布爾型 (true, false)
  4. 字符串 (std::string, char *, char [])
  5. 空指針 (std::nullptr_t)

上面說的既然zapi::ds::Variant能夠包裝一切必要的類型,是否是就夠了呢?答案是否認的,雖然zapi::ds::Variant能夠容納C++的這些數據類型,可是它不提供任何特定類型的計算,好比常見的四則運算,字符串鏈接,函數調用等等。
那麼問題又來了,你可能會問,爲何不提供這樣的接口呢?接下來我就來解釋下爲何不在zapi::ds::Variant爲何不提供這些接口,緣由有以下幾點:
1.zapi::ds::Variant設計的目的就是充當一個容器,方便zendAPIzend engine進行數據傳遞,它強調的數據傳遞而不是數據的計算。
2.zendAPI的設計理念是,單一的類完成單一的任務,把字符串操做和整形操做甚至函數調用等等雜在一塊兒違背了這個理念。spa

使用範例
using zapi::ds::Variant;

Varaint nullVar(nullptr);
Variant numVar(123);
Variant doubleVar(3.14);
zapi::ds::NumericVariant

根據上面討論的,看着名字不用我說,你們都能猜出這個類的做用吧,沒錯,您猜的是對的,這個是對zapi::ds::Variant再次封裝,爲數值類型的zapi::ds::Variant提供數值計算的能力,好比四則運算, 大小比較運算。設計

使用範例
using zapi::ds::NumericVariant;

NumericVariant num1(123);
NumericVariant num2(321);
NumericVariant sum = num1 + num2;
long rawSum = sum.toLong();
bool cmp = num1 < num2; // cmp is true

std::int32_t raw32int1 = 123;
std::int16_t raw32int2 = 23;
NumericVariant num3(raw32int1); // value is 123
NumericVariant num4(raw32int2); // value is 23
sum = num3 + num4; // sum is 146

zapi::ds::NumericVariant 參考手冊指針

zapi::ds::StringVariant

這個類跟zapi::ds::NumericVariant同樣,看名字咱們就知道這個類是爲字符串操做而設計的,它爲咱們提供了常見的字符串接口,拼接,子串查找,替換等等。下面咱們就舉幾個常見的使用的範例:code

使用範例
using zapi::ds::StringVariant;

StringVariant str1("hello zapi"); // str1 is hello zapi
str1 += ", hello"; // now hello zapi, hello
char c = str1[0]; // c is h
std::string upperStr1 = str1.toUpperCase();
str1.replace("zapi", "zendAPI"); // str1 is hello zendAPI, hello
str1.prepend("=> "); // str1 now is => hello zendAPI, hello

zapi::ds::StringVariant 參考手冊

好了數據類型瞭解完畢,咱們下面開始進入實現環節。

第一步

打開hellozapi項目下的hellozapi/defs.h文件,在文件中輸入咱們的C++函數的原型聲明代碼。

#ifndef ZAPI_HELLOZAPI_DEFS_H
#define ZAPI_HELLOZAPI_DEFS_H

#include "zapi/ZendApi.h"

using zapi::ds::Variant;
using zapi::ds::NumericVariant;
using zapi::ds::StringVariant;

void print_project_name(const StringVariant &prefix);
void print_develop_team();
Variant get_version();
Variant add_two_num(const NumericVariant &num1, const NumericVariant &num2);

#endif // ZAPI_HELLOZAPI_DEFS_H

第二步

打開hellozapi項目下的hellozapi/impls.cpp文件,在文件中輸入咱們的C++函數的實現代碼。

#include "defs.h"
#include <iostream>

void print_project_name(const StringVariant &prefix)
{
   zapi::out << prefix << " " << "hellozapi" << std::endl;
}

void print_develop_team()
{
   zapi::out << "qcoreteam" << std::endl;
}

Variant get_version()
{
   return "v1.0.2";
}

Variant add_two_num(const NumericVariant &num1, const NumericVariant &num2)
{
   return num1 + num2;
}

第三步

將咱們的實現的C++函數與zend engine進行整合。打開咱們的入口文件hellozapi/entry.cpp,輸入咱們的函數註冊代碼。

#include "zapi/ZendApi.h"
#include "defs.h"

using zapi::lang::Constant;
using zapi::lang::ValueArgument;

extern "C" {

ZAPI_DECL_EXPORT void *get_module() 
{
   static zapi::lang::Extension hellozapi("hellozapi", "1.0");
   Constant hellozapiVersionConst("HELLO_ZAPI_VERSION", 0x010002);
   Constant hellozapiNameConst("HELLO_ZAPI_NAME", "Hello zendAPI!");
   Constant helloDebugModeConst("HELLO_DEBUG_MODE", true);
   Constant helloPiConst("HELLO_ZAPI_PI", 3.14);
   hellozapi.registerConstant(std::move(hellozapiVersionConst));
   hellozapi.registerConstant(std::move(hellozapiNameConst));
   hellozapi.registerConstant(std::move(helloDebugModeConst));
   hellozapi.registerConstant(std::move(helloPiConst));
   
   hellozapi.registerFunction<decltype(print_project_name), print_project_name>
         ("print_project_name", {
             ValueArgument("prefix", zapi::lang::Type::String)
          });
   hellozapi.registerFunction<decltype(print_develop_team), print_develop_team>
         ("print_develop_team");
   hellozapi.registerFunction<decltype(get_version), get_version>("get_version");
   hellozapi.registerFunction<decltype(add_two_num), add_two_num>
         ("add_two_num", {
             ValueArgument("num1", zapi::lang::Type::Numeric),
             ValueArgument("num2", zapi::lang::Type::Numeric)
          });
   return hellozapi;
}

}

到這裏,代碼稍稍有些複雜了,可是細心的同窗會發現,其實代碼是頗有規律的,只是重複調用而已,在這段代碼中咱們引入了幾個新的類型,下面我先將這樣類型作些講解,而後咱們再對這個代碼段進行解釋。

zapi::lang::Type 類型

zendAPIzend engine的宏類型定義從新用enum class進行了從新定義,方便實施C++的類型檢查,好比經常使用的類型有:

  1. zapi::lang::Type::Undefined
  2. zapi::lang::Type::Null
  3. zapi::lang::Type::False
  4. zapi::lang::Type::True
  5. zapi::lang::Type::Long
  6. zapi::lang::Type::String

zapi::lang::Type 參考手冊

zapi::lang::ValueArgument 類型

zendAPI支持的參數傳遞有兩種,按值傳參和按引用傳參。zapi::lang::ValueArgument類型就是爲了支持按值傳遞參數機制,它的構造函數很簡單,第一個參數是傳遞的參數的名字,第二個參數是這個參數的類型,第三個參數設置這個參數是不是必須的參數。
好比下面的代碼咱們定義了一個名叫arg1的參數,類型是字符串而且是非必要參數

ValueArgument("arg1", zapi::lang::Type::String, false);

zapi::lang::ValueArgument 參考手冊

zapi::lang::Extension::registerFunction 函數接口

爲了支持不一樣類型的函數,zapi::lang::Extension::registerFunction被設計成了一個模板函數,在這篇文章中咱們暫時使用了用於註冊非成員函數指針的部分。
傳遞的模板參數有:

  1. 函數的類型 (通常咱們不定義函數的類型,使用decltype進行獲取)
  2. 函數指針值 (會被zendAPI在運行時進行調用)

decltype 參考手冊

傳遞的調用參數有:

  1. 函數的名字
  2. 函數接受的參數列表std::initializer_list<zapi::lang::Argument>

這裏的 zapi::lang::Argument 是 zapi::lang::ValueArgument 的基類,通常不直接使用。

zapi::lang::Extension::registerFunction 參考手冊
std::initializer_list 參考手冊

有了上面的背景知識,如今咱們解釋函數註冊代碼就簡單多了,您也很容易就能理解。

hellozapi.registerFunction<decltype(print_project_name), print_project_name>
      ("print_project_name", {
             ValueArgument("prefix", zapi::lang::Type::String)
      });

這行代碼註冊一個原型爲print_project_name($prefix);PHP函數,當這個函數被zend engine執行的時候,咱們的C++函數void print_project_name(const StringVariant &prefix);將被運行時調用。

hellozapi.registerFunction<decltype(print_develop_team), print_develop_team>
     ("print_develop_team");

這行代碼註冊一個原型爲print_develop_teamPHP函數,當這個函數被zend engine執行的時候,咱們的C++函數void print_develop_team();將被運行時調用。

hellozapi.registerFunction<decltype(get_version), get_version>("get_version");

這行代碼註冊一個原型爲get_versionPHP函數,當這個函數被zend engine執行的時候,咱們的C++函數Variant get_version();將被運行時調用。

hellozapi.registerFunction<decltype(add_two_num), add_two_num>
     ("add_two_num", {
        ValueArgument("num1", zapi::lang::Type::Numeric),
        ValueArgument("num2", zapi::lang::Type::Numeric)
     });

這行代碼註冊一個原型爲add_two_numPHP函數,當這個函數被zend engine執行的時候,咱們的C++函數Variant add_two_num(const NumericVariant &num1, const NumericVariant &num2);將被運行時調用。

咱們走到這裏,函數註冊就完成了,雖然有些小長,可是您不也堅持看完了嗎?
下面讓咱們在PHP代碼中愉快的進行調用吧。

<?php
if (function_exists("print_project_name")) {
    print_project_name("nb, ");
}
if (function_exists("print_develop_team")) {
    print_develop_team();
}
if (function_exists("get_version")) {
    $version = get_version();
    echo $version;
}
echo "\n";
if (function_exists("add_two_num")) {
    $sum = add_two_num(1, 2);
    echo $sum;
}

// you will get output:
// nb, hellozapi
// qcoreteam
// v1.0.2
// 3

怎麼樣,實現函數也不過如此吧,根本沒啥難度,哈哈哈,您到時候也能自豪的說,我也能沒事的試試寫寫擴展啦,給PHP語言添加幾個原生函數了。下一篇,咱們來點更刺激的,教你們怎麼實現原生的Class

原文地址:C++ 開發 PHP 7 擴展之原生函數定義

相關文章
相關標籤/搜索