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 扩展之原生函数定义

相关文章
相关标签/搜索