仿照PHP的实现简单的扩展动态加载
PHP通过扩展机制,可以方便的实现对PHP的动态扩展,在PHP扩展加载过程中简单的分析了PHP扩展的加载过程,下面可以继续根据PHP的相关实现,实现一个自己的简单的支持扩展的程序。
在PHP中,有个非常重要的数据结构zend_module_entry,可以先看一下它的定义:
typedef struct _zend_module_entry zend_module_entry;
struct _zend_module_entry {
unsigned short size;
unsigned int zend_api;
unsigned char zend_debug;
unsigned char zts;
const struct _zend_ini_entry *ini_entry;
const struct _zend_module_dep *deps;
const char *name;
const struct _zend_function_entry *functions;
int (*module_startup_func)(INIT_FUNC_ARGS);
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
int (*request_startup_func)(INIT_FUNC_ARGS);
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
const char *version;
size_t globals_size;
#ifdef ZTS
ts_rsrc_id* globals_id_ptr;
#else
void* globals_ptr;
#endif
void (*globals_ctor)(void *global TSRMLS_DC);
void (*globals_dtor)(void *global TSRMLS_DC);
int (*post_deactivate_func)(void);
int module_started;
unsigned char type;
void *handle;
int module_number;
char *build_id;
};
比较复杂,其中最重要的,也是真正编写扩展中会接触到的,主要包括name,functions,module_startup_func,module_shutdown_func,request_startup_func,request_shutdown_func这几个成员,其中name标明了扩展的名字,functions表明了该扩展拥有的函数,以及4个startup/shutdown相关函数。
在编写扩展时,需要指定一个zend_module_entry,如下:
zend_module_entry counter_module_entry = {
STANDARD_MODULE_HEADER,
"counter",
counter_functions,
PHP_MINIT(counter),
PHP_MSHUTDOWN(counter),
PHP_RINIT(counter), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(counter), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(counter),
"0.1", /* Replace with version number for your extension */
STANDARD_MODULE_PROPERTIES
};
可以看到每个扩展都会有相关的一个zend_module_entry,并在这个entry里指明扩展的相关信息。
在zend_module_entry中,functions是一个_zend_function_entry类型的指针,这是一个函数类型,看看这个类型的定义:
typedef struct _zend_function_entry {
const char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
const struct _zend_arg_info *arg_info;
zend_uint num_args;
zend_uint flags;
} zend_function_entry;
其中fname是函数的名字,handler是指向函数的指针,需要注意的是,INTERNAL_FUNCTION_PARAMETERS是一个宏,展开后是int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC,需要说明的是,所有的PHP扩展函数的参数都是这些,而实现的函数的参数,是通过zend_parse_parameters函数获取的。
有了这两个结构体,我们就可以”山寨”一个自己的动态扩展机制了。
首先,需要定义一个函数结构体,就叫function_entry吧。
typedef struct _function_entry {
const char *fname;
void (*handler)(void);
} function_entry;
这个function_entry比较简单,只有一个名字还有一个函数指针,为了简单起见,暂时所有的扩展函数都是没有参数,同时也没有返回值的吧。
有了函数结构体,就可以定义一下标志扩展的结构体了,就叫ext_entry吧。
typedef struct _ext_entry {
const char *name;
const int func_nums;
const struct _function_entry *functions;
} ext_entry;
同样的非常简单,只有扩展名,扩展所拥有的函数个数,已经保存扩展函数的一个数组指针。
接下来便是实现扩展加载,以及扩展调用的部分了:
#include <dlfcn.h>
#include <string.h>
#include "ext.h"
#define MAX_EXT_NUM 10
ext_entry* exts[MAX_EXT_NUM] = {0};
int ext_num = 0;
int load_ext(const char *path)
{
if (ext_num == MAX_EXT_NUM) {
return -1;
}
void *handle;
ext_entry* entry;
ext_entry* (*get_ext_entry)(void);
handle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
if (!handle) {
return -2;
}
get_ext_entry = (ext_entry *(*)(void)) dlsym(handle, "get_ext_entry");
if (!get_ext_entry) {
dlclose(handle);
return -3;
}
entry = get_ext_entry();
exts[ext_num++] = entry;
return ext_num;
}
int call_ext_func(const char *extname, const char *func)
{
for (int i=0; i<ext_num; i++)
{
if (strcmp(extname, exts[i]->name) == 0) {
for (int j=0; j<exts[i]->func_nums; j++) {
if (strcmp(func, exts[i]->functions[j].fname) == 0) {
exts[i]->functions[j].handler();
return 0;
}
}
}
}
return -1;
}
一共只有两个函数,load_ext以及call_ext_func,分别用来加载一个扩展,以及调用某个扩展的某个函数。需要说明的是,在扩展中,必须实现get_ext_entry函数,这个函数返回一个ext_entry的指针,load_ext函数根据扩展返回的ext_entry指针进行加载。
再看一个示例的扩展
#include <stdio.h>
#include "ext.h"
void print()
{
printf("print in myext");
}
function_entry functions[1] = { {"print", print} };
ext_entry myext_entry = {
"myext",
1,
functions
};
ext_entry* get_ext_entry()
{
return &myext_entry;
}
以及main
#include <stdio.h>
#include "ext.h"
int main(int argc, char *argv[])
{
char path[100] = {0};
char ext[100] = {0};
char func[100] = {0};
scanf("%s", path);
printf("%d\n", load_ext(path));
scanf("%s", ext);
scanf("%s", func);
call_ext_func(ext, func);
return 0;
}
编译的时候,首先编译主程序,再将手动写的扩展编译成so动态库,然后,就可以调用了。
最后调用的结果如下:
$ ./ext_skel
/path/to/myext.so
1
myext
print
print in myext