찾다
백엔드 개발PHP 튜토리얼[번역] [php 확장 개발 및 임베딩] 20장 - PHP의 고급 임베딩


고급 임베딩

PHP의 임베딩은 동기 로딩 및 스크립트 실행 이상의 기능을 제공할 수 있습니다. PHP 실행 모듈의 다양한 부분이 어떻게 결합되는지, 스크립트를 제공하여 호스트 애플리케이션으로 다시 호출할 수 있는지 이 장에서는 SAPI 계층에서 제공하는 I/O 후크의 이점을 포함하고 이미 수행한 작업을 확장합니다. 이전 주제에서 정보를 얻은 실행 모듈로부터 학습합니다.

PHP로 다시 호출

추가로 이전 장에서 본 것과 유사한 외부 스크립트를 로드하기 위해 PHP 임베디드 애플리케이션은 사용자 공간 eval()과 유사한 명령을 구현합니다.

int zend_eval_string(char *str, zval *retval_ptr,
                     char *string_name TSRMLS_DC)

여기서 str은 실제 실행될 PHP 스크립트 코드이며 string_name은 실행과 관련된 임의의 설명 정보입니다. 오류가 발생하면 PHP는 이 설명 정보를 오류 출력의 "파일 이름"으로 사용합니다. 전달된 코드에 의해 생성된 반환 값으로 설정됩니다. 다음 코드를 사용하여 새 프로젝트를 생성해 보세요.

#include <sapi/embed/php_embed.h>

int main(int argc, char *argv[]) {
    PHP_EMBED_START_BLOCK(argc, argv)
        zend_eval_string("echo &#39;Hello World!&#39;;", NULL, "Simple Hello World App" TSRMLS_CC);
    PHP_EMBED_END_BLOCK()
    
    return 0;
}

이제 명령을 사용하거나 19장 "호스팅 환경 설정" " 빌드하세요(Makefile 또는 명령에서 embed1을 embed2로 교체)

대안: 스크립트 파일 포함

예상 가능 , 애플리케이션이 복잡한 열기/준비/실행 실행 시퀀스를 간단하지만 더 강력한 디자인 대안으로 변환할 수 있기 때문에 이전 방법보다 훨씬 쉽게 외부 스크립트 파일을 컴파일하고 실행할 수 있습니다.

#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 &#39;%s&#39;;", filename);
        zend_eval_string(include_script, NULL, filename TSRMLS_CC);
        efree(include_script);
    PHP_EMBED_END_BLOCK()
    
    return 0;
}

참고: 이 특정 접근 방식은 한 가지 단점을 허용해야 합니다. 파일 이름에 작은따옴표가 포함되어 있으면 구문 분석 오류가 발생합니다. 그러나 이 문제는 ext/standard/php_string.h에 있는 php_addslashes() API 호출을 사용하여 해결할 수 있습니다. 이 파일과 부록의 API 참조를 읽으면 재창조를 피할 수 있는 많은 기능을 찾을 수 있습니다.

사용자 공간 함수 호출

보시는 바와 같이 스크립트 로딩 및 실행을 볼 수 있습니다. 파일 내부적으로 사용자 공간 함수를 호출하는 두 가지 방법이 있습니다. 이제 가장 확실한 방법은 함수 이름과 모든 매개변수를 하나의 거대한 문자열로 구성한 다음 반환 값을 수집하는 zend_eval_string()을 재사용하는 것입니다. 🎜>

    PHP_EMBED_START_BLOCK(argc, argv)
        char    *command;
        zval    retval;

        spprintf(&command, 0, "nl2br(&#39;%s&#39;);", 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()

는 이전 include와 매우 유사합니다. 이 방법에는 치명적인 결함이 있습니다. 입력 매개변수 paramin(번역자가 제공한 예는 argv[1 ])에 잘못된 데이터가 제공되면 함수는 실패하거나 더 나쁜 경우 예상치 못한 결과가 발생할 수 있습니다. 해결책은 항상 런타임 코드 부분을 컴파일하지 않고 call_user_function() API를 사용하여 함수를 직접 호출하는 것입니다.

int call_user_function(HashTable *function_table, zval **object_pp,
                       zval *function_name, zval *retval_ptr,
                       zend_uint param_count, zval *params[] TSRMLS_DC);

실제로 엔진 외부에서 호출할 때 function_table은 항상 EG(function_table)입니다. 객체에서 호출되거나 클래스 메서드의 경우 object_pp는 호출 인스턴스 zval이어야 합니다. IS_OBJECT 유형 또는 클래스에 대한 정적 호출의 경우 IS_STRING 값 function_name은 일반적으로 호출할 함수의 이름을 포함하는 IS_STRING 값이지만 IS_ARRAY일 수도 있습니다. 0번째 요소는 개체 또는 클래스 이름을 포함합니다. 첫 번째 요소에는 메서드 이름이 포함됩니다.

이 함수 호출의 결과는 반환 값을 들어오는 param_count와 params가 가리키는 zval로 설정하는 것입니다. argc/argv. 즉, params[0]에는 전달된 첫 번째 매개변수가 포함되고 params[param_count - 1]에는 전달된 마지막 매개변수가 포함됩니다.

下面是用这种方法重新实现上面的例子:

    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()的机制(可以参考第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 &#39;Hello World!&#39;;", NULL,
                         "Embed 2 Eval&#39;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 &#39;%s&#39;;", 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 &#39;%s&#39;", 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)进行了模块注册)

小结

이 장에서는 이전 장에서 몇 가지 간단한 임베디드 예제를 살펴보고 이를 확장했습니다. 이제 다양한 비스레드 애플리케이션에 PHP를 넣을 수 있으며 이제 확장 및 임베디드 기본 사항을 익힐 수 있습니다. zval, class, Resource, HashTable에서 실제로 실제 프로젝트를 시작할 수 있습니다.

나머지 부록에서는 php, zend 및 기타 확장 프로그램에 의해 노출되는 많은 API 함수가 있음을 볼 수 있습니다. . 최근 몇 년 동안 일반적으로 사용되는 코드 조각과 수백 개의 오픈 소스 PECL 프로젝트를 볼 수 있으며 이는 향후 프로젝트에 대한 참조로 사용할 수 있습니다. 위 내용은 [번역][php 확장 개발 및 임베딩] 20장-PHP 고급 임베딩 내용입니다. 더 많은 관련 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
PHP의 현재 상태 : 웹 개발 동향을 살펴보십시오PHP의 현재 상태 : 웹 개발 동향을 살펴보십시오Apr 13, 2025 am 12:20 AM

PHP는 현대 웹 개발, 특히 컨텐츠 관리 및 전자 상거래 플랫폼에서 중요합니다. 1) PHP는 Laravel 및 Symfony와 같은 풍부한 생태계와 강력한 프레임 워크 지원을 가지고 있습니다. 2) Opcache 및 Nginx를 통해 성능 최적화를 달성 할 수 있습니다. 3) PHP8.0은 성능을 향상시키기 위해 JIT 컴파일러를 소개합니다. 4) 클라우드 네이티브 애플리케이션은 Docker 및 Kubernetes를 통해 배포되어 유연성과 확장 성을 향상시킵니다.

PHP 대 기타 언어 : 비교PHP 대 기타 언어 : 비교Apr 13, 2025 am 12:19 AM

PHP는 특히 빠른 개발 및 동적 컨텐츠를 처리하는 데 웹 개발에 적합하지만 데이터 과학 및 엔터프라이즈 수준의 애플리케이션에는 적합하지 않습니다. Python과 비교할 때 PHP는 웹 개발에 더 많은 장점이 있지만 데이터 과학 분야에서는 Python만큼 좋지 않습니다. Java와 비교할 때 PHP는 엔터프라이즈 레벨 애플리케이션에서 더 나빠지지만 웹 개발에서는 더 유연합니다. JavaScript와 비교할 때 PHP는 백엔드 개발에서 더 간결하지만 프론트 엔드 개발에서는 JavaScript만큼 좋지 않습니다.

PHP vs. Python : 핵심 기능 및 기능PHP vs. Python : 핵심 기능 및 기능Apr 13, 2025 am 12:16 AM

PHP와 Python은 각각 고유 한 장점이 있으며 다양한 시나리오에 적합합니다. 1.PHP는 웹 개발에 적합하며 내장 웹 서버 및 풍부한 기능 라이브러리를 제공합니다. 2. Python은 간결한 구문과 강력한 표준 라이브러리가있는 데이터 과학 및 기계 학습에 적합합니다. 선택할 때 프로젝트 요구 사항에 따라 결정해야합니다.

PHP : 웹 개발의 핵심 언어PHP : 웹 개발의 핵심 언어Apr 13, 2025 am 12:08 AM

PHP는 서버 측에서 널리 사용되는 스크립팅 언어이며 특히 웹 개발에 적합합니다. 1.PHP는 HTML을 포함하고 HTTP 요청 및 응답을 처리 할 수 ​​있으며 다양한 데이터베이스를 지원할 수 있습니다. 2.PHP는 강력한 커뮤니티 지원 및 오픈 소스 리소스를 통해 동적 웹 컨텐츠, 프로세스 양식 데이터, 액세스 데이터베이스 등을 생성하는 데 사용됩니다. 3. PHP는 해석 된 언어이며, 실행 프로세스에는 어휘 분석, 문법 분석, 편집 및 실행이 포함됩니다. 4. PHP는 사용자 등록 시스템과 같은 고급 응용 프로그램을 위해 MySQL과 결합 할 수 있습니다. 5. PHP를 디버깅 할 때 error_reporting () 및 var_dump ()와 같은 함수를 사용할 수 있습니다. 6. 캐싱 메커니즘을 사용하여 PHP 코드를 최적화하고 데이터베이스 쿼리를 최적화하며 내장 기능을 사용하십시오. 7

PHP : 많은 웹 사이트의 기초PHP : 많은 웹 사이트의 기초Apr 13, 2025 am 12:07 AM

PHP가 많은 웹 사이트에서 선호되는 기술 스택 인 이유에는 사용 편의성, 강력한 커뮤니티 지원 및 광범위한 사용이 포함됩니다. 1) 배우고 사용하기 쉽고 초보자에게 적합합니다. 2) 거대한 개발자 커뮤니티와 풍부한 자원이 있습니다. 3) WordPress, Drupal 및 기타 플랫폼에서 널리 사용됩니다. 4) 웹 서버와 밀접하게 통합하여 개발 배포를 단순화합니다.

과대 광고 : 오늘 PHP의 역할을 평가합니다과대 광고 : 오늘 PHP의 역할을 평가합니다Apr 12, 2025 am 12:17 AM

PHP는 현대적인 프로그래밍, 특히 웹 개발 분야에서 강력하고 널리 사용되는 도구로 남아 있습니다. 1) PHP는 사용하기 쉽고 데이터베이스와 완벽하게 통합되며 많은 개발자에게 가장 먼저 선택됩니다. 2) 동적 컨텐츠 생성 및 객체 지향 프로그래밍을 지원하여 웹 사이트를 신속하게 작성하고 유지 관리하는 데 적합합니다. 3) 데이터베이스 쿼리를 캐싱하고 최적화함으로써 PHP의 성능을 향상시킬 수 있으며, 광범위한 커뮤니티와 풍부한 생태계는 오늘날의 기술 스택에 여전히 중요합니다.

PHP의 약한 참고 자료는 무엇이며 언제 유용합니까?PHP의 약한 참고 자료는 무엇이며 언제 유용합니까?Apr 12, 2025 am 12:13 AM

PHP에서는 약한 참조가 약한 회의 클래스를 통해 구현되며 쓰레기 수집가가 물체를 되 찾는 것을 방해하지 않습니다. 약한 참조는 캐싱 시스템 및 이벤트 리스너와 같은 시나리오에 적합합니다. 물체의 생존을 보장 할 수 없으며 쓰레기 수집이 지연 될 수 있음에 주목해야합니다.

PHP의 __invoke 마법 방법을 설명하십시오.PHP의 __invoke 마법 방법을 설명하십시오.Apr 12, 2025 am 12:07 AM

\ _ \ _ 호출 메소드를 사용하면 객체를 함수처럼 호출 할 수 있습니다. 1. 객체를 호출 할 수 있도록 메소드를 호출하는 \ _ \ _ 정의하십시오. 2. $ obj (...) 구문을 사용할 때 PHP는 \ _ \ _ invoke 메소드를 실행합니다. 3. 로깅 및 계산기, 코드 유연성 및 가독성 향상과 같은 시나리오에 적합합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

mPDF

mPDF

mPDF는 UTF-8로 인코딩된 HTML에서 PDF 파일을 생성할 수 있는 PHP 라이브러리입니다. 원저자인 Ian Back은 자신의 웹 사이트에서 "즉시" PDF 파일을 출력하고 다양한 언어를 처리하기 위해 mPDF를 작성했습니다. HTML2FPDF와 같은 원본 스크립트보다 유니코드 글꼴을 사용할 때 속도가 느리고 더 큰 파일을 생성하지만 CSS 스타일 등을 지원하고 많은 개선 사항이 있습니다. RTL(아랍어, 히브리어), CJK(중국어, 일본어, 한국어)를 포함한 거의 모든 언어를 지원합니다. 중첩된 블록 수준 요소(예: P, DIV)를 지원합니다.

WebStorm Mac 버전

WebStorm Mac 버전

유용한 JavaScript 개발 도구

VSCode Windows 64비트 다운로드

VSCode Windows 64비트 다운로드

Microsoft에서 출시한 강력한 무료 IDE 편집기

에디트플러스 중국어 크랙 버전

에디트플러스 중국어 크랙 버전

작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

맨티스BT

맨티스BT

Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.