Zend API 一(自用备注)

PHP中文手册《Zend API:深入 PHP 内核》

 

1. 摘要

2. 概述

3. 可扩展性

4. 源码布局

5. 自动构建系统

6. 开始创建扩展

7. 使用扩展

8. 故障处理

9. 关于模块代码的讨论

10. 接收参数

11. 创建变量

12. 使用拷贝构造函数复制变量内容

13. 返回函数值

14. 信息输出

15. 启动函数与关闭函数

16. 调用用户函数

17. 支持初始化文件(php.ini)

18. 何去何从

19. 参考:关于配置文件的一些宏

20. API宏

 

 

 

Zend API:深入PHP 内核(一)摘要

      知者不言,言者不知。

           ――老子《道德》五十六章

      候,单纯依靠PHP本身是不行的。尽管普通用很少遇到种情况,但一些专业性的则经常需要将PHP的性能发挥到极致(里的性能是指速度或功能)。由于受到PHP言本身的限制,同时还可能不得不把大的文件包含到每个脚本当中。因此,某些新功能并不是能被实现,所以我另外找一些方法来克服PHP些缺点。

      了解到了一点,我应该接触一下PHP的心并探究一下它的内核-可以编译PHP之工作的C-的候了。

 

Zend API:深入PHP 内核(二)概述

 

      PHP起来容易做起来PHP在已经发展成了一个具有数兆字源代的非常成熟的系。要想深入这样的一个系,有很多西需要学和考。在写一章候,我决定采用的方式。并不是最科学和专业的方式,但却应该是最有趣和最有效的一种方式。在下面的小里,你首先会非常快速的学到如何写一个虽然很基但却能立即运行的展,然后将会学到有关Zend API 的高功能。另外一个选择就是将其作一个整体,一次性的述所有的些操作、设计、技巧和诀窍等,并且可以实际动手前就可以得到一副完整的愿景。看起来似乎是一个更好的方法,也没有死角,但它却枯燥无味、费时费力,很容易人感到气就是我们为什么要采用非常直接的法的原因。

 

      注意,尽管一章会尽可能多述一些关于PHP内部工作机制的知,但要想真的出一份在任何时间任何情况下的PHP展指南,那直是不可能的。PHP是如此大和复,以致于只有你践一下才有可能真正理解它的内部工作机制,因此我强烈推荐你随参考它的源代行工作。

 

Zend 是什么?PHP又是什么?

 

Zend 指的是言引擎,PHP指的是我从外面看到的一套完整的系听起来有点糊涂,但其并不复见图3-1 PHP 内部)。实现一个WEB脚本的解器,你需要完成以下三个部分的工作:

 

1.器部分:负责对输入代的分析、翻行;

2.功能性部分:负责具体实现语言的各种功能(比如它的函数等等);

3.接口部分:负责WEB器的会等功能。

Zend 包括了第一部分的全部和第二部分的局部,PHP包括了第二部分的局部和第三部分的全部。他合起来称之PHP包。Zend构成了言的核心,同也包含了一些最基本的PHP函数的实现PHP包含了所有造出言本身各种著特性的模


3-1  PHP内部

下面将要讨论PHP在哪里展以及如何展。

 

 

 

Zend API:深入 PHP 内核 (三)展性

 

外部模块

 

外部模块可以在脚本运行时使用dl()函数载入。这个函数从磁盘载入一个共享对象并将它的功能与调用该函数的脚本进行绑定并使之生效。脚本终止后,这个外部模块将在内存中被丢弃。这种方式有利有弊:

优点:

      外部模块不需要重新对 PHP 进行编译。

      PHP 通过“外包”方式来让自身的体积保持很小。

缺点:

      共享对象在每次脚本调用时都需要对其进行加载,速度较慢。

      附加的外部模块文件会让磁盘变得比较散乱。

使用:dl()函数手动加载 或者php.ini文件当中添加扩展。

 

综上所述,外部模块非常适合开发第三方产品,较少使用的附加的小功能或者仅仅是调试等这些用途。为了迅速开发一些附加功能,外部模块是最佳方式。但对于一些经常使用的、实现较大的,代码较为复杂的应用,那就有些得不偿失了。

 

 

内建模块

 

内建模块被直接编译进 PHP 并存在于每一个PHP处理请求当中。它们的功能在脚本开始运行时立即生效。和外部 模块一样,内建模块也有一下利弊:

优点:

      无需专门手动载入,功能即时生效。

      无需额外的磁盘文件,所有功能均内置在PHP二进制代码当中。

缺点:

      修改内建模块时需要重新编译 PHP。

      PHP 二进制文件会变大并且会消耗更多的内存。

 

 

Zend API:深入 PHP 内核 (四)布局

 

在我们开始讨论具体编码这个话题前,你应该让自己熟悉一下 PHP 的源代码树以便可以迅速地对各个源文件进行定位。这也是编写和调试 PHP 扩展所必须具备的一种能力。

 

 

目录

内容

php-src

包含了PHP主源文件和主头文件;在这里你可以找到所有的 PHP API 定义、宏等内容。(重要). 其他的一些东西你也可以在这里找到。

ext

这里是存放动态和内建模块的仓库;默认情况下,这些就是被集成于主源码树中的“官方” PHP 模块。自 PHP 4.0开始,这些PHP标准扩展都可以编译为动态可载入的模块。(至少这些是可以的)。

main

这个目录包含主要的 PHP 宏和定义。 (重要)

pear

这个目录就是“PHP 扩展与应用仓库”的目录。包含了PEAR 的核心文件。

sapi

包含了不同服务器抽象层的代码。

TSRM

Zend 和 PHP的 “线程安全资源管理器” (TSRM) 目录。

Zend

包含了Zend 引擎文件;在这里你可以找到所有的 Zend API 定义与宏等。(重要)

当然,讨论PHP包里面全部每一个文件无疑是超出了本章的范,但你应该

 

必看文件:

main/php.h: 位于 PHP 主目录。这个文件包含了绝大部分 PHP 宏及 API 定义。

Zend/zend.h: 位于 Zend 主目录。这个文件包含了绝大部分 Zend 宏及 API 定义。

Zend/zend_API.h: 也位于 Zend 主目录,包含了 Zend API 的定义。

 

除此之外,你也应该注意一下这些文件所包含的一些文件。举例来说,哪些文件与 Zend 执行器有关,哪些文件又为 PHP 初始化工作提供了支持等等。在阅读完这些文件之后,你还可以花点时间再围绕 PHP 包来看一些文件,了解一下这些 文件和模块之间的依赖性――它们之间是如何依赖于别的文件又是如何为其他文件提供支持的。同时这也可以帮助你适应一 下 PHP 创作者们代码的风格。要想扩展 PHP,你应该尽快适应这种风格。

 

 

扩展规范

 

Zend 是用一些特定的规范构建的。为了避免破坏这些规范,你应该遵循以下的几个规则:

      几乎对于每一项重要的任务,Zend 都预先提供了极为方便的宏。在下面章节的图表里将会描述到大部分基本函数、结构和宏。这些宏定义大多可以在 Zend.h 和 Zend_API.h 中找到。我们建议您在学习完本节之后仔细看一下这些文件。(当 然你也可以现在就阅读这些文件,但你可能不会留下太多的印象。)


内存管理

     资源管理仍然是一个极为关键的问题,尤其是对服务器软件而言。资源里最具宝贵的则非内存莫属了,内存管理也必须极端小心。内存管理在 Zend 中已经被部分抽象,而且你也应该坚持使用这些抽象,原因显而易见:由于得以抽象,Zend 就可以完全控制内存的分配。Zend 可以确定一块内存是否在使用,也可以自动释放未使用和失去引用的内存块,因此就可 以避免内存泄漏。下表列出了一些常用函数:

函数: 描述

 

函数

描述

emalloc()

用于替代 malloc()。内存分配函数

efree()

用于替代 free()。释放已分配的块

estrdup()

用于替代 strdup()。将串拷贝到新建的位置处

estrndup()

用于替代strndup()。速度要快于 estrdup() 而且是二进制安全的。如果你在复制之前预先知道这个字符串的长度那就推荐你使用这个函数。

ecalloc()

用于替代 calloc()。分配主存储器

erealloc()

用于替代 realloc()。重新分配主存

 

emalloc(), estrdup(), estrndup(), ecalloc(), 和 erealloc() 用于申请内部的内存,efree() 则用来释放这些前面这些函数 申请的内存。e*() 函数所用到的内存仅对当前本地的处理请求有效,并且会在脚本执行完毕,处理请求终止时被释放。

 

目录与文件函数

 

      下列目录与文件函数应该在 Zend模块内使用。它们的表现和对应的 C 语言版本完全一致, 只是在线程级提供了虚拟目录的支持。

 

Zend 函数

对应的 C 函数

V_GETCWD()

getcwd() 取当前工作目录

V_FOPEN()

fopen()  打开一个流

V_OPEN()

open() 打开一个文件用于读或写

V_CHDIR()

chdir() 改变工作目录

V_GETWD()

getwd()  取当前工作目录 建议使用getcwd()

V_CHDIR_FILE()

将当前的工作目录切换到一个以文件名为参数的该文件所在的目录。

V_STAT()

stat()  读取打开文件信息 (取得文件状态)

V_LSTAT()

stat()类似,当文件为符号连接时, lstat()会返回该link 本身的状态。

 

字符串处理

 

      在 Zend 引擎中,与处理诸如整数、布尔值等这些无需为其保存的值而额外申请内存的简单类型不同,如果你想从一个函数返回一个字符串,或往符号表新建一个字符串变量,或做其他类似的事情,那你就必须确认是否已经使用上面的 e*() 等函数为这些字符串申请内存。(你可能对此没有多大的感觉。无所谓,现在你只需在脑子里有点印象即可,我们稍后就会再次回到这个话题)

 

复杂类型

      像数组和对象等这些复杂类型需要另外不同的处理。它们被出存在哈希表中,Zend 提供了一些简单的 API 来操作这些类型 。

 

 

Zend API:深入 PHP 内核 (五)自动构建系统

 

PHP 提供了一套非常灵活的自动构建系统(automatic build system),它把所有的模块均放在 Ext 子目录下。每个模块除自身的源代码外,还都有一个用来配置该扩展的 config.m4 文件(详情请参见http://www.gnu.org/software/m4/manual/m4.html)。

 

 

包括 .cvsignore 在内的所有文件都是由位于 Ext 目录下的 ext_skel 脚本自动生成的,

它的参数就是你想创建模块的名称。这个脚本会创建一个与模块名相同的目录,里面包含了与该模块对应的一些的文件。

 

下面是操作步骤:

 

./ext_skel –extname=my_module

 

Creating directory my_module

Creating basic files: config.m4 .cvsignoremy_module.c php_my_module.h CREDITS EXPERIMENTAL tests/001.php

t my_module.php [done].

 

To use your new extension, you will have toexecute the following steps:

 

1. $ cd ..

2. $ vi ext/my_module/config.m4

3. $ ./buildconf

4. $ ./configure –[with|enable]-my_module

5. $ make

6. $ ./php -f ext/my_module/my_module.php

7. $ vi ext/my_module/my_module.c

8. $ make

 

(注:以上操作貌似是跟随php码编译成内建模)

(外建模快步)

1. vim config.m4

2. vim my_module.c my_module.h

3. /usr/local/php/bin/phpize[--clean]

4. ./configure --with-php-config=/usr/local/php/bin/php-config

5. make && make install

6. vim php.ini extension = "my_module.so"

7./usr/local/php/sbin/php-fpm restart

8. php -f my_module.php

 


这些指令就会生成前面所说的那些文件。为了能够在自动配置文件和构建程序中包含新增加的模块,你还需要再运行一次 buildconf 命令。这个命令会通过搜索 Ext 目录和查找所有 config.m4 文件来重新生成 configure 脚本。默认情况下的的 config.m4 文件如例 3-1 所示,看起来可能会稍嫌复杂:

 

 

dnl $Id: build.xml,v 1.1 2005/08/21 16:27:06 goba Exp $

dnl config.m4 for extension my_module

dnl Comments in this file start with the string 'dnl'.

dnl Remove where necessary. This file will not work

dnl without editing.

dnl If your extension references something external, use with:

dnl PHP_ARG_WITH(my_module, for my_module support, dnl Make surethat the comment is aligned:

dnl [ --with-my_module Include my_module support])

dnl Otherwise use enable:

dnl PHP_ARG_ENABLE(my_module, whether to enable my_module support,

dnl Make sure that the comment is aligned:

dnl [ --enable-my_module Enable my_module support])

if test $PHP_MY_MODULE != “no”; then

dnl Write more examples of tests here...

dnl # –with-my_module -> check with-path

dnl SEARCH_PATH = /usr/local /usr # you might want to change this

dnl SEARCH_FOR=/include/my_module.h you most likely want to changethis

dnl if test -r $PHP_MY_MODULE/; then # path given as parameter

dnl MY_MODULE_DIR=$PHP_MY_MODULE

dnl else # search default path list

dnl AC_MSG_CHECKING([for my_module files in default path])

dnl for i in $SEARCH_PATH ; do

dnl if test -r $i/$SEARCH_FOR; then

dnl MY_MODULE_DIR=$i

AC_MSG_RESULT(found in $i)

dnl fi

dnl done

dnl fi

dnl

dnl if test -z "$MY_MODULE_DIR"; then

dnl AC_MSG_RESULT([not found])

dnl AC_MSG_ERROR([Please reinstall the my_module distribution])

dnl fi

dnl # –with-my_module -> add include path

dnl PHP_ADD_INCLUDE($MY_MODULE_DIR/include)

dnl # –with-my_module -> chech for lib and symbolpresence

dnl LIBNAME=my_module # you may want to change this

dnl LIBSYMBOL=my_module # you most likely want to change this

dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,

dnl [ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $MY_MODULE_DIR/lib,MY_MODULE_SHARED_LIBADD)

dnl AC_DEFINE(HAVE_MY_MODULELIB,1,[ ])

dnl ],[

dnl AC_MSG_ERROR([wrong my_module lib version or lib not found])

dnl ],[

dnl -L$MY_MODULE_DIR/lib -lm -ldl

dnl ])

dnl

dnl PHP_SUBST(MY_MODULE_SHARED_LIBADD)

PHP_NEW_EXTENSION(my_module, my_module.c, $ext_shared)

fi

 

文件解析:

1   dnl 前缀的都是注释

2  config.m4 文件负责在配置时解析 configure 的命令行选项。这就是说它将检查所需的外部文件并且要 做一些类似配置与安装的任务。

3   默认的配置文件将会在 configure脚本中产生两个配置指令:–with-my_module 和      –enable-my_module。当需要引用外部文件时使用第一个选项(就像用 –with-apache 指令来引用Apache 的目录一样)。第二个选项可以让用户简单的决定是否要启用该扩展。不管你使用哪一个指令,   你都应该注释掉另外一个。也就是说,如果你使用了–enable-my_module, 那就应该去掉–with-my_module。反之亦然。(注:第二个选项'可能'是用于内建模)

4   默认情况下,通过 ext_skel 创建的 config.m4 都能接受指令,并且会自动启用该扩展。启用该扩展是通过PHP_EXTENSION 这个宏进行的。如果你要改变一下默认的情况,想让用户明确的使用–enable-my_module 或–with-my_module 指令来把扩展包含在 PHP 二进制文件当中,那么将

“if test "$PHP_MY_MODULE" != “no””改为“if test "$PHP_MY_MODULE" == "yes"”即可。

      if test"$PHP_MY_MODULE" == "yes"; then dnl

           Action..PHP_EXTENSION(my_module, $ext_shared)

      fi

      这样就会导致在每次重新配置和编译 PHP 时都要求用户使用 –enable-my_module 指令。

5   另外请注意在修改config.m4文件后需要重新运行buildconf命令(外建模快使用phpize重新编译)

6   明:

      PHP_REQUIRE_CXX 用于指定这个扩展用到了C++;

      PHP_ADD_INCLUDE 指定PHP扩展模块用到的头文件目录;

      PHP_CHECK_LIBRARY 指定PHP扩展模块PHP_ADD_LIBRARY_WITH_PATH定义以及库连接错误信息等;

      PHP_ADD_LIBRARY(stdc++,"",EXTRA_LDFLAGS)用于将标准C++库链接进入扩展

      PHP_SUBST(EXTERN_NAME_SHARED_LIBADD) 用于说明这个扩展编译成动态链接库的形式;

      PHP_NEW_EXTENSION 用于指定有哪些源文件应该被编译,文件和文件之间用空格隔开;

      ext_skel默认生成的模块框架是针对C的,我们要使用C++进行PHP扩展,那除以上的      PHP_REQUIRE_CXX,PHP_ADD_LIBRARY两个宏必需外,还要把my_module .c改名成      my_module.cpp。

 (注:使用c++需要外到一些操作:)

      将引用C 的文件使用extern“C”{}包起来 

      并使用BEGIN_EXTERN_C() END_EXTERN_C()ZEND_GET_MODULE(my_module)函数包起来

 

编译安装

 

/usr/local/php/bin/phpize

./configure--with-php-config=/usr/local/php/bin/php-config

make&& make install

 

vim php.ini

extension = "my_module.so"

重启

/usr/local/php/sbin/php-fpm restart

 

测试

php -r 'var_dump(my_module("xxx"));'

或者 php my_module.php

 

 

 

Zend API:深入PHP内核 (六)开始

      我们先来创建一个非常简单的扩展,这个扩展除了一个将其整形参数作为返回值的函数外几乎什么都没有。下面(“例3-2 一个简单的扩展”)就是这个样例的代码:

例3.2 一个简单的扩展
/* include standard header */
#include "php.h"

/*declaration of functions to be exported */
ZEND_FUNCTION(first_module);

/*compiled function list so Zend knows what‘s in this module */
zend_function_entry firstmod_functions[] =
{
      ZEND_FE(first_module, NULL)
      {NULL, NULL, NULL}
};

/*compiled module information */
zend_module_entry firstmod_module_entry =
{
      STANDARD_MODULE_HEADER,
      "First Module",
      firstmod_functions,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NO_VERSION_YET,
      STANDARD_MODULE_PROPERTIES
};

/*implement standard "stub" routine to introduce ourselves to Zend */
#if COMPILE_DL_FIRST_MODULE
      ZEND_GET_MODULE(firstmod)
#endif

/*implement function that is meant to be made available to PHP */
ZEND_FUNCTION(first_module)
{
      long parameter;

      if (zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC, "l", &parameter) ==   FAILURE){
           return;
      }

      RETURN_LONG(parameter);
}

      这段代码已经包含了一个完整的PHP 模块。稍后我们会详细解释这段代码,现在让我们先讨论一下构建过程。(在我们讨论API 函数前,这可以让心急的人先实验一下。)

模块的编译

模块的编译基本上有两种方法:

      1、在Ext 目录内使用“make” 机制,这种机制也可以编译出动态可加载模块。
      2、手动编译源代码。

      第一种方法明显受到人们的偏爱。自 PHP 4.0 以来,这也被标准化成了一个的复杂的构建过程。这种复杂性也导致了难于被理解这个缺点。在本章最后我们会更详细的讨论这些内容,但现在还是让我们使用默认的 make 文件吧。

      第二种方法很适合那些(因为某种原因而)没有完整 PHP 源码树的或者是很喜欢敲键盘的人。虽然这些情况是比较罕见,但为了内容的完整性我们也会介绍一下这种方法。

      使用 make 进行编译

      为了能够使用这种标准机制流程来编译这些代码,让我们把它所有的子目录都复制到 PHP 源码树的 Ext 目录下。然后运行 buildconf 命令,这将会创建一个新的包含了与我们的扩展相对应的选项的 configure 脚本。默认情况下,样例中的所有代码都是未激活的,因此你不用担心会破坏你的构建程序。在buildconf 执行完毕后,再使用 configure –help 命令就会显示出下面的附加模块:

--enable-array_experimentsBOOK: Enables array experiments
--enable-call_userland BOOK: Enables userland module
--enable-cross_conversion BOOK: Enables cross-conversion module
--enable-first_module BOOK: Enables first module
--enable-infoprint BOOK: Enables infoprint module
--enable-reference_test BOOK: Enables reference test module
--enable-resource_test BOOK: Enables resource test module
--enable-variable_creation BOOK: Enables variable-creation module

前面样例(“例3-2 一个简单的扩展”)中的模块(first_module)可以使用 –enable-first_module 或–enable-first_module=yes来激活。

手动编译

手动编译需要运行以下命令:

动作

命令

编译

cc -fpic -DCOMPILE_DL_FIRST_MODULE=1 -I/usr/local/include -I. -I.. -I../Zend -c -o <your_object_file> <your_c_file>

连接

cc -shared -L/usr/local/lib -rdynamic -o <your_module_file> <your_object_file(s)>

      编译命令只是简单的让编译器产生一些中间代码(不要忽略了-fpic 参数),然后又定义了COMPILE_DL 常量来通知代码这是要编译为一个动态可加载的模块(通常用来测试,我们稍后会讨论它)。这些选项后面是一些编译这些源代码所必须包含的库文件目录。

      注意:本例中所有include 的路径都是都是Ext 目录的相对路径。如果您是在其他目录编译的这些源文件,那么还要相应的修改路径名。编译所需要的目录有 PHP 目录,Zend 目录和模块所在的目录(如果有必要的话)。

      连接命令也是一个非常简单的把模块连接成一个动态模块的命令。

      你可以在编译指令中加入优化选项,尽管这些已经在样例中忽略了(不过你还是可以从前面讨论的 make 模版文件中发现一些)。

      注意,手动将模块静态编译和连接到PHP 二进制代码的指令很长很长,因此我们在这里不作讨论。(手动输入那些指令是很低效的。)

 

Zend API:深入 PHP 内核 (七)使用

 

      根据你所选择的不同的构建程,你要么把编译进一个新的PHP的二制文件,然后再接到Web器(或以CGI模式运行),要么将其编译成一个.so(共享)文件。如果你将上面的例文件first_module.c编译成了一个共享,那么编译后的文件应该first_module.so。要想使用它,你就必把他复制到一个PHP访问到的地方。如果仅仅测试简单,你可以把它复制到你的htdocs下,然后用“例3.3 first_module.so的一个测试文件”中的代行一下测试。如果你将其直接编译编译进PHP制文件的,那就不用dl()函数了,因为这个模的函数在脚本一开始运行就生效了。

 

警告:

      了安全起,你不应该将你的动态放入一个公共目。即使是一个简单测试你可以那么做,那也应该把它放进产境中的一个隔离的目


3.3 first_module.so的一个测试文件

<?php
      // remove next comment if necessary
      // dl("first_module.so");
      $param = 2
      $return = first_module($param);
      print("We sent '$param' and got'$return'");
?>

      测试文件,应该输We sent ’2′ and got ’2′

 

      若有需要,你可以dl()函数来入一个动态可加个函数负责寻找指定的共享行加使其函数在PHP中生效。例模块仅输出了一个函数first_module()个函数接受一个参数,并将其转换为整数作函数的果返回。

      如果你已经进行到了一步,那么,恭喜你,你已成功建了你的第一个PHP展!


Zend API:深入  PHP  内核  (八)故障处理



实际上,在对静态或动态模块进行编译时没有太多故障处理工作要做。唯一可能的问题就是编译器会警告说找不到某些定义或者类似的事情。如果出现这种情况,你应该确认一下所有的头文件都是可用的并且它们的路径都已经在编译命令中被指定。为了确保每个文件都能被正确地定位,你可以先提取一个干净的 PHP源码树,然后在 Ext 目录使用自动构建工具来创建这些文件。用这种方法就可以确保一个安全的编译环境。假如这样也不行,那就只好试试手动编译了。

 

PHP 也可能会警告说在你的模块里面有一些未定义的函数。(如果你没有改动样例文件的话这种情况应该不会发生。) 假如你在模块中拼错了一些你想访问的外部函数的名字, 那么它们就会在符号表中显示为“未能连接的符号”。这样在 PHP动态加载或连接时,它们就不会运行—在二进制文件中没有相应的符号。为了解决这个问题,你可以在你的模块文件中找一下错误的声明或外部引用。注意,这个问题仅仅发生在动态可加载模块身上。而在静态模块身上则不会发生,因为静态模块在编译时就会抛出这些错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值