使用 dlopen 和 dlsym 来使用 C++ 中的函数、类

1 问题简介

正常情况下,dlopen 和 dlsym 是用来处理 C 库中的函数的,但对 C++ 来说,情况稍微复杂,如在 Android framework media 框架中加载 C++ 软解库组件时使用到 dlsym 来链接函数符号

typedef SoftOMXComponent *(*CreateSoftOMXComponentFunc)(
        const char *, const OMX_CALLBACKTYPE *,
        OMX_PTR, OMX_COMPONENTTYPE **);

CreateSoftOMXComponentFunc createSoftOMXComponent =
    (CreateSoftOMXComponentFunc)dlsym(
            libHandle,
            "_Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPE"
            "PvPP17OMX_COMPONENTTYPE");

这个函数符号名 “_Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPE” 跟 C++ 库中实际定义的函数名 “createSoftOMXComponent” 有很大的不同,这是为什么呢?这节主要来探究这个问题。

2. 原因分析

在 C/C++ 程序(库、目标文件)中,所有非静态的(non-static)函数在二进制文件中都是以 “符号(symbol)” 形式出现的。这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。

我们可以使用 nm 或者 readelf -s 命令来查看二进制文件中的符号信息,如 libffmpeg C 库的符号信息。

$ readelf -s libffmpeg.so
...
// 一些方法的符号信息
1065: 000969ac   540 FUNC    GLOBAL DEFAULT    7 av_packet_merge_side_data
1066: 00096bc8   556 FUNC    GLOBAL DEFAULT    7 av_packet_split_side_data
1067: 00096df4   112 FUNC    GLOBAL DEFAULT    7 av_packet_shrink_side_dat

// 一些变量的符号信息
2559: 006b9848    72 OBJECT  GLOBAL DEFAULT   12 ff_vqf_demuxer
2560: 006b9890    72 OBJECT  GLOBAL DEFAULT   12 ff_w64_demuxer
2561: 006b98d8    72 OBJECT  GLOBAL DEFAULT   12 ff_wav_demuxer
...

在 C 中,符号名正是函数名,strcpy 函数的符号名就是 “strcpy”,因为在 C 中两个非静态函数的名字各不相同。

但是在 C++ 中允许重载,不同的函数可能有相同的函数名但不同的参数,并且有很多 C 所没有的特征,比如类、成员函数、异常说明等等,因此不可能直接用函数名来作为符号名。

为了解决这个问题,C++ 采用了所谓的 name mangling(名字混淆),它把函数名和参数信息杂糅在一起,改造成只有编译器才懂的符号,例如前面的 “_Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPE” 符号即是 createSoftOMXComponent 函数名带了些参数信息。

每个编译器都按自己的方式来进行 Name Mangling 。所以各个编译器给二进制文件生成的符号可能都不相同,如用 Android 编译生成的 C++ 二进制文件符号都带前缀 “_ZNxandroid”。

$ readelf -s libstagefright.so
5497: 000ae647    24 FUNC    WEAK   DEFAULT   12 _ZNK7android6VectorINS_4S
5498: 0012dd15    22 FUNC    GLOBAL DEFAULT   12 _ZNK9mkvparser4Tags3Tag12
5499: 0013d4f9    56 FUNC    WEAK   DEFAULT   12 _ZNSt3__16vectorIhNS_9all
5500: 0008f01d     6 FUNC    GLOBAL DEFAULT   12 _ZThn8_N7android6ACodec25
5501: 000ba611   316 FUNC    GLOBAL DEFAULT   12 _ZN7android10JPEGSource4r

3 解决方案

3.1 完整符号名传递

明白了 C++ 编译器 Name Mangling 的原理,自然而然有了第一种解决方案,就跟引言中 Google 的做法是一样的。使用 nm 命令获取库函数的完整符号名,直接传给 dlsym 即可。

$ nm xxx.so | grep Fun_Name
3.2 extern “C”

C++ 有个特定的关键字用来声明采用 C binding 的函数:extern “C” 。 用 extern “C” 声明的函数将使用函数名作符号名,就像 C 函数一样。因此,只有非成员函数才能被声明为 extern “C”,并且不能被重载。

尽管限制多多,extern “C” 函数还是非常有用,因为它可以像 C 函数一样被 dlopen 动态加载。冠以 extern “C” 限定符后,并不意味着函数中无法使用 C++ 代码了,相反,它仍然是一个完全的 C++ 函数,可以使用任何 C++ 特性和各种类型的参数。

extern “C” 有两种声明方式,下方第一个示例使用内联(inline)形式还有就是使用 extern “C” { … } 花括号包含这种。

extern "C" int FunA;
extern "C" void FunB();

extern "C" {
    extern int FunA;
    extern void FunB();
}
加载类

冠以 extern “C” 限定符后,并不意味着函数中无法使用 C++ 代码了,相反,它仍然是一个完全的 C++ 函数,可以使用任何 C++ 特性和各种类型的参数。

我们利用函数 new 一个对象返回即可。

extern "C" polygon* create() {
    return new polygon;
}

extern "C" void destroy(polygon* p) {
    delete p;
}
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值