linux 平台加载动态库通常使用dlopen,dlsym,dlclose三个函数实现
最近写了一个小程序,遇到dlsym总是调用失败返回空值,查找了很多相关的资料,确定动态库的创建有问题、下面是最初版的 有问题的动态库代码:
dlldemo_global.h
#ifndef DLLDEMO_GLOBAL_H
#define DLLDEMO_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(DLLDEMO_LIBRARY)
# define DLLDEMOSHARED_EXPORT Q_DECL_EXPORT
#else
# define DLLDEMOSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // DLLDEMO_GLOBAL_H
dlldemo.h
#ifndef DLLDEMO_H
#define DLLDEMO_H
#include "dlldemo_global.h"
//导出函数
DLLDEMOSHARED_EXPORT int Add(int a,int b);
DLLDEMOSHARED_EXPORT int Sub(int a,int b);
class DLLDEMOSHARED_EXPORT DLLDemo
{
public:
DLLDemo();
};
#endif // DLLDEMO_H
dlldemo.cpp
#include "dlldemo.h"
DLLDemo::DLLDemo()
{
}
int Add(int a,int b)
{
return a+b;
}
int Sub(int a,int b)
{
return a-b;
}
这样导出来的库中的函数已经发生了变化,可以通过nm libDLLDemo.so来查看
root@scada:/home/work/DLLDemo/lib# nm libDLLDemo.so
0000000000200b30 B __bss_start
0000000000200b30 b completed.6903
w __cxa_finalize@@GLIBC_2.2.5
0000000000000660 t deregister_tm_clones
00000000000006f0 t __do_global_dtors_aux
0000000000200890 t __do_global_dtors_aux_fini_array_entry
0000000000200b28 d __dso_handle
00000000002008a0 d _DYNAMIC
0000000000200b30 D _edata
0000000000200b38 B _end
0000000000000794 T _fini
0000000000000730 t frame_dummy
0000000000200888 t __frame_dummy_init_array_entry
0000000000000880 r __FRAME_END__
0000000000200b00 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000000610 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000200898 d __JCR_END__
0000000000200898 d __JCR_LIST__
w _Jv_RegisterClasses
U qt_version_tag@@Qt_5.7
00000000000006a0 t register_tm_clones
0000000000200b30 d __TMC_END__
000000000000076b T _Z3Addii
000000000000077f T _Z3Subii
0000000000000760 T _ZN7DLLDemoC1Ev
0000000000000760 T _ZN7DLLDemoC2Ev
000000000000079d r _ZStL19piecewise_construct
发现Add函数已经变成了_ZNAddii,Sub函数变成了_ZNSubii,这样在动态引用时dlsym时总是返回null。下面是修饰规则:
DLL(动态库)导出函数名乱码含义 C++编译时函数名修饰约定规则: __stdcall调用约定: 1、以"?"标识函数名的开始,后跟函数名; 2、函数名后面以"@@YG"标识参数表的开始,后跟参数表; 3、参数表以代号表示: X--void D--char E--unsigned char F--short H--int I--unsigned int J--long K--unsigned long M--float N--double _N--bool .... PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复; 4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前; 5、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。 其格式为"[email protected]@YG*****@Z"或"[email protected]@YG*XZ",例如 int Test1(char *var1, unsigned long)[email protected]@[email protected] void Test2()-----"[email protected]@YGXXZ" __cdecl调用约定: 规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。 __fastcall调用约定: 规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。 如果要用DEF文件输出一个"C++"类,则把要输出的数据和成员的修饰名都写入.def模块定义文件 所以... 通过def文件来导出C++类是很麻烦的,并且这个修饰名是不可避免的
怎么办呐?为什么呐?
这是因为在导出函数时使用的g++编译器,导出时自动加上约定的符号,为了使用原生的函数名,因此将导出函数名时加上extern “C”,表明使用gcc编译器,这样导出的函数名就不会改变了。以下是改过的头文件dlldemo.h代码:
#ifndef DLLDEMO_H
#define DLLDEMO_H
#include "dlldemo_global.h"
//导出函数
extern "C" DLLDEMOSHARED_EXPORT int Add(int a,int b);
extern "C" DLLDEMOSHARED_EXPORT int Sub(int a,int b);
class DLLDEMOSHARED_EXPORT DLLDemo
{
public:
DLLDemo();
};
#endif // DLLDEMO_H
我们再来看看导出函数名吧:
root@scada:/home/work/DLLDemo/lib# nm libDLLDemo.so
000000000000076b T Add
0000000000200b30 B __bss_start
0000000000200b30 b completed.6903
w __cxa_finalize@@GLIBC_2.2.5
0000000000000660 t deregister_tm_clones
00000000000006f0 t __do_global_dtors_aux
0000000000200890 t __do_global_dtors_aux_fini_array_entry
0000000000200b28 d __dso_handle
00000000002008a0 d _DYNAMIC
0000000000200b30 D _edata
0000000000200b38 B _end
0000000000000794 T _fini
0000000000000730 t frame_dummy
0000000000200888 t __frame_dummy_init_array_entry
0000000000000880 r __FRAME_END__
0000000000200b00 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000000608 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000200898 d __JCR_END__
0000000000200898 d __JCR_LIST__
w _Jv_RegisterClasses
U qt_version_tag@@Qt_5.7
00000000000006a0 t register_tm_clones
000000000000077f T Sub
0000000000200b30 d __TMC_END__
0000000000000760 T _ZN7DLLDemoC1Ev
0000000000000760 T _ZN7DLLDemoC2Ev
000000000000079d r _ZStL19piecewise_construct
以上命令输出结果查看到Add 和Sub两个函数名。再此调用函数dlsym,此时不为空,调用成功了。
以下是调用该动态库的代码:
#include <QCoreApplication>
#include <dlfcn.h>
//#include "dlldemo.h"
typedef int(* FuncAddTest)(int, int); // 定义函数指针类型的别名
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
const char *dylib_path = "/home/work/DLLDemo/lib/libDLLDemo.so";
void *handle = NULL;
char *error = NULL;
if((handle = dlopen(dylib_path, RTLD_GLOBAL | RTLD_NOW)) == NULL)
{
printf("dlopen - %sn", dlerror());
exit(-1);
}
else
{
//获取add地址
FuncAddTest addfunc = (FuncAddTest)dlsym(handle, "Add");
if(addfunc)
{
printf("1 + 2 = %d\n",addfunc(1,2));
}
else
{
if ((error = dlerror()) != NULL) {
fprintf (stderr, "%s ", error);
exit(1);
}
printf("未找到\n");
}
dlclose(handle);
}
return a.exec();
}