引言

在做项目过程中库函数是一个很重要的模块,而我们往往将每个功能独立成一个函数,这样能够提高函数的复用性。

根据使用过的场景,项目过程中对库函数的引用总共3种方式

方式 链接时刻 库指定时刻 特点
静态链接 编译时 编译时 编译时检查链接错误,编入运行程序(运行程序独立)
动态链接 运行时 编译时 编译时检查链接错误,运行时调入依赖库
动态链接 运行时 运行时 编译时不做任何检查,运行时调入依赖库

其中前两种是我们熟悉的,最后一种是“完全”动态方式,包括库文件的指定都是由代码完成的。

这里先重点讲解最后一种,后续会在补充,如果忘记了,可以留言催一下。

链接静态库

静态链接动态库

动态链接动态库

定义了一组接口函数,但无法得知具体的实现细节,可能是由别人实现,程序在运行时根据某个传入的参考,动态调用这一组接口,并按照特定的方式运行,在C++中多少像虚函数的特点-提供接口而不在乎具体的实现。

这种场景比较适合于一个框架(开发者A),多个不同的细节实现(开发者B,C…)的一种应用。

从上面的描述,要完成本项功能,包括调用框架的编写(相当于服务器端)和被调用库函数的编写(相当于“客户端”)。而要做到跨平台,这两者必须都是跨平台的。

动态库编写

C语言有不带类的C和带类的C(C++),C++在编译时,实际上是要被转换为C的,所以从调用的角度来讲,只有函数,没有类。这样问题就来了:类如何导出?即调用者如何得到类的信息?

对于后出现的语言,例如Java,C#,这根本不是问题,语言的开发者已为我们考虑了这个情况。在微软的世界里(Windows),这也不是大问题,微软自己有一套规则。微软的COM的一个特点就是用来描述导出类的,但可惜并没有被大家接受。在Linux世界里,似乎只有一个个的函数能被“开放出来”,供其他开发者所调用。

如果要开发跨平台的库(不提供源代码),还是老老实实回归C语言吧。

Windows

  • .def文件方法

    .c.cpp的编写不做任何变化,VS项目的属性中,将上面的.def文件添加到链接器->输入->模块定义文件中(注意路径,可利用宏)。
    在cmake中可以将 def跟源文件一起添加

    1. 建立一个.def文件,将需要导出的函数按如下格式编写:

      1
      2
      3
      4
      5
      6
      LIBRARY    "dlldemo"

      EXPORTS
      DllInit @1
      DllFunc @2
      DllFree @3

      这种方式不建议用多态,会有问题

    2. 在.h声明导出函数时,采用extern C包裹它们,如下所示:

      1
      2
      3
      4
      5
      6
      extern "C"
      {
      bool DllInit();
      bool DllFree();
      const char* DllFunc(const char *name);
      }
  • 特别声明每个需要导出的函数
    只需要修改 .h头文件即可,对于 .cpp内的函数无需修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #if defined(_WIN32)
    #ifdef DLLDEMO_EXPORTS
    #define DLLDEMO_API extern "C" __declspec(dllexport)
    #else
    #define DLLDEMO_API extern "C" __declspec(dllimport)
    #endif
    #elif defined(__GNUC__)
    #define DLLDEMO_API extern "C" __attribute__ ((visibility("default")))
    #endif

    DLLDEMO_API bool DllInit();
    DLLDEMO_API bool DllFree();
    DLLDEMO_API const char* DllFunc(const char *name);

Linux

Linux下实现起来比较简单,只在.h声明导出函数时,采用extern C包裹它们,如上面的例子所示,即是跨平台的解决方案,推荐使用。

如果函数不多,建议编写一个 .def文件方便一些,如果函数较多,且其声明不断变化,采用宏定义 __declspec(dllexport)较好,这样维护方便。

WINDOWS函数导出

具体实现

通过调用不同平台的接口,实现统一动态库加载的接口。主要实现了四个接口:加载动态库 LoadLib ,获取动态库函数 GetLibFun,释放动态库 FreeLib,获取错误码 FreeLib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

#if defined(_WIN32)
#include <windows.h>
typedef HMODULE MODULE_HANDLE;
#endif

#if defined(__linux__)
#include <dlfcn.h>
typedef void * MODULE_HANDLE;
#endif

MODULE_HANDLE LoadLib(const char *plname)
{
#if defined(_WIN32)
return LoadLibraryA (plname);
#elif defined(__linux__)
return dlopen( plname, RTLD_NOW|RTLD_GLOBAL);
#endif
}
void FreeLib(MODULE_HANDLE h)
{
if(h){
#if defined(_WIN32)
FreeLibrary(h);
#elif defined(__linux__)
dlclose (h);
#endif
}
}
void *GetLibFun(MODULE_HANDLE h, const char *pfname)
{
if(h){
#if defined(_WIN32)
return (void *)GetProcAddress(h, pfname);
#elif defined(__linux__)
return dlsym(h,pfname);
#endif
}
return NULL;
}
const char GetLastErr()
{
char p[100];
int size=99;
#if defined(_WIN32)
sprintf(p,size, "%u",::GetLastError());
#elif defined(__linux__)
sprintf(p,size, "%s",dlerror());
#endif
return p;
}

class CDynamicLibrary
{
public:
CDynamicLibrary()
{
m_hModule = NULL;
}
~CDynamicLibrary()
{
FreeLib(m_hModule);
}
inline bool LoadLib(const char *lpname)
{
m_strDllName = lpname;
m_hModule = LoadLib(lpname);
return m_hModule!=NULL;
}
inline void *GetLibFun(const char *pfname)
{
return GetLibFun(m_hModule, pfname);
}
inline const char *GetLastErr()
{
char* msg = GetLastErr();
m_strMsg = msg;
return m_strMsg.c_str();
}

private:
std::string m_strMsg;
std::string m_strDllName;
MODULE_HANDLE m_hModule;
};

调用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void testdll()
{
typedef bool (*F_DllInit)();
typedef bool (*F_DllFree)();
typedef const char* (*F_DllFunc)(const char *name);
//这里只演示函数调用,类调用同理
#if defined(_WIN32_PLATFROM_)
char *pdllname = "dlldemo.dll";
#endif
#if defined(_LINUX_PLATFROM_)
char *pdllname = "libdlldemo.so";
#endif
MODULE_HANDLE h = LoadLib(pdllname);
if(h){
F_DllInit dll_init = (F_DllInit)h.GetProc("DllInit");
if(!dll_init){
printf("\nFunction DllInit Error: %s",h.GetLastError());
}else{
bool init_flag = dll_init();
}
F_DllFunc dll_func = (F_DllFunc)h.GetProc("DllFunc");
if(!dll_func){
printf("\nFunction DllFunc Error: %s",h.GetLastError());
}else{
const char* name="123";
const char* func_data = dll_func(name);
}
F_DllFree dll_free = (F_DllFree)h.GetProc("DllFree");
if(!dll_free){
printf("\nFunction DllFree Error: %s",h.GetLastError());
}else{
bool free_flag = dll_free();
}

}
}

参考

  1. https://blog.csdn.net/guxch/article/details/7915404