Heim >Backend-Entwicklung >PHP-Tutorial >[Übersetzung] [Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 20 – Erweiterte Einbettung von PHP
Erweiterte Einbettung
Die Einbettung von PHP kann mehr als nur synchrones Laden und Ausführen von Skripten ermöglichen wie die verschiedenen Teile des PHP-Ausführungsmoduls kombiniert werden, sodass sogar ein Skript an Ihre Hostanwendung zurückgerufen werden kann. In diesem Kapitel werden die Vorteile der von der SAPI-Schicht bereitgestellten I/O-Hooks behandelt und das, was Sie bereits gelernt haben, erweitert das Ausführungsmodul, das die Informationen aus dem vorherigen Thema erhalten hat.
Zurück zu PHP
Zusätzlich zum Laden Externe Skripte, ähnlich dem, was Sie im vorherigen Kapitel gesehen haben, Ihre eingebettete PHP-Anwendung, implementieren einen Befehl ähnlich dem User Space eval().
int zend_eval_string(char *str, zval *retval_ptr, char *string_name TSRMLS_DC)
Hier ist str der tatsächlicher PHP-Skriptcode, der ausgeführt werden soll, und string_name ist eine beliebige Beschreibung, die mit der Ausführung verknüpft ist. Wenn ein Fehler auftritt, verwendet PHP diese Beschreibung als „Dateinamen“ in der Fehlerausgabe. Sie haben es erraten wird auf den vom übergebenen Code erzeugten Rückgabewert gesetzt. Versuchen Sie, mit dem folgenden Code ein neues Projekt zu erstellen.
#include <sapi/embed/php_embed.h> int main(int argc, char *argv[]) { PHP_EMBED_START_BLOCK(argc, argv) zend_eval_string("echo 'Hello World!';", NULL, "Simple Hello World App" TSRMLS_CC); PHP_EMBED_END_BLOCK() return 0; }
Verwenden Sie nun den Befehl oder Kapitel 19 „Host einrichten“. Umgebung „Erstellen Sie es (ersetzen Sie „embed1“ durch „embed2“ im Makefile oder im Befehl)
Alternative: Include der Skriptdatei
Vorhersehbar macht dies das Kompilieren und Ausführen externer Skriptdateien viel einfacher als die vorherige Methode, da Ihre Anwendung die ansonsten komplexe Ausführungssequenz „Öffnen/Vorbereiten/Ausführen“ vereinfachen kann. Aber leistungsfähigere Designalternativen:
#include <sapi/embed/php_embed.h> int main(int argc, char *argv[]) { char *filename; if ( argc <= 1 ) { fprintf(stderr, "Usage: %s <filename.php> <arguments>\n", argv[1]); return -1; } filename = argv[1]; /* 忽略第0个参数 */ argc --; argv ++; PHP_EMBED_START_BLOCK(argc, argv) char *include_script; spprintf(&include_script, 0, "include '%s';", filename); zend_eval_string(include_script, NULL, filename TSRMLS_CC); efree(include_script); PHP_EMBED_END_BLOCK() return 0; }
HINWEIS: Diese besondere Methode muss einen Nachteil haben, nämlich dass, wenn der Dateiname einfache Anführungszeichen enthält, Dies führt jedoch zu einem Analysefehler, indem Sie den API-Aufruf php_addslashes() in ext/standard/php_string.h verwenden. Sie werden viele Funktionen finden, die dazu führen Damit Sie das Rad in Zukunft nicht neu erfinden müssen.
User-Space-Funktionen aufrufen
Wie Sie Wenn Sie sehen können, wie Skriptdateien geladen und ausgeführt werden, gibt es intern zwei Möglichkeiten, Userspace-Funktionen aufzurufen. Die offensichtlichste Möglichkeit besteht jetzt wahrscheinlich darin, zend_eval_string() wiederzuverwenden, den Funktionsnamen und alle seine Argumente in einem großen String zu organisieren und dann den Rückgabewert zu sammeln .
PHP_EMBED_START_BLOCK(argc, argv) char *command; zval retval; spprintf(&command, 0, "nl2br('%s');", argv[1]); zend_eval_string(command, &retval, "nl2br() execution" TSRMLS_CC); efree(command); printf("out: %s\n", Z_STRVAL(retval)); zval_dtor(&retval); PHP_EMBED_END_BLOCK()
ist dem vorherigen include sehr ähnlich. Diese Methode weist einen schwerwiegenden Fehler auf: Wenn der Eingabeparameter paramin (der Übersetzer in Im Beispiel schlägt die Funktion fehl oder führt schlimmer noch zu unerwarteten Ergebnissen. Die Lösung besteht darin, das Kompilieren des Laufzeitcodes immer zu vermeiden und die API-Aufruffunktion call_user_function direkt zu verwenden.
int call_user_function(HashTable *function_table, zval **object_pp, zval *function_name, zval *retval_ptr, zend_uint param_count, zval *params[] TSRMLS_DC);
Bei einem Aufruf von außerhalb der Engine ist function_table immer EG(function_table). Wenn ein Objekt oder eine Klassenmethode aufgerufen wird, muss es object_pp sein Die aufrufende Instanz zval vom Typ IS_OBJECT oder der Wert von IS_STRING für statische Aufrufe der Klasse function_name ist normalerweise der Wert von IS_STRING, der den Namen der aufzurufenden Funktion enthält, kann aber auch IS_ARRAY sein, das 0. Element enthält einen Objekt- oder Klassenname, und das 1. Element enthält den Methodennamen. Das Ergebnis dieses Funktionsaufrufs zeigt auf den eingehenden retval_ptr zval legt den Rückgabewert fest und params spielen die Rolle von argc/argv. Das heißt, params[0] enthält den ersten übergebenen Parameter und params[param_count - 1] enthält den letzten übergebenen Parameter.
下面是用这种方法重新实现上面的例子:
PHP_EMBED_START_BLOCK(argc, argv) zval *args[1]; zval retval, str, funcname; ZVAL_STRING(&funcname, "nl2br", 0); args[0] = &str; ZVAL_STRINGL(args[0], "HELLO WORLD!", sizeof("HELLO WORLD!"), 1); call_user_function(EG(function_table), NULL, &funcname, &retval, 1, args TSRMLS_CC); printf("out: %s\n", Z_STRVAL(retval)); zval_dtor(args[0]); zval_dtor(&retval); PHP_EMBED_END_BLOCK()
尽管代码看起来比较长, 但是工作量会显著降低, 因为这里没有要编译的中间代码, 传递的数据不需要复制, 每个参数都已经在Zend兼容的结构体中. 同时, 要记得原来的例子中在字符串中包含单引号时会有潜在的错误. 而这个版本没有这个问题.
错误处理
当发生错误时, 比如脚本解析错误, php将会进入到bailout模式. 在你已经看到的简单的嵌入式例子中, 这表示它将直接跳到PHP_EMBED_END_BLOCK()宏, 并且绕过所有这个块中的剩余代码. 由于多数潜入php解释器的应用, 目的并不只是为了执行php代码, 因此避免由于php脚本的故障导致整个应用崩溃是有意义的.
有一种方式可以将所有的执行限制到一个非常小的START/END块中, 这样发生崩溃就只影响当前块. 这种方式的缺点是每个START/END块函数都是独立的PHP请求. 因此比如下面START/END块, 虽然从语法逻辑上来看两个块是协同工作的, 但实际上它们之间是不共享公共作用域的.
int main(int argc, char *argv[]) { PHP_EMBED_START_BLOCK(argc, argv) zend_eval_string("$a = 1;", NULL, "Script Block 1"); PHP_EMBED_END_BLOCK() PHP_EMBED_START_BLOCK(argc, argv) /* 将打印出"NULL", 因为变量$a在这个请求中并没有定义. */ zend_eval_string("var_dump($a);", NULL, "Script Block 2"); PHP_EMBED_END_BLOCK() return 0; }
还有一种解决方法是将两个zend_eval_string()调用使用Zend特有的伪语言结构zend_try, zend_catch, zend_end_try进行隔离. 使用这些结构, 你的应用就可以按照想要的方式处理错误. 考虑下面的代码:
int main(int argc, char *argv[]) { PHP_EMBED_START_BLOCK(argc, argv) zend_try { /* 尝试执行一些可能失败的代码 */ zend_eval_string("$1a = 1;", NULL, "Script Block 1a"); } zend_catch { /* 发生错误, 则尝试执行另外一部分代码(一般错误的补救或报告等行为) */ zend_eval_string("$a = 1;", NULL, "Script Block 1"); } zend_end_try(); /* 这里将显示"NULL", 因为变量$a在这个请求中没有定义. */ zend_eval_string("var_dump($a);", NULL, "Script Block 2"); PHP_EMBED_END_BLOCK() return 0; }
在这个示例的第二个版本中, zend_try块中将发生解析错误, 但它只影响自己的代码块, 同时在zend_catch块中使用了一段好的代码对错误进行了处理. 同样你也可以尝试自己给var_dump()部分也加上这些块.
译注: 这里对zend_try/zend_catch/zend_end_try解释的不是很清楚, 因此做以下补充说明. 读者阅读这一部分内容需要首先了解sigsetjmp()/siglongjmp()的机制(可以参考a823753b809442f15532a0b488672c3d第10章第15节).
相关的定义如下:
#ifdef HAVE_SIGSETJMP# define SETJMP(a) sigsetjmp(a, 0) # define LONGJMP(a,b) siglongjmp(a, b) # define JMP_BUF sigjmp_buf #else # define SETJMP(a) setjmp(a) # define LONGJMP(a,b) longjmp(a, b) # define JMP_BUF jmp_buf #endif #define zend_try \ { \ JMP_BUF *__orig_bailout = EG(bailout); \ JMP_BUF __bailout; \ \ EG(bailout) = &__bailout; \ if (SETJMP(__bailout)==0) { #define zend_catch \ } else { \ EG(bailout) = __orig_bailout; #define zend_end_try() \ } \ EG(bailout) = __orig_bailout; \ }
zend_try {}代码块中的代码是在一个if语句中的, 这个if的条件是SETJMP(__bailout) == 0, SETJMP()是在当前程序执行的点设置一个可回溯的点(保存了当前执行上下文和环境), SETJMP()的返回比较特殊, 它有两种返回: 1) 直接返回, 此时返回值为0; 2) 调用LONGJMP()返回到对应__bailout当时调用SETJMP()的位置, 此时返回值非0.
基于上面的论述, 可以看出, 当zend_try的代码块中调用了LONGJMP()的时候, 程序将回到if ( SETJMP(__bailout) == 0 )的位置开始执行, 并且它的返回值为-1, 因此, 进入到对应的else语句块, 也就是zend_catch语句块的代码.
zend_end_try()则只是一个结尾的花括号.
php中的这个伪语言结构正式这种方式实现的异常处理机制, 在系统的关键点调用zend_bailout()(在Zend/zend.h中定义)即可.
本例中, 译者增加了zend_bailout()调用, 演示了这个伪语言结构的使用.
初始化php
迄今为止, 你看到的PHP_EMBED_START_BLOCK()和PHP_EMBED_END_BLOCK()宏都用于启动, 执行, 终止一个紧凑的院子的php请求. 这样做的优点是任何导致php bailout的错误顶多影响到PHP_EMBED_END_BLOCK()宏之内的当前作用域. 通过将你的代码执行放入到这两个宏之间的小块中, php的错误就不会影响到你的整个应用.
你刚才已经看到了, 这种短小精悍的方法主要的缺点在于每次你建立一个新的START/END块的时候, 都需要创建一个新的请求, 新的符号表, 因此就失去了所有的持久性语义.
要想同时得到两种优点(持久化和错误处理), 就需要将START和END宏分解为它们各自的组件(译注: 如果不明白可以参考这两个宏的定义). 下面是本章开始给出的embed2.c程序, 这一次, 我们对它进行了分解:
#include <sapi/embed/php_embed.h> int main(int argc, char *argv[]) { #ifdef ZTS void ***tsrm_ls; #endif php_embed_init(argc, argv PTSRMLS_CC); zend_first_try { zend_eval_string("echo 'Hello World!';", NULL, "Embed 2 Eval'd string" TSRMLS_CC); } zend_end_try(); php_embed_shutdown(TSRMLS_C); return 0; }
它执行和之前一样的代码, 只是这一次你可以看到打开和关闭的括号包裹了你的代码, 而不是无法分开的START和END块. 将php_embed_init()放到你应用的开始, 将php_embed_shutdown()放到末尾, 你的应用就得到了一个持久的单请求生命周期, 它还可以使用zend_first_try {} zend_end_try(); 结构捕获所有可能导致你整个包装应用跳出末尾的PHP_EMBED_END_BLOCK()宏的致命错误.
这个时候要注意, zend_first_try, 而不是zend_try. 在TRy/catch块外围使用zend_first_try非常重要, 因为zend_first_try执行了一些额外的步骤(清理EG(bailout))
为了看看真实世界环境的这种方法的应用, 我们将本章前面一些的例子的启动和终止处理进行了抽象:
#include <sapi/embed/php_embed.h> #ifdef ZTS void ***tsrm_ls; #endif static void startup_php(void) { /* Create "dummy" argc/argv to hide the arguments * meant for our actual application */ int argc = 1; char *argv[2] = { "embed4", NULL }; php_embed_init(argc, argv PTSRMLS_CC); } static void shutdown_php(void) { php_embed_shutdown(TSRMLS_C); } static void execute_php(char *filename) { zend_first_try { char *include_script; spprintf(&include_script, 0, "include '%s';", filename); zend_eval_string(include_script, NULL, filename TSRMLS_CC); efree(include_script); } zend_end_try(); } int main(int argc, char *argv[]) { if (argc <= 1) { printf("Usage: embed4 scriptfile"); return -1; } startup_php(); execute_php(argv[1]); shutdown_php(); return 0; }
类似的概念也可以应用到处理任意代码的执行以及其他任务. 只需要确认在最外部的容器上使用zend_first_try, 则里面的每个容器上使用zend_try即可.
覆写INI_SYSTEM和INI_PERDIR选项
在上一章中, 你曾经使用zend_alter_ini_setting()修改过一些php的ini选项. 由于samp/embed直接将你的脚本推入了运行时模式, 因此许多重要的INI选项在控制返回到你的应用时并没有被修改. 为了修改这些值, 就需要在主引擎启动之后而请求启动之前执行代码.
有一种方式是拷贝php_embed_init()的内容到你的应用中, 在你的本地拷贝中做必要的修改, 接着使用你修改后的版本替代它. 当然这种方式可能会有问题.
首先也是最重要的, 你实际已经对别人的部分代码做了分支, 然而可能别人还会向其中添加新的代码. 现在, 你就不再是只维护自己的应用了, 还需要保持分支出来的代码和主分支保持一致. 幸运的是, 还有几种更简单的方法:
覆写默认的php.ini文件
因为嵌入式和其他的php sapi实现一样都是sapi, 它通过一个sapi_module_struct挂入到引擎中. 嵌入式SAPI定义并设置了这个结构体的一个实例, 你的应用可以在调用php_embed_init()之前访问它.
在这个结构体中, 有一个名为php_ini_path_override的char *类型字段. 为了让嵌入的请求使用你的可选文件扩展php和Zend, 只需要在调用php_embed_init()之前将这个字段设置为NULL终止的字符串. 下面是embed4.c中修改版的startup_php()函数:
static void startup_php(void) { /* Create "dummy" argc/argv to hide the arguments * meant for our actual application */ int argc = 1; char *argv[2] = { "embed4", NULL }; php_embed_module.php_ini_path_override = "/etc/php_embed4.ini"; php_embed_init(argc, argv PTSRMLS_CC); }
这就使得每个使用嵌入库的应用可以保持自定义, 而不用将自己的配置暴露给别人. 相反, 如果你想要你的应用不使用php.ini, 只需要设置php_embed_module的php_ini_ignore字段, 这样所有的设置都将使用内建的默认值, 除非由你的应用手动进行修改.
覆写嵌入启动
sapi_module_struct结构还包含一些回调函数, 下面是其中4个在PHP启动和终止阶段比较有用的回调:
/* From main/SAPI.h */ typedef struct _sapi_module_struct { ... int (*startup)(struct _sapi_module_struct *sapi_module); int (*shutdown)(struct _sapi_module_struct *sapi_module); int (*activate)(TSRMLS_D); int (*deactivate)(TSRMLS_D); ... } sapi_module_struct;
这些方法的名字熟悉吗? 它们对应于扩展的MINIT, MSHUTDOWN, RINIT, RSHUTDOWN, 并且和对应在扩展生命周期中的阶段一致. 要利用这些钩子, 可以如下修改embed4中的startup_php()函数:
static int (*original_embed_startup)(struct _sapi_module_struct *sapi_module); static int embed4_startup_callback(struct _sapi_module_struct *sapi_module) { /* 首先调用原来的启动回调, 否则环境未就绪 */ if (original_embed_startup(sapi_module) == FAILURE) { /* 这里可以做应用的失败处理 */ return FAILURE; } /* 调用原来的embed_startup实际上让我们进入到ACTIVATE阶段而不是STARTUP阶段, * 但是我们仍然可以修改多数INI_SYSTEM和INI_PERDIR选项. */ zend_alter_ini_entry("max_execution_time", sizeof("max_execution_time"), "15", sizeof("15") - 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("safe_mode", sizeof("safe_mode"), "1", sizeof("1") - 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); return SUCCESS; } static void startup_php(void) { /* 创建假的argc/argv, 隐藏应用实际的参数 */ int argc = 1; char *argv[2] = { "embed4", NULL }; /* 使用我们自己的启动函数覆写标准的启动方法, 但是保留了原来的指针, 因此它仍然能够被调用到 */ original_embed_startup = php_embed_module.startup; php_embed_module.startup = embed4_startup_callback; php_embed_init(argc, argv PTSRMLS_CC); }
使用safe_mode, open_basedir这样的选项, 以及其他用以限制独立脚本行为的选项, 可以让你的应用更加安全可靠.
捕获输出
除非你开发的是非常简单的控制台应用, 否则你应该不希望php脚本代码产生的输出直接被扔到激活的终端上. 捕获这些输出和你刚才用以覆写启动处理器的方法类似.
在sapi_module_struct中还有一些有用的回调:
typedef struct _sapi_module_struct { ... int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC); void (*flush)(void *server_context); void (*sapi_error)(int type, const char *error_msg, ...); void (*log_message)(char *message); ... } sapi_module_struct;
标准输出: ub_write
所有用户空间的echo和print语句产生的输出, 以及其他内部通过php_printf()或PHPWRITE()产生的输出, 最终都将被发送到激活的SAPI的ub_write()方法. 默认情况, 嵌入式SAPI直接将这些数据交给stdout管道, 而不关心你的应用的输出策略.
假设你的应用想要把所有的输出都发送到一个独立的控制台窗口; 你可能需要实现一个类似于下面伪代码块所描述的回调:
static int embed4_ub_write(const char *str, unsigned int str_length TSRMLS_DC) { output_string_to_window(CONSOLE_WINDOW_ID, str, str_length); return str_length; }
要让这个函数能够处理php产生的内容, 你需要在调用php_embed_init()之前对php_embed_module结构做适当的修改:
php_embed_module.ub_write = embed4_ub_write;
注意: 哪怕你决定你的应用不需要php产生的输出, 也必须为ub_write设置一个回调. 将它的值设置为NULL将导致引擎崩溃, 当然, 你的应用也不能幸免.
缓冲输出: Flush
你的应用可能会使用缓冲php产生的输出进行优化, sapi层提供了一个回调用以通知你的应用"现在请发送你的缓冲区数据", 你的应用并没有义务去实施这个通知; 不过, 由于这个信息通常是由于足够的理由(比如到达请求结束位置)才产生的, 听从这个意见并不会有什么坏处.
下面的这对回调函数, 以256字节缓冲区缓冲数据由引擎安排执行flush.
char buffer[256]; int buffer_pos = 0; static int embed4_ubwrite(const char *str, unsigned int str_length TSRMLS_DC) { char *s = str; char *d = buffer + buffer_pos; int consumed = 0; /* 缓冲区够用, 直接追加到缓冲区后面 */ if (str_length < (256 - buffer_pos)) { memcpy(d, s, str_length); buffer_pos += str_length; return str_length; } consumed = 256 - buffer_pos; memcpy(d, s, consumed); embed4_output_chunk(buffer, 256); str_length -= consumed; s += consumed; /* 消耗整个传入的块 */ while (str_length >= 256) { embed4_output_chunk(s, 256); s += 256; consumed += 256; } /* 重置缓冲区头指针内容 */ memcpy(buffer, s, str_length); buffer_pos = str_length; consumed += str_length; return consumed; } static void embed4_flush(void *server_context) { if (buffer_pos < 0) { /* 输出缓冲区中剩下的内容 */ embed4_output_chunk(buffer, buffer_pos); buffer_pos = 0; } }
在startup_php()中增加下面的代码, 这个基础的缓冲机制就就绪了:
php_embed_module.ub_write = embed4_ub_write; php_embed_module.flush = embed4_flush;
标准错误: log_message
在启用了log_errors INI设置时, 在启动或执行脚本时如果碰到错误, 将激活log_message回调. 默认的php错误处理程序会在处理显示(这里是调用log_message回调)之前, 格式化这些错误消息, 使其称为整齐的, 人类可读的内容.
关于log_message回调, 这里你需要注意的第一件事是它并不包含长度参数, 因此它并不是二进制安全的. 也就是说, 它只是按照NULL终止来处理字符串末尾.
使用它来做错误报告通常不会有什么问题, 实际上, 它可以用于在错误消息的呈现上做更多的事情. 默认情况下, sapi/embed将会通过这个简单的内建回调, 发送这些错误消息到标准错误管道:
static void php_embed_log_message(char *message) { fprintf (stderr, "%s\n", message); }
如果你想发送这些消息到日志文件, 则可以使用下面的版本替代:
static void embed4_log_message(char *message) { FILE *log; log = fopen("/var/log/embed4.log", "a"); fprintf (log, "%s\n", message); fclose(log); }
特殊错误: sapi_error
少数特殊情况的错误属于某个sapi, 因此将绕过php的主错误处理程序. 这些错误一般是由于使用不当造成的, 比如非web应用不应该使用header()函数, 上传文件到控制台应用程序等.
由于这些情况都离你所开发的sapi/embed应用非常遥远, 因此最好保持这个回调为空. 不过, 如果你非要坚持去捕获每种类型错误的源, 也只需要实现一个回调函数, 并在调用php_embed_init()之前覆写它就可以了.
同时扩展和嵌入
在你的应用中运行php代码固然不错, 但是此刻, php执行环境仍然和你的主应用是隔离的, 它们并没有在真正意义上的一个层级进行交互.
现在你应该对php扩展的开发以及构建启用方面比较熟悉了. 你也已经有完成了嵌入工作的例程, 这样就省去了这份工作. 将扩展代码植入到嵌入式应用中的工作量要比标准扩展小. 下面是一个新的嵌入式项目:
#include <sapi/embed/php_embed.h> #ifdef ZTS void ***tsrm_ls; #endif /* Extension bits */ zend_module_entry php_mymod_module_entry = { STANDARD_MODULE_HEADER, "mymod", /* extension name */ NULL, /* function entries */ NULL, /* MINIT */ NULL, /* MSHUTDOWN */ NULL, /* RINIT */ NULL, /* RSHUTDOWN */ NULL, /* MINFO */ "1.0", /* version */ STANDARD_MODULE_PROPERTIES }; /* Embedded bits */ static void startup_php(void) { int argc = 1; char *argv[2] = { "embed5", NULL }; php_embed_init(argc, argv PTSRMLS_CC); zend_startup_module(&php_mymod_module_entry); } static void execute_php(char *filename) { zend_first_try { char *include_script; spprintf(&include_script, 0, "include '%s'", filename); zend_eval_string(include_script, NULL, filename TSRMLS_CC); efree(include_script); } zend_end_try(); ] int main(int argc, char *argv[]) { if (argc <= 1) { printf("Usage: embed4 scriptfile";); return -1; } startup_php(); execute_php(argv[1]); php_embed_shutdown(TSRMLS_CC); return 0; }
现在, 你可以定义function_entry向量, 启动/终止函数, 定义类, 以及所有你想增加的东西. 现在, 它和你使用用户空间的dl()命令加载这个扩展库一样, 在这一个命令中Zend将自动的处理所有的钩子并对你的模块进行注册, 就绪等待使用.(译注: startup_php()中调用zend_startup_module(&php_mymod_module_entry)进行了模块注册)
小结
In diesem Kapitel haben Sie sich einige einfache eingebettete Beispiele im vorherigen Kapitel angesehen und diese erweitert. Sie können PHP bereits in verschiedene Nicht-Thread-Anwendungen integrieren. Jetzt beherrschen Sie die Erweiterung und die eingebetteten Grundlagen und können arbeiten Auf zval, Klasse, Ressource, HashTable können Sie tatsächlich ein echtes Projekt starten.
In den verbleibenden Anhängen sehen Sie, dass es viele API-Funktionen gibt, die von PHP, Zend und anderen Erweiterungen bereitgestellt werden . Sie werden einige häufig verwendete Codeausschnitte und Hunderte von Open-Source-PECL-Projekten der letzten Jahre sehen, die als Referenz für Ihre zukünftigen Projekte verwendet werden können Das Obige ist der Inhalt von [Übersetzung][Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 20 – Erweiterte Einbettung von PHP Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!