vlc 插件加载模拟程序

vlc 插件加载模拟程序
前言:
网上对VLC 的插件加载理论性质的比较多,或是抄来抄去,为了让大家较为深刻的理解其中的原理,这就是写本文的目的。
插件动态加载是一个非常优秀的一种程序架构,各个模块的偶合性较低,很符合我们软件模块独立性思想,可以提高软件项目的开发进度,在所有功能模块都没有完善的情况下,能快速推出市场,后续可以以插件的方式升级,维护也相对较为简单。VLC 就是这种架构,虽然是C语言,但面向对象思想无处不在。下面我会用C++语言来模拟VLC的插件加载过程。

环境:
Linux EastonWoo 3.13-1-amd64 #1 SMP Debian 3.13.10-1 (2014-04-15) x86_64 GNU/Linux
系统:64位Debian 系统。
工具链: gcc version 4.8.2 (Debian 4.8.2-21)
VLC版本:2.1.4

程序架构:
主程序:main.cpp
插件目录:modules (demux/demux_plugin.cpp  mux/mux_plugin.cpp
头文件:include (vlc_plugin.h插件接口文件)
编译规则:Makfile

vlc_plugin_load_simulate-> ls
include/  main.cpp  Makefile  modules/


vlc_plugin_load_simulate-> tree
.
├── include
│   └── vlc_plugin.h
├── main.cpp
├── Makefile
└── modules
    ├── demux
    │   └── demux_plugin.cpp
    ├── main_plugin.cpp
    ├── main_plugin.h
    └── mux
        └── mux_plugin.cpp


4 directories, 7 files
vlc_plugin_load_simulate-> 


程序分析:
1) Makefile 
vlc_plugin_load_simulate-> cat Makefile 

CPP:=g++
CPPFLAGS:=-g -std=c++0x

src_dirs := .
src_dirs += modules
src_dirs += modules/mux
src_dirs += modules/demux

SRC := $(foreach dir,$(src_dirs),$(wildcard $(dir)/*.cpp))

OBJ := $(filter-out ./main.o, $(SRC:%.cpp=%.o))

TMP := $(foreach f, $(OBJ), $(dir $(f))lib$(notdir $(f)))
LIB := $(TMP:%.o=%.so)


all: $(LIB) main.o
	$(CPP) $(CPPFLAGS) -ldl -L./modules -lmain_plugin -o main.elf  main.o  # -ldl 是dlopen ,dlsym等库
	LD_LIBRARY_PATH=./modules ./main.elf  # 为了方便,运行程序也放在这里了。

# 茎是 modules/demux/lib.so  % 被传递,把移开后的路径加回来。
lib%.so:%.o
	$(CPP) $(CPPFLAGS) -shared -fPIC -o $@ $<	

%.o:%.cpp
	$(CPP) $(CPPFLAGS) -fPIC -o $@ -c $<

clean:
	find . -name *.o -exec rm -rf {} \;
	find . -name *.so -exec rm -rf {} \;
	find . -name *.elf -exec rm -rf {} \;


2) main.cpp
vlc_plugin_load_simulate-> cat main.cpp 
/// @file main.cpp
/// @brief 
/// @author EastonWoo <31417071@qq.com>
/// 0.01
/// @date 2014-07-25

#include <stdio.h>
#include <list>
#include <dlfcn.h>
#include <string>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>


#include "include/vlc_plugin.h"
#include "modules/main_plugin.h"

typedef int (*FUNC)(vlc_set_cb , void *);
typedef std::list <void*> L;

L lHandle;

int AddPluginAndTest(char *file);



/**
 * Opens a DIR pointer.
 *
 * @param dirname UTF-8 representation of the directory name
 * @return a pointer to the DIR struct, or NULL in case of error.
 * Release with standard closedir().
 */
DIR *vlc_opendir (const char *dirname)
{
    return opendir (dirname);
}

/**
 * Reads the next file name from an open directory.
 *
 * @param dir The directory that is being read
 *
 * @return a UTF-8 string of the directory entry. Use free() to release it.
 * If there are no more entries in the directory, NULL is returned.
 * If an error occurs, errno is set and NULL is returned.
 */
char *vlc_readdir( DIR *dir )
{
    /* Beware that readdir_r() assumes <buf> is large enough to hold the result
     * dirent including the file name. A buffer overflow could occur otherwise.
     * In particular, pathconf() and _POSIX_NAME_MAX cannot be used here. */
    struct dirent *ent;
    char *path = NULL;

    long len = fpathconf (dirfd (dir), _PC_NAME_MAX);
    /* POSIX says there shall we room for NAME_MAX bytes at all times */
    if (len == -1 || len < NAME_MAX)
        len = NAME_MAX;
    len += sizeof (*ent) + 1 - sizeof (ent->d_name);

    struct dirent *buf = (struct dirent *)malloc (len);
    if (buf == NULL)
        return NULL;

    int val = readdir_r (dir, buf, &ent);
    if (val != 0)
        errno = val;
    else if (ent != NULL)
        path = strdup (ent->d_name);
    free (buf);
    return path;
}

/**
 * Finds file/inode information, as stat().
 * Consider using fstat() instead, if possible.
 *
 * @param filename UTF-8 file path
 */
int vlc_stat (const char *filename, struct stat *buf)
{
    return stat (filename, buf);
}

/**
 * Finds file/inode information, as lstat().
 * Consider using fstat() instead, if possible.
 *
 * @param filename UTF-8 file path
 */
int vlc_lstat (const char *filename, struct stat *buf)
{
    return lstat (filename, buf);
}
 
















// 部分参数去掉了。
static void AllocatePluginDir (const char* base_path, const char *absdir, const char *reldir) 
{
    DIR *dh = vlc_opendir (absdir);
    if (dh == NULL)
        return;

    /* Parse the directory and try to load all files it contains. */
    for (;;)
    {
        char *file = vlc_readdir (dh), *relpath = NULL, *abspath = NULL;
        if (file == NULL)
            break;

        /* Skip ".", ".." */
        if (!strcmp (file, ".") || !strcmp (file, ".."))
            goto skip;

        /* Compute path relative to plug-in base directory */
        if (reldir != NULL)
        {
            if (asprintf (&relpath, "%s""/""%s", reldir, file) == -1)
                relpath = NULL;
        }
        else
            relpath = strdup (file);
        if (relpath == NULL)
            goto skip;

        /* Compute absolute path */
        if (asprintf (&abspath, "%s""/""%s", base_path, relpath) == -1)
        {
            abspath = NULL;
            goto skip;
        }

        struct stat st;
        if (vlc_stat (abspath, &st) == -1)
            goto skip;

        if (S_ISREG (st.st_mode))
        {
            static const char prefix[] = "lib";
            static const char suffix[] = "_plugin.so";
            size_t len = strlen (file);

#ifndef __OS2__
            /* Check that file matches the "lib*_plugin"LIBEXT pattern */
            if (len > strlen (suffix)
             && !strncmp (file, prefix, strlen (prefix))
             && !strcmp (file + len - strlen (suffix), suffix))
#else
            /* We load all the files ending with LIBEXT on OS/2,
             * because OS/2 has a 8.3 length limitation for DLL name */
                // 文件: .so结尾
            if (len > strlen (LIBEXT)
             && !strcasecmp (file + len - strlen (LIBEXT), LIBEXT))
#endif
                // AllocatePluginFile (bank, abspath, relpath, &st);
                AddPluginAndTest(abspath);
        }
        else if (S_ISDIR (st.st_mode))
            /* Recurse into another directory */
            // 目录:递归 /work/source/vlc/vlc-2.1.4/modules/audio_filter/
            AllocatePluginDir (base_path, abspath, relpath);
    skip:
        free (relpath);
        free (abspath);
        free (file);
    }
    closedir (dh);
}

int handle_plugin(void* opaque, void* plugin_func)
{
    if(plugin_func)
    {
        // 这里是模拟版本,正式版应加入Index来说是这个plugin_func是做什么的。
        typedef int (*PLU_FUNC_CB)();
        ((PLU_FUNC_CB)plugin_func)();
    }
    else 
    {
        printf("---->> vlc_set (opaque, NULL); 测试\n");
    }
    return 0;
}

// 找到插件, 加入到进程。
int AddPluginAndTest(char *file)
{
    //test
    // 不加载main_plugin
    std::string not_main(file);
    if(std::string::npos != not_main.find("main_plugin"))
    {
        printf(">>>>>>>>>>> %s <<<<<<<<<<<\n\n", file);
        return 0;
    }
    //test


    void* handle = NULL;
    if((handle = dlopen(file,RTLD_LAZY)) != NULL)  
    {  
        FUNC print_call = (FUNC)dlsym(handle, "vlc_entry__plugin");  
        if(!print_call) {printf("%s\n",dlerror());return -1;}
        printf("########### %s ###########\n", file);
        (*print_call)(handle_plugin, NULL);  
        printf("#########################################################\n\n");

        lHandle.push_back(handle); 
    }

    return 0;
}

int main(int argc, const char *argv[])
{
    char* path = main_plugin(); 
    if(path)
        printf("path = %s\n", path);
    
    // 递归遍历存放插件的目录./modules
    // AllocatePluginDir("./modules", "./modules", NULL);
    AllocatePluginDir(path, path, NULL);

    for(auto ip = lHandle.begin(); ip != lHandle.end(); ++ip)
    {
        dlclose(*ip);
    }
    lHandle.clear();
      
    return 0;
}

3) modules/main_plugin.cpp
vlc_plugin_load_simulate-> cat modules/main_plugin.cpp 
/// @file main_plugin.cpp
/// @brief 
/// @author EastonWoo <31417071@qq.com>
/// 0.01
/// @date 2014-07-25

//
// VLC中主程序都是插件模式加载的, 但加载的方式不一样,隐式加载动态库,这里只作一下说明。
//

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


char* main_plugin()
{
    char buf[10240] = {0};
    char *path = NULL;

    // /proc/self 是一个软连接,指向本进程的进程ID文件夹里,里面包含进程的运行信息
    /* Find the path to libvlc (i.e. ourselves) */
    FILE *maps = fopen ("/proc/self/maps", "rt"); 
    if (maps == NULL)
        return NULL;

    char *line = NULL;
    size_t linelen = 0;
    uintptr_t needle = (uintptr_t)main_plugin;

    fread(buf, 1, sizeof(buf), maps);
    printf("%s\n", buf);
    fseek(maps, 0, SEEK_SET);

    for (;;)
    {
        ssize_t len = getline (&line, &linelen, maps);
        if (len == -1)
            break;

        void *start, *end;
        if (sscanf (line, "%p-%p", &start, &end) < 2)
            continue;

        // 找到本函数在运行程序中的地址,此地址是否在动态库中, 找出后就可以知道动态库路径了。
        /* This mapping contains the address of this function. */
        if (needle < (uintptr_t)start || (uintptr_t)end <= needle)
            continue;
        printf("start  = %p\n", start);
        printf("needle = %p\n", needle);
        printf("end    = %p\n", end);

        char *dir = strchr (line, '/');
        if (dir == NULL)
            continue;

        char *file = strrchr (line, '/');
        if (end == NULL)
            continue;
        *file = '\0';

        if (asprintf (&path, "%s/", dir) == -1)
            path = NULL;
        break;
    }

    free (line);
    fclose (maps);
    return path;
}

4) modules/main_plugin.h
vlc_plugin_load_simulate-> cat modules/main_plugin.h
/// @file main_plugin.h
/// @brief 
/// @author EastonWoo <31417071@qq.com>
/// 0.01
/// @date 2014-07-25

#pragma once

char* main_plugin();

5) modules/mux/mux_plugin.cpp
vlc_plugin_load_simulate-> cat modules/mux/mux_plugin.cpp 
/// @file mux_plugin.cpp
/// @brief 
/// @author EastonWoo <31417071@qq.com>
/// 0.01
/// @date 2014-07-25

//显式加载动态库的
#include <stdio.h>

#include "../../include/vlc_plugin.h"

#define MODULE_NAME "demux"

static int  Open   ( );
static int Close  ( );

vlc_module_begin ()
    // set_callbacks( Open, Close )
    // 这里只作演示, 回调Open,Close实际应写到vlc_plugin.h里,set_callbacks( Open, Close ), 越简单越好
    if(vlc_set (opaque, (void*)Open))    
        goto error; 
    if(vlc_set (opaque, (void*)Close)) 
        goto error;

vlc_module_end ()

static int Open( )
{
    printf("mux: Open:可以控制Mux插件里的Open函数了\n");
    return 0;
}

static int Close( )
{
    printf("mux: Close:可以控制Mux插件里的Close函数了\n");
    return 0;
}

6) demux/demux_plugin.cpp
vlc_plugin_load_simulate-> cat modules/demux/demux_plugin.cpp 
/// @file demux_plugin.cpp
/// @brief 
/// @author EastonWoo <31417071@qq.com>
/// 0.01
/// @date 2014-07-25

//显式加载动态库的
#include <stdio.h>

#include "../../include/vlc_plugin.h"

#define MODULE_NAME "demux"

static int  Open   ( );
static int Close  ( );

vlc_module_begin ()
    // set_callbacks( Open, Close )
    // 这里只作演示, 回调Open,Close实际应写到vlc_plugin.h里,set_callbacks( Open, Close ), 越简单越好
    if(vlc_set (opaque, (void*)Open))    
        goto error; 
    if(vlc_set (opaque, (void*)Close)) 
        goto error;

vlc_module_end ()

static int Open( )
{
    printf("demux: Open:可以控制deMux插件里的Open函数了\n");
    return 0;
}

static int Close( )
{
    printf("demux: Close:可以控制deMux插件里的Close函数了\n");
    return 0;
}

7) include/vlc_plugin.h
vlc_plugin_load_simulate-> cat include/vlc_plugin.h 
/// @file vlc_plugin.h
/// @brief 
/// @author EastonWoo <31417071@qq.com>
/// 0.01
/// @date 2014-07-25

#pragma once 

#ifdef __cplusplus
extern "C"{
#endif


// typedef int (*vlc_set_cb) (void *, ...);
typedef int (*vlc_set_cb) (void *, void*);  // 暂时固定。

int vlc_entry__plugin (vlc_set_cb, void*);     // 头文件加入声明,使用C 编译器编译

// 由于是模拟程序,部分宏定义我已经翻译过来。
#define vlc_module_begin() \
int vlc_entry__plugin (vlc_set_cb, void*); \
int vlc_entry__plugin(vlc_set_cb vlc_set, void *opaque) \
{ \
    if(vlc_set (opaque, NULL)) \
        goto error; 

#define vlc_module_end() \
    return 0; \
error: \
    return -1; \
} 


#ifdef __cplusplus
}
#endif



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值