Libuv 加载库

libuv提供了一个跨平台的API来加载共享库shared libraries。这就可以用来实现你自己的插件/扩展/模块系统,它们可以被nodejs通过require()调用。只要你的库输出的是正确的符号,用起来还是很简单的。在载入第三方库的时候,要注意错误和安全检查,否则你的程序就会表现出不可预测的行为。下面这个例子实现了一个简单的插件,它只是打印出了自己的名字。

首先看下提供给插件作者的接口。

plugin/plugin.h

#ifndef UVBOOK_PLUGIN_SYSTEM
#define UVBOOK_PLUGIN_SYSTEM

// Plugin authors should use this to register their plugins with mfp.
void mfp_register(const char *name);

#endif

你可以在你的程序中给插件添加更多有用的功能(mfp is My Fancy Plugin)。使用了这个api的插件的例子:

plugin/hello.c

#include "plugin.h"

void initialize() {
    mfp_register("Hello World!");
}

我们的接口定义了,所有的插件都应该有一个能被程序调用的initialize函数。这个插件被编译成了共享库,因此可以被我们的程序在运行的时候载入。

$ ./plugin libhello.dylib
Loading libhello.dylib
Registered plugin "Hello World!"

共享库的后缀名在不同平台上是不一样的。在Linux上是libhello.so。

使用uv_dlopen首先载入了共享库libhello.dylib。再使用uv_dlsym获取了该插件的initialize函数,最后在调用它。

plugin/main.c

#include "plugin.h"

typedef void (*init_plugin_function)();

void mfp_register(const char *name) {
    fprintf(stderr, "Registered plugin \"%s\"\n", name);
}

int main(int argc, char **argv) {
    if (argc == 1) {
        fprintf(stderr, "Usage: %s [plugin1] [plugin2] ...\n", argv[0]);
        return 0;
    }

    uv_lib_t *lib = (uv_lib_t*) malloc(sizeof(uv_lib_t));
    while (--argc) {
        fprintf(stderr, "Loading %s\n", argv[argc]);
        if (uv_dlopen(argv[argc], lib)) {
            fprintf(stderr, "Error: %s\n", uv_dlerror(lib));
            continue;
        }

        init_plugin_function init_plugin;
        if (uv_dlsym(lib, "initialize", (void **) &init_plugin)) {
            fprintf(stderr, "dlsym error: %s\n", uv_dlerror(lib));
            continue;
        }

        init_plugin();
    }

    return 0;
}

函数uv_dlopen需要传入一个共享库的路径作为参数。当它成功时返回0,出错时返回-1。使用uv_dlerror可以获取出错的消息。

uv_dlsym的第三个参数保存了一个指向第二个参数所保存的函数的指针。init_plugin_function是一个函数的指针,它指向了我们所需要的程序插件的函数。