在功能开发过程中,我们编译功能库如果需要用到外部库或者其他三方库,最直接的方法就是在编译库的时候链接这个外部库,然后在加载功能SO时就会自动去链接这个外部库。这种方式大部分时候没有问题,但是如果所链接的外部库和硬件相关,比如是一个专用于高通平台的SO,如果在MTK平台使用,链接就会报错。那么怎么解决这个问题呢?
解决这个问题必须解决2个问题,一个是使用的时候动态地去链接外部库,而不是通过功能库去链接(外部库不能是功能库的needed so);二是保证编译的时候能够获取到所需外部库的接口,否则编译的时候就会报错。最开始想到解决第一点就是用dlopen函数,但是解决不了第二点,偶然在看mace代码的时候看到其加载opencl库的方法,正好解决了这个问题,今天记录下来.
https://github.com/XiaoMi/mace/blob/master/mace/core/runtime/opencl/opencl_wrapper.ccgithub.com一、首先简单介绍一下这几个函数:
#include <dlfcn.h>
void *dlopen(const char *filename, int flag);//打开库,返回动态库句柄
char *dlerror(void);//返回出现的错误
void *dlsym(void *handle, const char *symbol);//取函数
int dlclose(void *handle);//关闭库
dlopen打开一个动态链接库,并返回动态链接库的句柄。filename是SO加载路径,如果 filename 包含斜杠(“/”),则它被解释为(相对或绝对)路径名。否则,动态链接器将按正常SO路径搜索的方式寻找。
flags 参数必须包括以下两个值中的一个:
- RTLD_LAZY:执行延迟绑定。只在有需要的时候解析符号
- RTLD_NOW:dlopen()返回之前,将解析共享对象中的所有未定义符号。
flags 也可以通过以下零或多个值进行或运算设置:
- RTLD_GLOBAL:此共享对象定义的符号将可用于后续加载的共享对象的符号解析。
- RTLD_LOCAL:这与RTLD_GLOBAL相反,如果未指定任何标志,则为默认值。此共享对象中定义的符号不可用于解析后续加载的共享对象中的引用
当库被装入后,可以把 dlopen() 返回的句柄作为给dlsym()的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。
二、实现步骤
1.dlopen()加载指定路径的SO
//加载指定路径的SO,成功返回库的句柄给调用进程,如果失败返回nullptr
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (handle == nullptr) {
VLOG(2) << "Failed to load OpenCL library from path " << path
<< " error code: " << dlerror();
return nullptr;
}
2.dlsym()取函数
假如clGetPlatformIDs函数就是外部库中我需要调用的接口之一,通过
void *ptr = dlsym(handle, “clGetPlatformIDs”);返回这个函数对应的函数指针
clGetPlatformIDs= reinterpret_cast<clGetPlatformIDsFunc >(ptr);//强制转换成该函数指针
前面已经声明和定义了该函数指针:
using clGetPlatformIDsFunc = cl_int (*)(cl_uint, cl_platform_id *, cl_uint *);
clGetPlatformIDsFunc clGetPlatformIDs= nullptr;
#define MACE_CL_ASSIGN_FROM_DLSYM(func)
do {
void *ptr = dlsym(handle, #func);
if (ptr == nullptr) {
VLOG(1) << "Failed to load " << #func << " from " << path;
continue;
}
func = reinterpret_cast<func##Func>(ptr);
VLOG(2) << "Loaded " << #func << " from " << path;
} while (false)
MACE_CL_ASSIGN_FROM_DLSYM(clGetPlatformIDs);
...
#undef MACE_CL_ASSIGN_FROM_DLSYM
3.通过函数指针使用该接口
在当前工程重新定义一个完全一样的函数,然后利用该函数去调用dlsym取出的接口。
这样就可以在编译的时候不链接该SO,且编译能够通过。
cl_int clGetPlatformIDs(cl_uint num_entries,
cl_platform_id *platforms,
cl_uint *num_platforms) {
auto func = mace::runtime::OpenCLLibrary::Get()->clGetPlatformIDs;//获取取出的函数指针
if (func != nullptr) {
return func(num_entries, platforms, num_platforms);//使用该函数
} else {
return CL_INVALID_PLATFORM;
}
}
4.dlclose关闭库
正常库是不需要反复去打开和关闭的,而且库在进程结束时自动回关闭,因此实际应用过程中很少手动添加这一步。