본문 바로가기

진리는어디에

PHP Extension : Introduction to PHP and Zend

/**
 이 글의 원본(http://devzone.zend.com/node/view/id/1021)은 'Zend Devloper Zone'의 Sara Golemon님에 의해 작성 되었으며, 정확한 해석보다는 적절한 해석을 하고자 노력했습니다. 개인적으로 중간중간 별로 중요하다 싶지 않은 내용에 대해서는 건너 뛰었으니, 원본의 정확한 의미를 알고 싶으신 분들은 위의 링크를 따라 가보시는 것도 좋을 것 같습니다.
*/

Introduction
 이 튜토리얼은 PHP와 C에 대한 기본적인 지식을 가지고 있는 분들을 대상으로 합니다.
 시작하기 전에 여러분이 왜 PHP extension을 사용하려고 하는지 알아볼까요?

  1. PHP로 직접적인 호출 할 수 없는 라이브러리가 있기 때문
  2. PHP를 특이하게 사용하고 싶어서.
  3. PHP 코드의 성능개선 때문에.
  4. PHP 코드의 특정 부분을 감추고 싶어서.

What's an Extension?
 당신이 한번이라도 PHP를 사용해본 경험이 있다면, 당신은 이미 extension을 사용해 보았음이나 다름 없습니다. 몇가지 예외를 제외하고, 거의 모든 PHP의 userspace 함수들은 extension 모듈(module)에 그룹화 되어 들어 있습니다. 이 헤아릴 수 없이 많은 함수들은 '표준 익스텐션(standard extension)'이라고 불리며 총 400개가 넘습니다.

 PHP의 코어(core)는 크게 Zend Engine(ZE)과 PHP core, 두 부분으로 구성되어 있습니다. 하위 레벨에 자리 잡고 있는 ZE는 사람이 읽을 수 있는 스크립트를(human-readable script) 머신이 읽을 수 있는(machine-readable) 토큰으로 분리 하고, 각 토큰들을 프로세스 영역에서 실행하는 것을 담당합니다. 또한 메모리 매니지먼트(memory management), 변수 스코프(variable scope), 함수 호출(dispatching function calls) 등이 모두 ZE의 관리를 받습니다. 다른 절반인 PHP 코어는 SAPI(Server Application Programming Interface, also commonly used to refer to the host environment - Apache, IIS, CLI, CGI, etc) 레이어와의 통신과 바인딩을 담당합니다. 그리고 safe_mode와 open_basedir 체킹을 위한 일관성 있는 컨트롤 레이어(control layer) 뿐만 아니라 파일과 네트워크 I/O관련된 fopen(), fread(), fwrite() 유저스페이스(userspace) 함수를 제공 합니다.

Lifecycles
 SAPI가 시작 할 때, 예를 들자면 '/usr/local/apache/bin/apachectl start' 후, PHP는 각 extension의 코드를 로드하고 Module Initialization 루틴(MINIT)을 호출한다. 이 과정에서 각 extension은 내부 변수 초기화, 자원할당, 리소스 핸들러 등록과 ZE에 함수를 등록을 완료한다. 그래서 만일 스크립트에서 이 함수중 하나를 호출한다면, ZE는 어떤 함수가 실행 되어져야 할지 알 수 있다.

 다음, PHP는 페이지 처리 요청을 하기 위해서 SAPI 레이어를 기다립니다. 처리 요청 메시지가 발생하면 PHP는 ZE에게 스크립트를 실행 시킬 수 있는 환경을 만들도록 합니다. 이 때 PHP는 각 extension 모듈의 Request Initialization (RINIT) 함수를 호출 합니다. RINIT 는 특정 환경 변수를 셋팅하고, 특정 자원 할당, 또는 검사 같은 다른 작업을 수행 하기도 합니다. 예를 들어 session.auto_start 옵션이 켜져 있다면, RINIT 는 자동적으로 session_start() 함수를 호출하고 $_SESSION 변수를 미리 셋팅해 놓을 것입니다.

일단 요청이 초기화 되면, ZE는 PHP 스크립트를 토큰으로 잘게 쪼게고, 결국 opcode까지 쪼개어  opcode를 실행 시킵니다. opcode에서 extension 함수를 호출하면, ZE은 함수에 파라메터를 넘기고, 임시적으로 제어권을 extension 모듈로 넘깁니다.

스크립트의 실행이 완료되고 나면, PHP는 각 extension의 Request Shutdown (RSHUTDOWN) 함수를 호출하여 종료작업을 수행합니다(예를 들자면 세션변수를 디스크에 저장한다던지..). 다름으로 ZE가 모든 변수들에 대한 unset() 함수를 호출하면서 클린업 작업(cleanup process)을 수행 합니다(일반적으로 garbage collection 으로 잘 알려져 있지요).

모든 것이 끝나고 나면, PHP는 다른 요청을 처리하거나, shutdown 시그널을 처리 하기 위해 SAPI레이어를 기다립니다. 만일 shutdown 요청이 오면, PHP 는 각 extension의 Module Shutdown (MSHUTDOWN) 함수를 호출하고, 최종적으로 코어 서브시스템을 종료 시킵니다.

Memory Allocation
ZE는 영속성(persistence)를 표현 하는 플래그를 추가한, ZE만의 내부 메모리 관리 시스템을 가지고 있습니다. 영속적 할당(Persistent allocation) 이라는것은 단일 페이지 처리 요청보다 더 오래 지속되는 할당을 말합니다. 페이지 처리가 끝나도 이 메모리 영역은 없어지지 않습니다. 반대로 비-영속적 할당(Non-persistent allocation)이라는 것은, 페이지 처리 요청이 끝나면 자동적으로 메모리가 해제 되는 할당을 말하는 것입니다. 간단한 예를 들자면 사용자 역역의 변수(userspace variables)를 들 수 있겠습니다.

이론적으로 non-persistent 변수들의 해제가 자동적으로 이루어진다고 하더라도, 이것에만 모든 것을 맡기는 것은 그리 권장되지 않습니다. 할당된 메모리가 오랫동안 회수 되지 않은 채로 남아 있을 수도 있고, 이것이 종료 프로세스에도 영향을 미칠 수가 있습니다.

전동적으로 사용되는 메모리 할당 함수와 PHP/ZE에서 persistent, non-persistent 메모리 할당을 위해 사용되는 함수들을 비교 해보도록 하겠습니다.

Traditional Non-Persistent Persistent
malloc(count)
calloc(count, num)
emalloc(count)
ecalloc(count, num)
pemalloc(count, 1)*
pecalloc(count, num, 1)
strdup(str)
strndup(str, len)
estrdup(str)
estrndup(str, len)
pestrdup(str, 1)
pemalloc() & memcpy()
free(ptr) efree(ptr) pefree(ptr, 1)
realloc(ptr, newsize) erealloc(ptr, newsize) perealloc(ptr, newsize, 1)
malloc(count * num + extr)** safe_emalloc(count, num, extr) safe_pemalloc(count, num, extr)
* pemalloc() 함수군들은 'persistent' 플래그를 포함하고 있습니다. 예를 들어 emalloc(1234) 는  pemalloc(1234, 0)과 같습니다.
** safe_emalloc() 과 (in PHP 5) safe_pemalloc() 는 전수 오버플로우(integer overflows)를 대비하기 위해서 체크하는 작업을 추가로 하고 있습니다.


Settting Up a Build Environment
모든 작업들에 앞서, PHP가 먼저 설치 외어 있어야 합니다. 만일 여러분이 소스로 PHP를 설치하는 것이 어색하시다면 http://www.php.net/install.unix 를 살펴 보실 것을 권합니다. (윈도우에서의 extension개발은 다음장에서 다루도록 하겠습니다). 바이너리 버젼을 사용하는 것이 더 편하기는 하지만, 두 가지의 중요한 도움을 받을 수 없다는 단점이 있습니다. 첫번째는 --enable-debug. 이 옵션은 php를 컴파일 할 시 추가적인 심볼 정보를 이용하여 세그폴트가 발생할 경우 코어 덤프를 얻을 수 있습니다. 다른 옵션은 버젼에 따라 다른데, PHP 4.3에서는 이 옵션의 이름이 --enable-experimental-zts 이고, PHP 5 혹은 그 이후 버젼에서는  --enable-maintainer-zts 입니다. 이 옵션은 여러분이 단일 비-쓰레드 환경에서는 아무렇지 않지만 멀티 쓰레드 환경에서는 치명적일 수가 있는 실수들을 잡아 낼 수 있도록 도와 줍니다.

Hello World
이번 장에서는 전통적인 관례에 따라 "Hello world"라는 문자열을 리턴하는 extension함수를 하나 만들어 보도록 하겠습니다. 먼저 여러분이 PHP만을 이용해 코드를 작성한다면 아래와 같이 작성 할 수 있을 겁니다.

<?php

function hello_world() {
    return 'Hello World';
}

?>

이제 PHP extension으로 변경을 해보도록 하겠습니다. 처음 할 일은 PHP 소스 디렉토리 내부의 ext/ 디렉토리에 'hello'라는 디렉토리를 생성하는 것입니다. 사실 어느곳에 위치하더라도 상관은 없습니다만 일단은 이곳에 만들기를 권하겠습니다. 이제 이 'hello' 디렉토리 아래에 세개의 파일을 만들어야 합니다. hello_world 함수를 가지고 있는 소스 파일인 'hello.c', PHP가 extension을 로드하는데 사용되는 레퍼런스를 기록하고 있는 헤더 파일 'php_hello.h'. 그리고 컴파일 설정을 만들어 내기 위한 설정파일. 이 설정파일은 phpize 에 의해 사용됩니다.

config.m4

PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

php_hello.h

#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_FUNCTION(hello_world);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif

hello.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_hello.h"
static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}

 위에서 보이는 대부분의 코드는 단지 extension을 PHP가 알아 볼 수 있도록 해주는 glue-protocol(다른 언어간에 호환성을 가지게 할 수있도록 도와 주는 프로토콜) 입니다. 우리가 관심을 가지고 보아야 하는 코드는 맨 아래의 네줄입니다. 정확하게는 아니지만 왠지 그 의미는 이해 할 수있을 것 같지 안나요? 간단히 의미를 따라가자면 :

  1. hello_world 라고 함수 이름을 선언 합니다.
  2. 함수는 "Hello World" 라는 문자열을 리턴 합니다.
  3. 흠..'1'? 이게 뭘까요?

ZE가 메모리를 관리 하는 방법을 다시 상기 시켜보자면, ZE는 스크립트 페이지에 대한 처리가 끝나면 자연적으로 변수들을 해제하는 메커니즘을 가지고 있습니다. 일반적으로 메모리를 해제 할 때 주로 하는 실수 중에 한가지가 이미 해제 했던 메모리를 또 한번 더 해제하는 경우가 있습니다. 이런 경우 세그폴트를 내면서 프로그램은 죽게 됩니다. 이것과 비슷하게 동적으로 할당 되지 않은 메모리를 해제 하려는 경우도 있습니다. 위 예제의 "Hello World"는 동적으로 메모리를 할당한 것이 아니지요. 만일 자동으로 변수들의 메모리를 해제하는 메커니즘에 따라 ZE가 "Hello world"역역의 메모리를 해제 하려 들면 오류가 발생하기 때문에 이것을 방지 하기 위해서 두번째 인자를 1(true)로 셋팅하게 되면, RETURN_STRING()으로 넘어가는 문자열은 추후 안전한 삭제를 위해서 복사되어 전달됩니다. 하지만 이미 메모리가 할당 된 후 파라메터로 넘어 오는 것도 종종 발생하지요. 그리고 ZE는 이것에 대해서 구분 할 방법이 없습니다. 이러한 이유로 RETURN_STRING() 은 우리에게 문자열에 대한 복사본이 필요한지 그렇지 않은지에 대한 지정을 할 수 있도록 한 것입니다. 파라메터로 넘어가는 문자열에 대한 복사가 필요하지 않은 경우를 이해하기 위해 아래의 코드를 살펴 보도록 하겠습니다.:

PHP_FUNCTION(hello_world)
{
    char *str;
    str = estrdup("Hello World");
    RETURN_STRING(str, 0);
}

 이 버젼에서 "Hello world"는 동적으로 할당 되어 졌고, RETURN_STRING()함수에 넘겨 질때는 두번째 인자가 0으로 셋팅되어 넘어갔습니다. 이럴경우 ZE는 문자열을 복사하지 않고, 나중에 자동적인 메모리 해제 메커니즘이 돌았을 때도 정상적인 해제 프로세스가 진행 될 수 있는 것입니다.

Building Your Extension
이제 마지막 단계 입니다. 여러분이 위의 세개의 코드를 잘 복사 했다면 아래의 세개의 명령을 ext/hello 디렉토리에서 실행 해보세요 :

$ phpize
$ ./configure --enable-hello
$ make

각 명령들이 정상적으로 수행 되었다면 ext/hello/modules/ 라는 디렉토리 밑에 hello.so라는 파일이 생성 되었을 것입니다. 이 파일을 여러분의 PHP extension 디렉토리로 복사하고(기본적으로 /usr/local/lib/php/extensions/ 로 지정되어 있습니다. php.ini  파일을 확인하세요) 시작시 이 모듈을 로드하기 위해 'php.ini'파일에 extension=hello.so 라는 라인을 추가하세요. 이제 아래의 명령을 실행 해 보세요:

$ php -r 'echo hello_world();'

모든 것이 정상적으로 진행되었다면, "Hello World"라는 문구를 볼 수 있을 겁니다.

이와 비슷하게 생긴 다른 리턴 함수들도 있도 있습니다. 정수를 리턴하기 위해서는 RETURN_LONG()를, 실수를 리턴하기 위해서는 RETURN_DOUBLE()를, true/false의 불리언 변수를 위해서는 RETURN_BOOL()를, NULL을 리턴하기 위해서는 RETURN_NULL()를 사용합니다. 'function_entry' 구조체에 각 액션에 대한 'PHP_FE()' 라인을 추가 하면서 이 기능들을 확인해 보도록 합시다. 그리고 파일 끝에 PHP_FUNCTION()들을 추가해야 하는것도 잊어서는 안되겠지요.

static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    PHP_FE(hello_long, NULL)
    PHP_FE(hello_double, NULL)
    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    {NULL, NULL, NULL}
};
PHP_FUNCTION(hello_long)
{
    RETURN_LONG(42);
}
PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

위의 작업과 함께, 함수들의 원형을 'php_hello.h'에 추가하는 작업을 잊어서는 안되겠습니다. :

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

수정이 끝나고 나면 다시 빌드를 해야 합니다. 실질적으로 'config.m4'에 대한 변경은 아무것도  하지 않았으므로 'phpize'와 './configure' 과정을 생략하고 바로 'make'를 하도록 합니다. 하지만, 정확한 빌드를 위해 처음 부터 차근히 빌드를 다시 시작 할 것을 권하고 싶군요. 참고적으로 'make'에 앞서 'make clean'을 반드시 실행 할 것을 권장합니다. 모든 빌드 과정이 끝나고 나면 새로 생성된 'helllo.so'를 extension 디렉토리로 복사해 새로운 버젼의 모듈로 교채 합니다.

At this point you could call the PHP interpreter again, passing it simple scripts to test out the functions you just added. In fact, why don't you do that now? I'll wait here...

끝났나요? 좋습니다. 결과를 출력하기 위해 'echo' 함수 대신 'var_dump' 함수를 사용한다면 'hello_bool'함수가 'true'를 리턴하는 것을 보실 수 있을 겁니다. 'RETURN_BOOL'에서 1의 의미가 바로 'true'였기 때문이죠. 관례적으로 'true'에는 1을 'false'에는 0을 사용하기는 하지만 그것에 너무 얽메일 필요는 없습니다. 가독성(readability)을 위해서 'RETURN_TRUE'와 'RETURN_FALSE'라는 매크로도 준비되어 있습니다. 다시 한번 'hello_bool'함수로 돌아가서 동일한 의미를 가지는 'RETURN_TRUE'를 사용하는 코드를 살펴 보도록 하겠습니다 :

PHP_FUNCTION(hello_bool)
{
    RETURN_TRUE;
}

이 함수는 이전에 나왔던 'RETURN_STRING()'과는 달리 리턴되는 변수를 복사 해야 하는지 그렇지 않은지를 지정하는 플래그가 없다는 사실에 유의하세요. 동적으로 할당 되는 가능성이 없으니 당연한 현상입니다. 이에 대한 보다 자세한 내용은 Part 2에서 다뤄 보도록 하겠습니다.

리턴 타입은 'mysql_connect()', fsockopen()', 'ftp_connect()'와 같은 함수에 의해 리턴 되는 'RESOURCE', 해쉬로 잘 알려진 'ARRAY', new를 사용해 리턴되는 'OBJECT' 이렇게 세 가지로 분류가 가능합니다. 이에 대한 자세한 사항은 Part II에서 변수를 다룰 때 좀 더 깊게 알아 보도록 하겠습니다.

INI Settings

The Zend Engine provides two approaches for managing INI values. We'll take a look at the simpler approach for now, and explore the fuller, but more complex, approach later on, when you've had a chance to work with global values.

Let's say you want to define a php.ini value for your extension, hello.greeting, which will hold the value used to say hello in your hello_world() function. You'll need to make a few additions to hello.c and php_hello.h while making a few key changes to the hello_module_entry structure. Start off by adding the following prototypes near the userspace function prototypes in php_hello.h:

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

Now head over to hello.c and take out the current version of hello_module_entry, replacing it with the following listing:

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()

PHP_MINIT_FUNCTION(hello)
{
    REGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(hello)
{
    UNREGISTER_INI_ENTRIES();

    return SUCCESS;
}

Now, you just need to add an #include to the rest of the #includes at the top of hello.c to get the right headers for INI file support:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

Finally, you can modify your hello_world function to use the INI value:

PHP_FUNCTION(hello_world)
{
    RETURN_STRING(INI_STR("hello.greeting"), 1);
}

Notice that you're copying the value returned by INI_STR(). This is because, as far as the PHP variable stack is concerned, this is a static string. In fact, if you tried to modify the string returned by this value, the PHP execution environment would become unstable and might even crash.

The first set of changes in this section introduced two methods you'll want to become very familiar with: MINIT, and MSHUTDOWN. As mentioned earlier, these methods are called during the initial startup of the SAPI layer and during its final shutdown, respectively. They are not called between or during requests. In this example you've used them to register the php.ini entries defined in your extension. Later in this series, you'll find how to use the MINIT and MSHUTDOWN functions to register resource, object, and stream handlers as well.

In your hello_world() function you used INI_STR() to retrieve the current value of the hello.greeting entry as a string. A host of other functions exist for retrieving values as longs, doubles, and Booleans as shown in the following table, along with a complementary ORIG counterpart which provides the value of the referenced INI setting as it was set in php.ini (before being altered by .htaccess or ini_set() statements).

Current Value Original Value Type
INI_STR(name) INI_ORIG_STR(name) char * (NULL terminated)
INI_INT(name) INI_ORIG_INT(name) signed long
INI_FLT(name) INI_ORIG_FLT(name) signed double
INI_BOOL(name) INI_ORIG_BOOL(name) zend_bool

The first parameter passed to PHP_INI_ENTRY() is a string containing the name of the entry to be used in php.ini. In order to avoid namespace collisions, you should use the same conventions as with your functions; that is, prefix all values with the name of your extension, as you did with hello.greeting. As a matter of convention, a period is used to separate the extension name from the more descriptive part of the ini setting name.

The second parameter is the initial value, and is always given as a char* string regardless of whether it is a numerical value or not. This is due primarily to the fact that values in an .ini file are inherently textual - being a text file and all. Your use of INI_INT(), INI_FLT(), or INI_BOOL() later in your script will handle type conversions.

The third value you pass is an access mode modifier. This is a bitmask field which determines when and where this INI value should be modifiable. For some, such as register_globals, it simply doesn't make sense to allow the value to be changed from within a script using ini_set() because the setting only has meaning during request startup - before the script has had a chance to run. Others, such as allow_url_fopen, are administrative settings which you don't want to allow users on a shared hosting environment to change, either via ini_set() or through the use of .htaccess directives. A typical value for this parameter might be PHP_INI_ALL, indicating that the value may be changed anywhere. Then there's PHP_INI_SYSTEM|PHP_INI_PERDIR, indicating that the setting may be changed in the php.ini file, or via an Apache directive in a .htaccess file, but not through the use of ini_set(). Or there's PHP_INI_SYSTEM, meaning that the value may only be changed in the php.ini file and nowhere else.

We'll skip the fourth parameter for now and only mention that it allows the use of a callback method to be triggered whenever the ini setting is changed, such as with ini_set(). This allows an extension to perform more precise control over when a setting may be changed, or trigger a related action dependant on the new setting.

Global Values

만일 PHP를 사용하다 특정 변수 혹은 인스턴스를 리퀘스트와는 상관 없이 계속 유지 해야  하는 경우가 있다면 본 장에서 설명하는 global values를 사용하면 된다. PHP는 멀티쓰레드 웹서버에서 구동 되도록 설계 되었기 때문에(Apache 2 and IIS),  전역변수에 대한 쓰레드 세이프를 지켜 줘야 한다.  PHP greatly simplifies this by using the TSRM (Thread Safe Resource Management) abstraction layer, sometimes referred to as ZTS (Zend Thread Safety). In fact, by this point you've already used parts of TSRM and didn't even know it. (Don't search too hard just yet; as this series progresses you'll come to discover it's hiding everywhere.)

The first part of creating a thread safe global is, as with any global, declaring it. For the sake of this example, you'll declare one global value which will start out as a long with a value of 0. Each time the hello_long() function is called you'll increment this value and return it. Add the following block of code to php_hello.h just after the #define PHP_HELLO_H statement:

#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif

You're also going to use the RINIT method this time around, so you need to declare its prototype in the header:

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);

Now let's go over to hello.c and add the following just after your include block:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

ZEND_DECLARE_MODULE_GLOBALS(hello)

Change hello_module_entry by adding PHP_RINIT(hello):

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    PHP_RINIT(hello),
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

And modify your MINIT function, along with the addition of another couple of functions, to handle initialization upon request startup:

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
}

PHP_RINIT_FUNCTION(hello)
{
    HELLO_G(counter) = 0;

    return SUCCESS;
}

PHP_MINIT_FUNCTION(hello)
{
    ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);

    REGISTER_INI_ENTRIES();

    return SUCCESS;
}

Finally, you can modify the hello_long() function to use this value:

PHP_FUNCTION(hello_long)
{
    HELLO_G(counter)++;

    RETURN_LONG(HELLO_G(counter));
}

In your additions to php_hello.h, you used a pair of macros - ZEND_BEGIN_MODULE_GLOBALS() and ZEND_END_MODULE_GLOBALS() - to create a struct named zend_hello_globals containing one variable of type long. You then conditionally defined HELLO_G() to either fetch this value from a thread pool, or just grab it from a global scope - if you're compiling for a non-threaded environment.

In hello.c you used the ZEND_DECLARE_MODULE_GLOBALS() macro to actually instantiate the zend_hello_globals struct either as a true global (if this is a non-thread-safe build), or as a member of this thread's resource pool. As extension authors, this distinction is one we don't need to worry about, as the Zend Engine takes care of the job for us. Finally, in MINIT, you used ZEND_INIT_MODULE_GLOBALS() to allocate a thread safe resource id - don't worry about what that is for now.

You may have noticed that php_hello_init_globals() doesn't actually do anything, yet we went to the trouble of declaring RINIT to initialize the counter to 0. Why?

The key lies in when the two functions are called. php_hello_init_globals() is only called when a new process or thread is started; however, each process can serve more than one request, so using this function to initialize our counter to 0 will only work for the first page request. Subsequent page requests to the same process will still have the old counter value stored here, and hence will not start counting from 0. To initialize the counter to 0 for every single page request, we implemented the RINIT function, which as you learned earlier is called prior to every page request. We included the php_hello_init_globals() function at this point because you'll be using it in a few moments, but also because passing a NULL to ZEND_INIT_MODULE_GLOBALS() for the init function will result in a segfault on non-threaded platforms.

INI Settings as Global Values

If you recall from earlier, a php.ini value declared with PHP_INI_ENTRY() is parsed as a string value and converted, as needed, to other formats with INI_INT(), INI_FLT(), and INI_BOOL(). For some settings, that represents a fair amount of unnecessary work duplication as the value is read over and over again during the course of a script's execution. Fortunately it's possible to instruct ZE to store the INI value in a particular data type, and only perform type conversions when its value is changed. Let's try that out by declaring another INI value, a Boolean this time, indicating whether the counter should increment, or decrement. Begin by changing the MODULE_GLOBALS block in php_hello.h to the following:

ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
    zend_bool direction;
ZEND_ENG_MODULE_GLOBALS(hello)

Next, declare the INI value itself by changing your PHP_INI_BEGIN() block thus:

PHP_INI_BEGIN()
    PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
    STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()

Now initialize the setting in the init_globals method with:

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
    hello_globals->direction = 1;
}

And lastly, use the value of the ini setting in hello_long() to determine whether to increment or decrement:

PHP_FUNCTION(hello_long)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }

    RETURN_LONG(HELLO_G(counter));
}

And that's it. The OnUpdateBool method you specified in the INI_ENTRY section will automatically convert any value provided in php.ini, .htaccess, or within a script via ini_set() to an appropriate TRUE/FALSE value which you can then access directly within a script. The last three parameters of STD_PHP_INI_ENTRY tell PHP which global variable to change, what the structure of our extension globals looks like, and the name of the global scope container where they're contained.

Sanity Check

By now our three files should look similar to the following listings. (A few items have been moved and grouped together, for the sake of readability.)

config.m4

PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])

if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

php_hello.h

#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1

#ifdef ZTS
#include "TSRM.h"
#endif

ZEND_BEGIN_MODULE_GLOBALS(hello)
    long counter;
    zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)

#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif

#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"

PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);

PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);

extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry

#endif

hello.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "php_hello.h"

ZEND_DECLARE_MODULE_GLOBALS(hello)

static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    PHP_FE(hello_long, NULL)
    PHP_FE(hello_double, NULL)
    PHP_FE(hello_bool, NULL)
    PHP_FE(hello_null, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    PHP_MINIT(hello),
    PHP_MSHUTDOWN(hello),
    PHP_RINIT(hello),
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif

PHP_INI_BEGIN()
    PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
    STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()

static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
    hello_globals->direction = 1;
}

PHP_RINIT_FUNCTION(hello)
{
    HELLO_G(counter) = 0;

    return SUCCESS;
}

PHP_MINIT_FUNCTION(hello)
{
    ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);

    REGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(hello)
{
    UNREGISTER_INI_ENTRIES();

    return SUCCESS;
}

PHP_FUNCTION(hello_world)
{
    RETURN_STRING("Hello World", 1);
}

PHP_FUNCTION(hello_long)
{
    if (HELLO_G(direction)) {
        HELLO_G(counter)++;
    } else {
        HELLO_G(counter)--;
    }

    RETURN_LONG(HELLO_G(counter));
}

PHP_FUNCTION(hello_double)
{
    RETURN_DOUBLE(3.1415926535);
}

PHP_FUNCTION(hello_bool)
{
    RETURN_BOOL(1);
}

PHP_FUNCTION(hello_null)
{
    RETURN_NULL();
}

What's Next?

In this tutorial we explored the structure of a simple PHP extension which exported functions, returned values, declared INI settings, and tracked its internal state during the course of a request.

In the next session we'll explore the internal structure of PHP variables, and how they're stored, tracked, and maintained within a script environment. We'll use zend_parse_parameters to receive parameters from a program when a function is called, and explore ways to return more complicated results, including the array, object, and resource types mentioned in this tutorial.

http://blog.naver.com/evonit?Redirect=Log&logNo=80001210173
http://devzone.zend.com/node/view/id/1021

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!