首页 后端开发 php教程 [翻译][php扩展开发和嵌入式]第19章-设置宿主环境

[翻译][php扩展开发和嵌入式]第19章-设置宿主环境

Feb 10, 2017 am 10:41 AM
php


设置宿主环境

现在你已经了解了PHPAPI的世界, 并可以使用zval以及语言内部扩展机制执行很多工作了, 是时候转移目标用它做它最擅长的事情了: 解释脚本代码.

嵌入式SAPI

回顾介绍中, php构建了一个层级系统. 最高层是提供用户空间函数和类库的所有扩展. 同时, 其下是服务API(SAPI)层, 它扮演了webserver(比如apache, iis以及命令行接口cli)的接口.

在这许多sapi实现中有一个特殊的sapi就是嵌入式sapi. 当这个sapi实现被构建时, 将会创建一个包含所有你已知的php和zend api函数以及变量的库对象, 这个库对象还包含一些额外的帮助函数和宏, 用以简化外部程序的调用.

生成嵌入式api的库和头文件和其他sapi的编译所执行的动作相同. 只需要传递--enable-embed到./configure命令中即可. 和以前一样, 使用--enable-debug对于错误报告和跟踪很有帮助.

你可能还需要打开--enable-maintainer-zts, 当然, 理由你已经耳熟能详了, 它将帮助你注意到代码的错误, 不过, 这里还有其他原因. 假设某个时刻, 你有多个应用使用php嵌入库执行脚本任务; 其中一个应用是简单的短生命周期的, 它并没有使用线程, 因此为了效率你可能想要关闭ZTS.

现在假设第二个应用使用了线程, 比如webserver, 每个线程需要跟踪自己的请求上下文. 如果ZTS被关闭, 则只有第一个应用可以使用这个库; 然而, 如果打开ZTS, 则两个应用都可以在自己的进程空间使用同一个共享对象.

当然, 你也可以同时构建两个版本, 并给它们不同的名字, 但是这相比于在不需要ZTS时包括ZTS带来的很小的效率影响更多的问题.

默认情况下, 嵌入式库将构建为libphp5.so共享对象, 或者在windows下的动态链接库, 不过, 它也可能使用可选的static关键字(--enable-embed=static)被构建为静态库.

构建为静态库的版本避免了ZTS/非ZTS的问题, 以及潜在的可能在一个系统中有多个php版本的情况. 风险在于这就意味着你的结果应用二进制将显著变大, 它将承载整个ZendEngine和PHP框架, 因此, 选择的时候就需要慎重的考虑你是否需要的是一个相对更小的库.

无论你选择那种构建方式, 一旦你执行make install, libphp5都将被拷贝到你的./configure指定的PREFIX目录下的lib/目录中. 此外还会在PREFIX/include/php/sapi/embed目录下放入名为php_embed.h的头文件, 以及你在使用php嵌入式库编译程序时需要的其他几个重要的头文件.

构建并编译一个宿主应用

究其本质而言, 库只是一个没有目的的代码集合. 为了让它工作, 你需要用以嵌入php的应用. 首先, 我们来封装一个非常简单的应用, 它启动Zend引擎并初始化PHP处理一个请求, 接着就回头进行资源的清理.

#include <sapi/embed/php_embed.h>

int main(int argc, char *argv[])
{
    PHP_EMBED_START_BLOCK(argc,argv)
    PHP_EMBED_END_BLOCK()

    return 0;
}
登录后复制


由于这涉及到了很多头文件, 构建实际上需要的时间要长于这么小的代码片段通常需要的时间. 如果你使用了不同于默认路径(/usr/local)的PREFIX, 请确认以下面的方式指定路径:

gcc -I /usr/local/php-dev/include/php/ \
	-I /usr/local/php-dev/include/php/main/ \
	-I /usr/local/php-dev/include/php/Zend/ \
	-I /usr/local/php-dev/include/php/TSRM/ \
	-lphp5 \
	-o embed1 
	embed1.c
登录后复制

由于这个命令每次输入都很麻烦, 你可能更原意用一个简单的Makefile替代:

CC = gcc 
CFLAGS = -c \
    -I /usr/local/php-dev/include/php/ \
    -I /usr/local/php-dev/include/php/main/ \
    -I /usr/local/php-dev/include/php/Zend/ \
    -I /usr/local/php-dev/include/php/TSRM/ \
    -Wall -g
LDFLAGS = -lphp5

all: embed1.c
    $(CC) -o embed1.o embed1.c $(CFLAGS)
    $(CC) -o embed1 embed1.o $(LDFLAGS)
登录后复制

这个Makefile和前面提供的命令有一些重要的区别. 首先, 它用-Wall开关打开了编译期的警告, 并且用-g打开了调试信息. 此外它将编译和链接两个阶段分为了两个独立的阶段, 这样在后期增加更多源文件的时候就相对容易. 请自己重新组着这个Makefile, 不过这里用于对齐的是Tab(水平制表符)而不是空格.

现在, 你对embed1.c源文件做修改后, 只需要执行一个make命令就可以构建出新的embed1可执行程序了.

通过嵌入包装重新创建cli

现在php已经可以在你的应用中访问了, 是时候让它做一些事情了. 本章剩下的核心就是围绕着在这个测试应用框架中重新创建cli sapi展开的.

很简单, cli二进制程序最基础的功能就是在命令行指定一个脚本的名字, 由php对其解释执行. 用下面的代码替换你的embed1.c的内容就在你的应用中实现了cli.

#include <stdio.h>
#include <sapi/embed/php_embed.h>

int main(int argc, char *argv[]) {
    zend_file_handle    script;

    /* 基本的参数检查 */
    if ( argc <= 1 ) {
        fprintf(stderr, "Usage: %s <filename.php> <arguments>\n", argv[0]);
        return -1;
    }
    
    /* 设置一个文件处理结构 */
    script.type             = ZEND_HANDLE_FP;
    script.filename         = argv[1];
    script.opened_path      = NULL;
    script.free_filename    = 0;
    if ( !(script.handle.fp = fopen(script.filename, "rb")) ) {
        fprintf(stderr, "Unable to open: %s\n", argv[1]);
        return -1;
    }
    
    /* 在将命令行参数注册给php时(php中的$argv/$argc), 忽略第一个命令行参数, 因为它对php脚本无意义 */
    argc --;
    argv ++;
    
    PHP_EMBED_START_BLOCK(argc, argv)
        php_execute_script(&script TSRMLS_CC);
    PHP_EMBED_END_BLOCK()
    
    return 0;
}
登录后复制

译注: 原著中的代码在译者的环境不能直接运行, 上面的代码是经过修改的.

当然, 你需要一个文件测试它, 创建一个小的php脚本, 命名为test.php, 在命令行使用你的embed程序执行它:

$ ./embed1 test.php
登录后复制

如果你给命令行传递了其他参数, 你可以在你的php脚本中使用$_SERVER['argc']/$_SERVER['argv']看到它们.

你可能注意到了, 在PHP_EMBED_START_BLOCK()和PHP_EMBED_END_BLOCK()之间的代码是缩进的. 这个细节是因为这两个宏实际上构成了一个C语言的代码块作用域. 也就是说PHP_EMBED_START_BLOCK()包含一个打开的花括号"{", 在PHP_EMBED_END_BLOCK()中则有与之对应的关闭花括号"}". 这样做非常重要的一个问题是它们不能被放入到独立的启动/终止函数中. 下一章你将看到这个问题的解决方案.

老技术新用

在PHP_EMBED_START_BLOCK()被调用后, 你的应用处于一个php请求周期的开始位置, 相当于RINIT回调函数完成以后. 此刻你就可以和前面一样执行php_execute_script()命令, 或者其他任意合法的, 可以在PHP_FUNCTION()或RINIT()块中出现的php/Zend API指令.

设置初始变量

第2章"变量的里里外外"中介绍了操纵符号表的概念, 第5至18章则介绍了怎样通过用户空间脚本调用内部函数使用这些技术. 到这里这些处理也并没有发生变化, 虽然这里并没有激活的用户空间脚本, 但是你的包装应用仍然可以操纵符号表. 将你的PHP_EMBED_START_BLOCK()/PHP_EMBED_END_BLOCK()代码块替换为下面的代码:

    PHP_EMBED_START_BLOCK(argc, argv)
        zval    *type;

        ALLOC_INIT_ZVAL(type);
        ZVAL_STRING(type, "Embedded", 1);
        ZEND_SET_SYMBOL(&EG(symbol_table), "type", type);
        
        php_execute_script(&script TSRMLS_CC);
    PHP_EMBED_END_BLOCK()
登录后复制

现在使用make重新构建embed1, 并用下面的测试脚本进行测试:

<?php
	var_dump($type);
?>
登录后复制

当然, 这个简单的概念可以很容易的扩展为填充这个类型信息到$_SERVER超级全局变量数组中.

    PHP_EMBED_START_BLOCK(argc, argv)
        zval    **SERVER_PP, *type;

        /* 注册$_SERVER超级全局变量 */
        zend_is_auto_global_quick("_SERVER", sizeof("_SERVER") - 1, 0 TSRMLS_CC);
        /* 查找$_SERVER超级全局变量 */
        zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&SERVER_PP) ;

        /* $_SERVER[&#39;SAPI_TYPE&#39;] = "Embedded"; */
        ALLOC_INIT_ZVAL(type);
        ZVAL_STRING(type, "Embedded", 1);
        ZEND_SET_SYMBOL(Z_ARRVAL_PP(SERVER_PP), "SAPI_TYPE", type);

        php_execute_script(&script TSRMLS_CC);
    PHP_EMBED_END_BLOCK()
登录后复制

译注: 译者的环境中代码运行到zend_hash_find()处$_SERVER尚未注册, 经过跟踪, 发现它是直到编译用户空间代码的时候, 发现用户空间使用了$_SERVER变量才进行的注册. 因此, 上面的代码中增加了zend_is_auto_global_quick()的调用, 通过这个调用将完成对$_SERVER的注册.

覆写INI选项

在第13章"INI设置"中, 有一部分是讲INI修改处理器的, 在那里看到的是INI阶段的处理. PHP_EMBED_START_BLOCK()宏则将这些代码放到了运行时阶段. 也就是说这个时候修改某些设置(比如register_globals/magic_quotes_gpc)已经有点迟了.

不过在内部访问也没有什么不好. 所谓的"管理设置"比如safe_mode在这个略迟的阶段可以使用下面的zend_alter_ini_entry()命令打开或关闭:

int zend_alter_ini_entry(char *name, uint name_length,
                         char *new_value, uint new_value_length,
                         int modify_type, int stage);
登录后复制

name, new_value以及它们对应的长度参数的含义正如你所预期的: 修改名为name的INI设置的值为new_value. 要注意name_length包含了末尾的NULL字节, 然而new_value_length则不包含; 然而, 无论如何, 两个字符串都必须是NULL终止的.

modify_type则提供简化的访问控制检查. 回顾每个INI设置都有一个modifiable属性, 它是PHP_INI_SYSTEM, PHP_INI_PERDIR, PHP_INI_USER等常量的组合值. 当使用zend_alter_ini_entry()修改INI设置时, modify_type参数必须包含至少一个INI设置的modifiable属性值.

用户空间的ini_set()函数通过传递PHP_INI_USER利用了这个特性, 也就是说只有modifiable属性包含PHP_INI_USER标记的INI设置才能使用这个函数修改. 当在你的嵌入式应用中使用这个API调用时, 你可以通过传递PHP_INI_ALL标记短路这个访问控制系统, 它将包含所有的INI访问级别.

stage必须对应于Zend Engine的当前状态; 对于这些简单的嵌入式示例, 总是PHP_INI_STAGE_RUNTIME. 如果这是一个扩展或更高端的嵌入式应用, 你可能就需要将这个值设置为PHP_INI_STAGE_STARTUP或PHP_INI_STAGE_ACTIVE.

下面是扩展embed1.c源文件, 让它在执行脚本文件之前强制开启safe_mode.

    PHP_EMBED_START_BLOCK(argc, argv)
        zval    **SERVER_PP, *type;

        /* 不论php.ini中如何设置都强制开启safe_mode */
        zend_alter_ini_entry("safe_mode", sizeof("safe_mode"), "1", sizeof("1") - 1, PHP_INI_ALL, PHP_INI_STAGE_RUNTIME);
        
        /* 注册$_SERVER超级全局变量 */
        zend_is_auto_global_quick("_SERVER", sizeof("_SERVER") - 1, 0 TSRMLS_CC);
        /* 查找$_SERVER超级全局变量 */
        zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&SERVER_PP) ;
        
        /* $_SERVER[&#39;SAPI_TYPE&#39;] = "Embedded"; */
        ALLOC_INIT_ZVAL(type);
        ZVAL_STRING(type, "Embedded", 1);
        ZEND_SET_SYMBOL(Z_ARRVAL_PP(SERVER_PP), "SAPI_TYPE", type);
        
        php_execute_script(&script TSRMLS_CC);
    PHP_EMBED_END_BLOCK()
登录后复制

定义附加的超级全局变量

在第12章"启动, 终止, 以及其中的一些点"中, 你知道了用户空间全局变量以及超级全局变量可以在启动(MINIT)阶段定义. 同样, 本章介绍的嵌入式直接跳过了启动阶段, 处于运行时状态. 和覆写INI一样, 这并不会显得太迟.

超级全局变量的定义实际上只需要在脚本编译之前定义即可, 并且在php的进程生命周期中它只应该出现一次. 在扩展中的正常情况下, MINIT是唯一可以保证这些条件的地方.

由于你的包装应用现在是在控制中的, 因此可以保证定义用户空间自动全局变量的这些点位于真正编译脚本源文件的php_execute_script()命令之前. 我们定义一个$_EMBED超级全局变量并给它设置一个初始值来进行测试:

    PHP_EMBED_START_BLOCK(argc, argv)
        zval    **SERVER_PP, *type, *EMBED, *foo;

        /* 在全局作用域创建$_EMBED数组 */
        ALLOC_INIT_ZVAL(EMBED);
        array_init(EMBED);
        ZEND_SET_SYMBOL(&EG(symbol_table), "_EMBED", EMBED);

        /* $_EMBED[&#39;foo&#39;] = &#39;Bar&#39;; */
        ALLOC_INIT_ZVAL(foo);
        ZVAL_STRING(foo, "Bar", 1); 
        add_assoc_zval_ex(EMBED, "foo", sizeof("foo"), foo);

        /* 注册超级全局变量$_EMBED */
        zend_register_auto_global("_EMBED", sizeof("_EMBED")
#ifdef ZEND_ENGINE_2
            , 1, NULL TSRMLS_CC);
#else
            , 1 TSRMLS_CC);
#endif

        /* 不论php.ini中如何设置都强制开启safe_mode */
        zend_alter_ini_entry("safe_mode", sizeof("safe_mode"), "1", sizeof("1") - 1, PHP_INI_ALL, PHP_INI_STAGE_RUNTIME);

        /* 注册$_SERVER超级全局变量 */
        zend_is_auto_global_quick("_SERVER", sizeof("_SERVER") - 1, 0 TSRMLS_CC);
        /* 查找$_SERVER超级全局变量 */
        zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&SERVER_PP) ;

        /* $_SERVER[&#39;SAPI_TYPE&#39;] = "Embedded"; */
        ALLOC_INIT_ZVAL(type);
        ZVAL_STRING(type, "Embedded", 1); 
        ZEND_SET_SYMBOL(Z_ARRVAL_PP(SERVER_PP), "SAPI_TYPE", type);

        php_execute_script(&script TSRMLS_CC);
    PHP_EMBED_END_BLOCK()
登录后复制

要记住, Zend Engine 2(php 5.0或更高)使用了不同的zend_register_auto_global()元婴, 因此你需要用前面讲php 4兼容时候讲过的#ifdef. 如果你不关心旧版本php的兼容性, 则可以丢弃这些指令让代码变得更加整洁.

小结

如你所见, 将完整的Zend Engine和PHP语言嵌入到你的应用中相比如扩展新功能来说工作量要少. 由于它们共享相同的基础API, 我们可以学习尝试让其他实例可访问.

通过本章的学习, 你了解了最简单的嵌入式脚本代码格式, 同时还有all-in-one的宏PHP_EBED_START_BLOCK()和PHP_EMBED_END_BLOCK(). 下一章你将回到这些宏的层的使用, 利用它们将php和你的宿主系统结合起来.

以上就是[翻译][php扩展开发和嵌入式]第19章-设置宿主环境的内容,更多相关内容请关注PHP中文网(www.php.cn)!


本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 带来了多项新功能、安全性改进和性能改进,同时弃用和删除了大量功能。 本指南介绍了如何在 Ubuntu、Debian 或其衍生版本上安装 PHP 8.4 或升级到 PHP 8.4

我后悔之前不知道的 7 个 PHP 函数 我后悔之前不知道的 7 个 PHP 函数 Nov 13, 2024 am 09:42 AM

如果您是一位经验丰富的 PHP 开发人员,您可能会感觉您已经在那里并且已经完成了。您已经开发了大量的应用程序,调试了数百万行代码,并调整了一堆脚本来实现操作

如何设置 Visual Studio Code (VS Code) 进行 PHP 开发 如何设置 Visual Studio Code (VS Code) 进行 PHP 开发 Dec 20, 2024 am 11:31 AM

Visual Studio Code,也称为 VS Code,是一个免费的源代码编辑器 - 或集成开发环境 (IDE) - 可用于所有主要操作系统。 VS Code 拥有针对多种编程语言的大量扩展,可以轻松编写

在PHP API中说明JSON Web令牌(JWT)及其用例。 在PHP API中说明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一种基于JSON的开放标准,用于在各方之间安全地传输信息,主要用于身份验证和信息交换。1.JWT由Header、Payload和Signature三部分组成。2.JWT的工作原理包括生成JWT、验证JWT和解析Payload三个步骤。3.在PHP中使用JWT进行身份验证时,可以生成和验证JWT,并在高级用法中包含用户角色和权限信息。4.常见错误包括签名验证失败、令牌过期和Payload过大,调试技巧包括使用调试工具和日志记录。5.性能优化和最佳实践包括使用合适的签名算法、合理设置有效期、

您如何在PHP中解析和处理HTML/XML? 您如何在PHP中解析和处理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示了如何使用PHP有效地处理XML文档。 XML(可扩展的标记语言)是一种用于人类可读性和机器解析的多功能文本标记语言。它通常用于数据存储

php程序在字符串中计数元音 php程序在字符串中计数元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符组成的序列,包括字母、数字和符号。本教程将学习如何使用不同的方法在PHP中计算给定字符串中元音的数量。英语中的元音是a、e、i、o、u,它们可以是大写或小写。 什么是元音? 元音是代表特定语音的字母字符。英语中共有五个元音,包括大写和小写: a, e, i, o, u 示例 1 输入:字符串 = "Tutorialspoint" 输出:6 解释 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。总共有 6 个元

解释PHP中的晚期静态绑定(静态::)。 解释PHP中的晚期静态绑定(静态::)。 Apr 03, 2025 am 12:04 AM

静态绑定(static::)在PHP中实现晚期静态绑定(LSB),允许在静态上下文中引用调用类而非定义类。1)解析过程在运行时进行,2)在继承关系中向上查找调用类,3)可能带来性能开销。

什么是PHP魔术方法(__ -construct,__destruct,__call,__get,__ set等)并提供用例? 什么是PHP魔术方法(__ -construct,__destruct,__call,__get,__ set等)并提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些?PHP的魔法方法包括:1.\_\_construct,用于初始化对象;2.\_\_destruct,用于清理资源;3.\_\_call,处理不存在的方法调用;4.\_\_get,实现动态属性访问;5.\_\_set,实现动态属性设置。这些方法在特定情况下自动调用,提升代码的灵活性和效率。

See all articles