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/
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->
.
├── 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