扩展定义详细教程

#PHP 是怎么加载扩展它的扩展的?

如果您自己编译过PHP的扩展就应该知道,PHP扩展一般在类Unix平台上被编译成.so文件,在Windows平台上被编译成dll文件,然后我们把编译得到的二进制的动态链接库文件放到PHP的标准扩展文件夹下面,到这里我们只是完成了一般,我们还得修改php.ini文件,使用语句extension=xxx.so来加载一个名为xxx的扩展,这里的xxx您可以使用您的扩展名称进行替换。完成这些步骤之后,如果您的扩展没有什么问题的话,在PHP启动的时候您的扩展就会被加载了,这个时候您可以使用php -m进行查看。

#关于扩展入口函数 get_module

说到扩展的加载,我们不能不说get_module函数,在PHP读取自己的配置文件之后,获取本次初始化需要动态加载的扩展列表,然后循环加载扩展对应的动态链接库文件,在完成动态连接之后PHP引擎会就会调用模块对应的get_module函数了,在我们写扩展的时候,我们必须在这个函数里面构造出一个zend_module_entry类型的结构体对象,然后将其指针。这个结构的定义非常复杂,大家有兴趣可以阅读PHP的相关源码。

#扩展元信息描述类 zapi::lang::Extension

为了让您的扩展开发工作更加轻松,现在轮到我们的主角上场了类zapi::lang::Extension上场了,zendAPI的设计目标中有一条很重要重要,就是在基于zendAPI开发的扩展的源码中,尽最大可能消灭PHP的宏调用,转而使用zendAPI提供的类型安全的接口。在使用zendAPI开发的扩展都像如下代码来定义扩展的入口:

#include "zapi/ZendApi.h"

using zapi::lang::Extension;

extern "C" {
    ZAPI_DECL_EXPORT void *get_module() 
    {
        static Extension demoExtension("demoext", "1.0");
        return demoExtension;
    }
}

在上面的代码中,您看到了一个完整的扩展入口函数的定义,所有基于zendAPI打造的扩展就会有以以上代码为模板的入口函数定义,在以上代码中,有几点需要指出来进行说明。
首先我们来说说头文件,在基于zendAPI进行扩展开发的时候,您只需要包含zapi/ZendApi.h这一个头文件就可以了,您再也不需要为了使用某个Zend Engine的结构体或者某某函数而去一堆没有任何文档说明的Zend Engine头文件中去寻找对应的头文件了。对于扩展开发需要的头文件,我们已经在zapi/ZendApi为您预先包含,您只要包含跟您自己扩展相关的头文件即可。zendAPI在内部已经帮您处理好复杂的Zend Engine需要的相关数据结构,然后为您提供一个简单明了的编程接口。

接下来我们说说代码块:

extern "C" {
}

大家肯定都知道”世界上最好的语言 PHP”的解释器是使用C语言开发的,而我们的zendAPI库是使用C++语言进行开发了,毕竟是两种语言,细节上的处理还是有些不一样的,特别是两种语言在语言实体的名字管理上面。学习过C++语言的同学都应该知道一个特性就是函数的重载,使用同样的函数名和不一样的参数,这样就可以让函数的调用者使用一个函数名去接受不一样的参数,大大缩减了接口的数量和函数接口的名字长度。这里我们就需要说说C++的里面的一个叫做name mangling的概念。其实在编译器编译完成之后,下一个步骤就是连接,不管是动态连接还是静态连接,在一定程度上都是一样的,其中一项重要的工作就是将程序中的实体的名字与地址进行绑定, 大家要记住这里的所有的名字都是在一个空间里面。所有的名字必须唯一,那么问题就来了,在C++源代码中,我们的函数命名是同样的名字,那不是与这里的规定相互矛盾了,哈哈,然而并没有,这里就是前面所说的name mangling的用武之地了。C++编译器在背后按照一定的规则悄悄的把你的函数名字进行了编码,生成了一个全局唯一的实体名字,从而保证了在连接器连接您的程序的时候看到的名字是全局唯一的。特别注意,这里每种编译器的名字生成算法不统一,所有您的程序再使用不一样的编译器的时候,生成的全局唯一性名字可能并不一样。

但是问题又来了,在C语言里面是没有这个概念的,在C中写的函数的名称是什么样子,他会原封不动的出现在Object文件中。那么我们在C++中定义的函数,怎么才能让C程序进行调用呢?这个就是我们这一节重点extenrn "C"语句块需要解决的问题了。extern "C"指示我们的C++编译器对语句块里面的所有的实体定义暂时关闭name mangling机制,从而保证这个语句块里面的实体名字出现在object文件中的名字就是源码中名字,从而在连接的时候才能顺利的跟C程序进行连接。

因为PHP解释器在加载基于zendAPI开发的扩展的使用,需要调用get_module函数,而这个函数我们是在C++语言环境进行定义的,所有我们在这里需要暂时关闭name mangling,否则的话,您一定会得到一个关于符号不存在的连接错误信息。

咱们先别高兴的太早,了解了上面的概念,您仅仅只完成了一般,您还需要了解关于符号导出的概念,在不同的编译器和不同的操作系统平台中,对符号的导出处理是不一样的,有的是默认导出一个符号,有的却不默认导出,如果一个符号导出,他就像一个局部变量一样,您无法在您的object文件之外进行使用,换句话说连接器根本就不知道有这个符号的存在。所以为了我们的扩展的可移植性,我们在get_module加上一个自定义的宏ZAPI_DECL_EXPORT,这个宏的作用就是强制编译器进行符号导出。这个也是为数不多的几个您在基于zendAPI开发扩展的时候需要了解的宏定义。

接下来我看get_module函数体里面,我们定义了一个zapi::lang::Extension类型的对象,特别注意,这里的 static 关键字,我们需要在PHP解释器运行期间都要保证我们的扩展元信息对象存在,所以我们在这里加入了一个局部静态变量进行实现,这里我们定义了一个名叫demoext的扩展,并且指定他们的版本号为1.0。然后将zapi::lang::Extension进行返回。

等等,细心的同学可能要说了,不对啊,上面不是说get_module要返回一个zend_module_entry结构体指针吗?这里怎么返回zapi::lang::Extension也可以呢?哈哈,这里算是一个zendAPI简化扩展开发的一个点,这里我们利用了C++的自定义运算符的机制,定义了一个向zend_module_entry *转换的转换运算符,自动的将zapi::lang::Extension对象转换成了目标类型,是不是很爽啊。

到这里我们就把zendAPI中如何定义扩展元信息对象需要知识点就讲完了,在zendAPI您将在很多的地方看到这种为了简化操作而定义的语言结构元信息描述类。是不是很简单啊,您根本看不到Zend Engine里面那些丑陋的,没有文档的宏调用了。概念讲完了,在下一节中,我们将详细学习zapi::lang::Extension袁欣欣描述类的一些重要方法的用法。