Skip to content

Commit

Permalink
Update ZendEngine1:ZendAPI:深入PHP内核.md
Browse files Browse the repository at this point in the history
  • Loading branch information
weiChen authored Jun 20, 2016
1 parent a8ba906 commit 6d0eae2
Showing 1 changed file with 143 additions and 2 deletions.
145 changes: 143 additions & 2 deletions ZendEngine1:ZendAPI:深入PHP内核.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,133 @@ Zend能够确定一个块是否在使用,自动释放未使用的块和丢失
### 分析解决问题
===

...
实际上,当编译静态或动态模块时没有太多的故障排除可以做。唯一可能产生的问题是编译器会对未定义的或类似的东西发出警告。
在本例中,确保所有的头文件可用并且命令行中的路径正确。确保所有的东西放在正确的位置,获取一个干净的 PHP 源码树,
使用 `ext` 目录内的干净文件自动构建;这保证了一个安全的编译环境。如果失败,尝试手动编译。

PHP 可能同样编译你的模块中没有的函数。(如果在相同的源码中不修改它们不会发生这种情况。)如果从外部访问你模块的函数是拼写错误的,
它们会在符号表中以 "unlinked symbols" 保存。在被 PHP 动态加载和链接期间,类型错误不会被解析 - 在主二进制中没有相符的符号。
在你的模块文件里查找错误的定义或错误的外部引用。注意,这个问题特别是对动态加载的模块;它不会在动态模块中产生。
静态模块的错误会在编译时显示。

### 根源讨论
===

现在你已经有了一个安全的构建环境并且可以把模块引入到 PHP 文件中,是时候讨论这一切如何工作了。

#### 模块结构

所有的 PHP 模块有共同的结构:

* 头文件包含(引入所有需要的宏,API定义,等。)

* C定义的导出函数(需要定义 Zend 函数块)

* Zend 函数块定义

* Zend 模块块定义

* **get_module()** 实现

* 所有导出函数的实现

#### 头文件包含

你模块真正需要包含的头文件是 `php.h`,在PHP目录内。这个文件引入所有的宏和 API 来构建对代码可用的新模块。

*提示*:为你的模块创建独立的包含模块特殊定义的头文件是个好的实践。这个头文件应该包含所有导出函数前面的定义并引入 `php.h`
如果你用 *ext_skel* 创建模块,你已经有了这样一个准备好的头文件。

#### 定义导出函数

要定义导出函数(作为一个原生函数对 PHP 可用),Zend 提供一套宏。一个简单的定义如下:

```
ZEND_FUNCTION ( my_function );
```

*ZEND_FUNCTION* 定义了一个由 Zend 内部 API 编译的新的C函数。它的意思是函数类型是 *void* 并接收
*INTERNAL_FUNCTION_PARAMETERS(another macro)* 作为参数。另外,它给函数名加 *zif* 前缀。
上面定义的展开版本如下:

```
void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS );
```

展开 *INTERNAL_FUNCTION_PARAMETERS* 结果如下:

```
void zif_my_function( int ht
, zval * return_value
, zval * this_ptr
, int return_value_used
, zend_executor_globals * executor_globals
);
```

由于解释器和执行器核心已经从主要的 PHP 包中分离出来,第二种定义宏和函数设置的API已逐步演化:Zend API。
所以 Zend API 现在处理很少的先前属于PHP的责任,大量的 PHP 函数已经缩减为调用 Zend API 的宏别名。
推荐的实践是任何使用 Zend API 的地方,旧的API只作兼容性维护。例如,zval 和 pval 是相同的。zval 是 Zend 的定义;
pval 是 PHP 的定义(实际上,pval 现在是 zval 的别名)。由于 *INTERNAL_FUNCTION_PARAMETERS* 宏现在是一个 Zend 宏,
上面的定义包含 zval。当写代码时,你应该总是使用 zval 确保是新的 Zend API。

这个定义的参数列表非常重要;你应该记住这些参数:
Zend's Parameters to Functions Called from PHP

| 参数 | 描述
|--- |---
| ht | 传递给 Zend 函数的参数个数。你应该避免直接接触,而用 ZEND_NUM_ARGS() 获取值。
| return_value | 这个变量用来从你的函数返回值给 PHP。访问这个值的最好方式是用预定义宏。具体描述见下面。
| this_ptr | 使用这个变量,你可以访问你函数中含有的对象,如果在对象中使用。使用函数 **getThis()** 来获取这个指针。
| return_value_used | 这个标记标明这个函数最终的返回值是否实际被脚本使用。0表明没有使用;1标明调用者使用了。<br/>这个标记的评估可以用来验证函数的正确使用 以及 在返回值需要很高代价的例子中的速度优化(例如,看 array.c 如何使用它)。
| executor_globals | 这个变量指向 Zend 引擎的全局设置。你在创建新变量时会发现很有用,(更多例子在后面)。<br/>执行器全局变量同样可以用宏 *TSRMLS_FETCH()* 引入到你的函数中。

#### Zend 函数块定义

现在你已经定义了导出函数,你同样需要引入 Zend 中。使用 Zend_function_entry 来引入函数列表。
这个数组连续包含所有的函数使它在外部可用,带有希望出现在PHP中的函数名和在C源码中定义的名。在内部,zend_function_entry 定义如下:

Example #4 Internal declaration of zend_function_entry.

```
typedef struct _zend_function_entry {
char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
unsigned char *func_arg_types;
} zend_function_entry;
```

| 名字 | 描述
|--- |---
| fname | 表示PHP中可见的函数名(如,fopen,mysql_connect,或在我们的例子中,first_module)。
| handler | 指向C函数负责处理函数调用。如,见前面讨论的标准宏 *INTERNAL_FUNCTION_PARAMETERS*
| func_arg_types | 允许你标记指定的参数,这样它们被强制以引用方式传递。你通常应该设为 NULL。

在上面的例子中,定义看起来像这样:

```
zend_function_entry firstmod_functions[] =
{
ZEND_FE(first_module, NULL)
{NULL, NULL, NULL}
};
```

你可以看到数组中最后一个记录是 {NULL, NULL, NULL}。这个标记设置用来让 Zend 知道何时到达导出函数的结尾。

```
Note:
你不能使用预定义宏给结尾的标记,因为它会尝试指向一个 “NULL” 名字的函数!
```


后面雷同,参考前面 1~7 章节.

---

### 调用用户使用的函数 [ Calling User Functions ]
===

你可以在自己的模块中调用用户函数,当实现回调时非常有用;例如,数组回调,检索,或简单的事件驱动程序。

用户函数可以用 `call_user_function_ex()` 来调用。它需要一个你想访问得函数表的哈希值,一个指向对象的指针(如果想使用方法),
Expand Down Expand Up @@ -521,6 +643,7 @@ Return value: 'hello'

### 初始化文件支持
===

PHP4以重新设计的文件支持为特色。现在直接在代码中指定默认的初始化方式已成为可能,在运行时读取和改变这些值,
并且为更改通知创建消息句柄。

Expand Down Expand Up @@ -605,6 +728,7 @@ ZEND_MSHUTDOWN_FUNCTION(mymodule)

### 从这儿去哪儿
===

你已经学了很多关于PHP的。你知道如何创建动态加载的模块并静态链接的扩展。你已经学了PHP和Zend变量的内部存储并能创建和访问这些变量。
你知道相当一套做许多常规任务的工具函数如打印信息字符,自动绑定变量到符号表,等等。

Expand All @@ -614,6 +738,23 @@ ZEND_MSHUTDOWN_FUNCTION(mymodule)

### 参考:一些配置宏
===
**config.m4**

#### config.m4

文件 `config.m4``buildconf` 处理,并且必须包含配置期间所有要执行的命令。例如,它们可以是包含测试的外部文件,如头文件,库,等。
PHP 定义了一套宏用于这个处理,最有用的描述如下。

| 宏 | 描述
|--- |---
| *AC_MSG_CHECKING(message)* | 配置期间打印一个 "checking <message>" 文本。
| *AC_MSG_RESULT(value)* | 把结果给 *AC_MSG_CHECKING*; 应该指定为 yes 或 no。
| *AC_MSG_ERROR(message)* | 配置期间打印消息和错误消息并终止脚本。
| *AC_DEFINE(name, value, description)* | 添加 `#define`*php_config.h* ,以value为值,并含有描述(这对模块的条件式编译很有用)。
| *AC_ADD_INCLUDE(path)* | 添加一个含路径的编译器;例如,如果模块需要添加头文件的检索路径时使用。
| *AC_ADD_LIBRARY_WITH_PATH(libraryname, librarypath)* | 指定链接的一个其它库
| *AC_ARG_WITH(modulename, description, unconditionaltest, conditionaltest)* | 相当强大的宏,添加模块描述到 configure --help 输出。<br/>PHP检测选项 --with-<modulename> 是否传给了配置脚本。如果是,它运行非条件式的脚本(如 --with-myext=yes),<br/>例子中选项值保存在 $withval 中。否则,它执行条件检测。
| *PHP_EXTENSION(modulename, [shared])* | 这个调用PHP配置你的扩展的宏是必须的。你可以应用第二个参数标示是否想要编译为共享模块。<br/>这将在编译时为你的代码产生一个定义 *COMPILE_DL_<modulename>*

---

后面雷同,参考前面第3章节

0 comments on commit 6d0eae2

Please sign in to comment.