>  기사  >  백엔드 개발  >  [번역] [php 확장 개발 및 임베딩] 20장 - PHP의 고급 임베딩

[번역] [php 확장 개발 및 임베딩] 20장 - PHP의 고급 임베딩

黄舟
黄舟원래의
2017-02-10 10:43:041919검색


고급 임베딩

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()的机制(可以参考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 &#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으로 문의하세요.