Heim  >  Artikel  >  Backend-Entwicklung  >  [Übersetzung][Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 5 – Ihre erste Erweiterung

[Übersetzung][Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 5 – Ihre erste Erweiterung

黄舟
黄舟Original
2017-02-09 11:31:371205Durchsuche

Ihre erste Erweiterung

Für die Erstellung jeder PHP-Erweiterung sind mindestens zwei Dateien erforderlich: eine Konfigurationsdatei, die der Kompilierungszeit mitteilt, welche Dateien erstellt werden sollen und welche externen Bibliotheken erforderlich sind, und mindestens eine Quelldatei , die die eigentliche Arbeit erledigt.

Profiling-Erweiterungen

Tatsächlich gibt es normalerweise eine zweite oder dritte Konfigurationsdatei und eine oder mehrere Header-Dateien, die Sie benötigen um eine Datei jedes Typs hinzuzufügen und mit ihnen zu arbeiten.

Konfigurationsdateien

Um zu beginnen, erstellen Sie zunächst im ext/-Verzeichnis Ihres PHP-Quellbaums ein Verzeichnis mit dem Namen „sample“. Das Verzeichnis kann überall abgelegt werden, aber um später in diesem Kapitel Win32- und statische Build-Optionen zu demonstrieren, erstellen wir es zuerst im Quellcodeverzeichnis.

Als Nächstes geben Sie dieses Verzeichnis ein und erstellen eine Datei mit dem Namen config .m4 und geben Sie den folgenden Inhalt ein:

PHP_ARG_ENABLE(sample,  
  [Whether to enable the "sample" extension],  
  [  enable-sample        Enable "sample" extension support])  
  
if test $PHP_SAMPLE != "no"; then  
  PHP_SUBST(SAMPLE_SHARED_LIBADD)  
  PHP_NEW_EXTENSION(sample, sample.c, $ext_shared)  
fi

Dies ist die Mindestanforderung, um die Option „enable-sample“ aufrufen zu können, wenn ./configure Der zweite Parameter von PHP_ARG_ENABLE wird bei der erweiterten Konfiguration angezeigt Die Datei wird während der ./configure-Verarbeitung erreicht. Der dritte Parameter wird als Hilfemeldung angezeigt, wenn der Terminalbenutzer ./configure --help ausführt.

Haben Sie sich jemals gefragt, warum es „Einige Erweiterungskonfigurationen verwenden“ gibt? extname, während einige Erweiterungen with-extname verwenden? Es gibt keinen funktionalen Unterschied zwischen den beiden. Tatsächlich bedeutet „enable“, dass für die Aktivierung dieser Funktion keine anderen Bibliotheken von Drittanbietern erforderlich sind Zur Aktivierung dieser Funktion sind weitere Voraussetzungen erforderlich.

Jetzt muss Ihre Beispielerweiterung nicht mit anderen Bibliotheken verknüpft werden, Sie müssen also nur die Aktivierungsversion verwenden. „External Libraries“ werden wir die Verwendung von with einführen und den Compiler anweisen, zusätzliche CFLAGS- und LDFLAGS-Einstellungen zu verwenden.

Wenn der Endbenutzer ./configure mit der Option „enable-sample“ aufruft, wird dies auf die lokale Umgebungsvariable $PHP_SAMPLE angewendet auf „Ja“ gesetzt sein. PHP_SUBST() ist eine von PHP modifizierte Version des Standard-Autoconf-Makros AC_SUBST(), das beim Erstellen von Erweiterungen als gemeinsam genutzte Module erforderlich ist.

Zu guter Letzt definiert PHP_NEW_EXTENSION() das Modul und listet alle Module auf, die als Quelldateien verwendet werden müssen, die als Teil einer Erweiterung kompiliert werden. Wenn mehrere Dateien erforderlich sind, können sie mit Leerzeichen im zweiten Argument aufgezählt werden, zum Beispiel:

PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)

Das letzte Argument entspricht dem Befehl PHP_SUBST(SAMPLE_SHARED_LIBADD) und wird auch beim Erstellen eines gemeinsam genutzten Moduls benötigt.

Header-Datei

Beim Entwickeln in C legen Sie die Datentypdefinition zur Isolierung in eine externe Header-Datei Es ist eine gängige Praxis, es aus der Quelldatei zu isolieren. Obwohl PHP dies nicht erfordert, kann es vereinfacht werden, wenn das Modul aus einer einzelnen Quelldatei

in Ihrer php_sample.h-Headerdatei entsteht , Folgendes Der folgende Inhalt beginnt

#ifndef PHP_SAMPLE_H  
/* 防止重复包含 */  
#define PHP_SAMPLE_H  
  
/* 定义扩展的属性 */  
#define PHP_SAMPLE_EXTNAME    "sample"  
#define PHP_SAMPLE_EXTVER    "1.0"  
  
/* 在php源码树外面构建时引入配置选项 */  
#ifdef HAVE_CONFIG_H  
#include "config.h"  
#endif  
  
/* 包含php的标准头文件 */  
#include "php.h"  
  
/* 定义入口点符号, Zend在加载这个模块的时候使用 */  
extern zend_module_entry sample_module_entry;  
#define phpext_sample_ptr &sample_module_entry  
  
#endif /* PHP_SAMPLE_H */

Diese Header-Datei erledigt zwei Hauptaufgaben: Wenn die Erweiterung mit dem PHPize-Tool erstellt wird (diese Methode wird normalerweise in diesem Buch verwendet), dann ist HAVE_CONFG_H definiert, also config.h wird normalerweise eingebunden, unabhängig davon, wie die Erweiterung kompiliert wird. Diese Header-Datei enthält andere Header-Dateien, die für den Zugriff auf die meisten PHP-APIs im PHP-Quellcode verwendet werden

Als nächstes definieren Sie die von Ihrer Erweiterung verwendete zend_module_entry-Struktur als extern, damit dieses Modul, wenn es mit extension=xxx geladen wird, von Zend mit dlopen und dlsym() abgerufen werden kann.

Übersetzungshinweis: Informationen zum Ladevorgang finden Sie im Blog des Übersetzers

Die Header-Datei enthält auch einige Vorverarbeitungen, die die Informationen definieren, die in der Originaldatei verwendet werden.

Quellcode

Zum Schluss noch das Wichtigste für Sie Benötigt wird sample.c Erstellen Sie ein einfaches Quellcode-Skelett in:

#include "php_sample.h"  
  
zend_module_entry sample_module_entry = {  
#if ZEND_MODULE_API_NO >= 20010901  
     STANDARD_MODULE_HEADER,  
#endif  
    PHP_SAMPLE_EXTNAME,  
    NULL, /* Functions */  
    NULL, /* MINIT */  
    NULL, /* MSHUTDOWN */  
    NULL, /* RINIT */  
    NULL, /* RSHUTDOWN */  
    NULL, /* MINFO */  
#if ZEND_MODULE_API_NO >= 20010901  
    PHP_SAMPLE_EXTVER,  
#endif  
    STANDARD_MODULE_PROPERTIES  
};  
  
#ifdef COMPILE_DL_SAMPLE  
ZEND_GET_MODULE(sample)  
#endif
So einfach ist das. Diese drei Dateien sind alles, was Sie zum Erstellen eines Modulskeletts benötigen. Es hat jedoch keine Funktionalität Wie Sie später in diesem Abschnitt ausfüllen, sind funktionale Vorlagen eine gute Wahl. Aber zuerst sehen wir uns an, was los ist. Die Zeile, die mit

beginnt, ist sehr einfach. Sie enthält die Header-Datei, die Sie gerade erstellt haben der Rest des PHP-Quellbaums.

Als nächstes erstellen Sie die zend_module_entry-Struktur, die Sie in der Header-Datei definiert haben. Beachten Sie, dass das erste Element von zend_module_entry ein bedingter Ausdruck ist, wenn die aktuelle ZEND_MODULE_API_NO-Definition vorliegt . Diese API-Nummer Wahrscheinlich php4.2.0 Wenn Sie sicher sind, dass Ihre Erweiterung nicht auf einer älteren Version als dieser installiert wird, können Sie den Teil #ifdef abschneiden und das Element STANDARD_MODULE_HEADER direkt einfügen Es dauert auf jeden Fall etwas Zeit während der Kompilierung und hat keinen Einfluss auf die resultierende Binärdatei oder die für die Verarbeitung erforderliche Zeit. Daher ist es in den meisten Fällen am besten, diese Bedingung direkt abzuschneiden. Das Gleiche gilt für Folgendes Versionsattribute.

Die anderen 6 Elemente sind jetzt zunächst auf NULL gesetzt. Sie können ihren Zweck in den Kommentaren danach sehen.

Schließlich können Sie unten alle möglichen PHP-Elemente sehen Als gemeinsam genutztes Modul kompiliert. Ein gemeinsames Element, das alle Erweiterungen haben. Diese kurze Bedingung ist eine Referenz, die von Zend hinzugefügt wird, wenn Sie sie dynamisch laden. Machen Sie sich keine Sorgen um die Details, Sie müssen nur sicherstellen, dass sie vorhanden ist, sonst wird möglicherweise der nächste Abschnitt angezeigt funktioniert nicht.

Erstellen Sie Ihre erste Erweiterung

Da Sie nun alle Dateien haben, ist es Zeit zum Kompilieren und Installieren. Die Schritte unterscheiden sich geringfügig vom Kompilieren der Haupt-PHP-Binärdatei.

在*nix上构建

第一步是使用config.m4中的信息作为末班生成./configure脚本. 这可以运行在你安装主php二进制时附带安装的phpize程序来完成:

$ phpize  
PHP Api Version: 20041225  
Zend Module Api No: 20050617  
Zend Extension Api No: 220050617

Zend Extension Api No前面多出来的2并不是印刷错误; 它对应于Zend引擎2这个版本号, 一般认为要保持这个API编号大于它对应的ZE1版本.

如果你此时查看当前目录 你会注意到比刚才的3个文件多了不少文件. phpize程序结合你扩展的config.m4文件以及从你的php构建中收集的信息和所有让编译发生所需的一切. 这意味着你不用纠缠在makefile和定位php头上面. php已经帮你做了这个工作.

下一步就简单了, 执行./configure. 这里你只要配置你的扩展, 因此你需要做的如下:

$ ./configure --enable-sample

注意这里没有使用enable-debug和enable-maintainer-zts. 这是因为phpize已经将它们的值从主php构建中拿过来并应用到你的扩展的./configure脚本中了.

现在, 构建它! 和其他任何的包一样, 你只需要键入make, 生成的脚本文件就会处理剩下的事情.

构建处理完成后, 你会得到一个消息指出sample.so已经编译并放在了当前构建目录下一个名为"modules"的目录中.

在windows上构建

译者不熟悉windows平台, 因此略过.

将构建的扩展作为共享模块加载

在请求的时候为了让php找到这个模块, 需要将它放到php.ini中extension_dir设置的目录下. 默认的php.ini放在/usr/local/lib/php.ini; 不过这个默认值可能会因为包管理系统而不同. 检查php -i的输出可以看到你这个配置文件在哪里.

如果php.ini中的这个设置没有修改过, 它的值默认是"PHP_HOME/lib/php/extensions/debug-zts-20100525", 后面的debug-zts-20100525分别是是否启用调试, 是否启用zts, PHPAPI编号. 如果你还没有已加载的扩展, 或者说除了sample.so没有其他扩展, 可以将这个值修改到你make产生模块的路径. 否则, 直接将产生的sample.so拷贝到这个设置的目录下.(译注: php -i | grep extension_dir查找你的extension_dir设置)

在extension_dir指向正确的位置后, 有两种方式告诉php去加载你的模块. 第一种是在脚本中使用dl()函数:

<?php  
    dl(&#39;sample.so&#39;);  
    var_dump(get_loaded_modules());  
?>

如果脚本没有显示sample已加载, 则表示有哪里出了问题. 查看输出上面的错误消息作为线索, 或者如果在php.ini中进行了相应的设置就参考error_log.

第二种方式, 也是更常用的方式, 在php.ini中使用extension指令指定要加载的模块. 这个指令在php.ini的设置中是比较特殊的, 可以多次以不同的值使用它. 因此如果你已经在php.ini中设置了一个扩展, 不要在同一行使用分隔符的方式列举, 而是插入新的一行: extension=sample.so. 此时你的php.ini看起来是这样的:

extension_dir=/usr/local/lib/php/modules/  
extension=sample.so

现在你可以不使用dl()运行相同的脚本, 或者直接执行php -m命令, 就可以在已加载模块列表中看到sample了.

所有本章剩余以及以后章节的代码, 都假设你已经按照这里描述的方法加载了当前扩展. 如果你计划使用dl(), 请确认在测试脚本中加入加载的代码(dl()).

静态构建

在已加载模块列表中, 你可能注意到了一些在php.ini中并没有使用extension指令包含的模块. 这些模块是直接构建到php中的, 它们作为主php程序的一部分被编译进php中.

在*nix下静态构建

现在, 如果你现在进入到php源码树的根目录, 运行./configure --help, 你会看到虽然你的扩展和所有其他模块都在ext/目录下, 但它并没有作为一个选项列出. 这是因为, 此刻, ./configure脚本已经生成了, 而你的扩展并不知道. 要重新生成./configure并让它找到你的新扩展, 你需要做的只是执行一条命令:

$ ./buildconf

如果你使用产品发布版的php做开发, 你会发现./buildconf自己不能工作. 这种情况下, 你需要执行: ./buildconf --force来绕过对./configure命令的一些保护.

现在你执行./configure --help就可以看到--enable-sample是一个可用选项了. 此时, 你就可以重新执行./configure, 使用你原来构建主php时使用的所有选项, 外加--enable-sample, 这样构建出来的php二进制文件就是完整的, 包含你自己扩展的程序.

当然, 这样做还有点早. 你的扩展除了占用空间还应该做一些事情.

windows下静态构建

译者不熟悉windows环境, 因此略过.

功能函数

在用户空间和扩展代码之间最快捷的链接就是PHP_FUNCTION(). 首先在你的sample.c文件顶部, #include "php_sample.h"之后增加下面代码:

PHP_FUNCTION(sample_hello_world)  
{  
    php_printf("Hello World!\n");  
}

PHP_FUNCTION()宏函数就像一个普通的C函数定义, 因为它按照下面方式展开:

#define PHP_FUNCTION(name) \  
    void zif_##name(INTERNAL_FUNCTION_PARAMETERS)

这种情况下, 它就等价于:

void zif_sample_hello_world(zval *return_value,  
    char return_value_used, zval *this_ptr TSRMLS_DC)

当然, 只定义函数还不够. 引擎需要知道函数的地址以及应该暴露给用户空间的函数名. 这通过下一个代码块完成, 你需要在PHP_FUNCTION()块后面增加:

static function_entry php_sample_functions[] = {  
    PHP_FE(sample_hello_world,        NULL)  
    { NULL, NULL, NULL }  
};

php_sample_functions向量是一个简单的NULL结束向量, 它会随着你向扩展中增加功能而变大. 每个你要暴露出去的函数都需要在这个向量中给出说明. 展开PHP_FE()宏如下:

{ "sample_hello_world", zif_sample_hello_world, NULL},

这样提供了新函数的名字, 以及指向它的实现函数的指针. 这里第三个参数用于提供暗示信息, 比如某些参数需要引用传值. 在第7章"接受参数"你将看到这个特性.

现在, 你有了一个要暴露的函数列表, 但是仍然没有连接到引擎. 这通过对sample.c的最后一个修改完成, 将你的sample_module_entry结构体中的NULL, /* Functions */一行用php_sample_functions替换(请确保留下那个逗号).

现在, 通过前面介绍的方式重新构建, 使用php命令行的-r选项进行测试, -r允许你不用创建文件直接在命令行运行简单的代码片段:

$ php -r &#39;sample_hello_world();

如果一切OK的话, 你将会看到输出"Hello World!". 恭喜!!!

Zend内部函数

内部函数名前缀"zif_"是"Zend内部函数"的命名标准, 它用来避免可能的符号冲突. 比如, 用户空间的strlen()函数并没有实现为void strlen(INTERNAL_FUNCTION_PARAMETERS), 因为它会和C库的strlen冲突.

zif_的前缀也并不能完全避免名字冲突的问题. 因此, php提供了可以使用任意名字定义内部函数的宏: PHP_NAMED_FUNCTION(); 例如PHP_NAMED_FUNCTION(zif_sample_hello_world)等同于前面使用的PHP_FUNCTION(sample_hello_world)

当使用PHP_NAMED_FUNCTION定义实现时, 在function_entry向量中, 可以对应使用PHP_NAMED_FE()宏. 因此, 如果你定义了自己的函数PHP_NAMED_FUNCTION(purplefunc), 就要使用PHP_NAMED_FE(sample_hello_world, purplefunc, NULL), 而不是使用PHP_FE(sample_hello_world, NULL).

我们可以在ext/standard/file.c中查看fopen()函数的实现, 它实际上使用PHP_NAMED_FUNCTION(php_if_fopen)定义.从用户空间角度来看, 它并不关心函数是什么东西, 只是简单的调用fopen(). 

函数别名

有时一个函数可能会有不止一个名字. 回想一下, 普通的函数内部定义是用户空间函数名加上zif_前缀, 我们可以看到用PHP_NAMED_FE()宏可以很容易的创建这个可选映射.

PHP_FE(sample_hello_world,    NULL)  
PHP_NAMED_FE(sample_hi,    zif_sample_hello_world,     NULL)

PHP_FE()宏将用户空间函数名sample_hello_world和PHP_FUNCTION(sample_hello_world)展开而来的zif_sample_hello_world关联起来. PHP_NAMED_FE()宏则将用户空间函数名sample_hi和同一个内部实现关联起来.

现在, 假设Zend引擎发生了一个大的变更, 内部函数的前缀从zif_修改为pif_了. 这个时候, 你的扩展就不能工作了, 因为当到达PHP_NAMED_FE()时, 发现zif_sample_hello_world没有定义.

这种情况并不常见, 但非常麻烦, 可以使用PHP_FNAME()宏展开sample_hello_world避免这个问题:

PHP_NAMED_FE(sample_hi, PHP_FNAME(sample_hello_world), NULL)

这种情况下, 即便函数前缀被修改, 扩展的zend_function_entry也会使用宏扩展自动的更新.

现在, 你这样做已经可以工作了, 但是我们已经不需要这样做了. php暴露了另外一个宏, 专门设计用于创建函数别名. 前面的例子可以如下重写:

PHP_FALIAS(sample_hi, sample_hello_world, NULL)

实际上这是官方的创建函数别名的方法, 在php源码树中你时常会看到它.

小结

本章你创建了一个简单的可工作的php扩展, 并学习了在主要平台上构建它需要的步骤. 在以后的章节中, 你将会继续丰满这个扩展, 最终让它包含php的所有特性.

php源码树和它编译/构建在各个平台上依赖的工具经常会发生变化, 如果本章介绍的某些地方不能正常工作, 请参考php.net在线手册的Installation一掌, 查看你使用的版本的特殊需求.

以上就是 [翻译][php扩展开发和嵌入式]第5章-您的第一个扩展的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn