动态链接库知识汇总

近期将算法封装了dll,将最近的一些知识做下整理。

一、动态链接库两种链接类型

显示链接:

 显式链接一般是指在程序运行中,由程序代码用LoadLibrary和LoadLibraryEx函数来加载动态库。这两个函数仅仅是把库文件映射到你的进程地址空间中,并不会查找你要使用的函数的地址。如果这些函数成功,就会返回库文件在你的进程中的映像的基址,随便你就可以使用这个基址来调用GetProcAddress()函数来得到你要用的库中的函数的地址了。如果LoadLibrary函数失败,就会返回NULL,并不会强制杀死你的应用程序。

优点:手动加载dll,手动导出函数,

缺点:使用繁琐。

 

隐式链接:

隐式链接是指在代码中使用了了库中的代码,只是在链接时,链接器会把该库的符号信息以及导入函数的信息写入到生成的Exe文件的特定的区段中。当该程序加载时,操作系统会根据这个区段中的信息,来查找每个它需要的动态库,并根据这些动态库的导出表,与程序中的导入表相配对,以确定程序中使用的动态库中的代码在什么位置。这样应用程序就能够正确是链接到动态库中的代码了。如果在这个过程中,发生了错误,比如没有找到要求的库文件,或者库中没有你要使用的函数,程序的初始化就会失败,操作系统就会报错,并终止该程序的初始化.

优点:使用方便,

缺点:启动时加载,程序启动会变慢,常驻内存。

exe中包含了lib文件,后续只需要带dll即可。

常见的使用场景如加载opencv库,itk库,vtk库等。

二、使用场景

(1)软件规模

由于大型软件一般运行内存较大,此时最好使用显示链接,用完即卸载,类似插件的方式,不用常驻内存。

 

三、动态链接库如何调试

动态链接库调试时主要需要在dll的工程目录中进行设置,主要方法如下:

(1)https://blog.csdn.net/zang141588761/article/details/80922010

(2)https://blog.csdn.net/Simon798/article/details/99576041?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase

(3)在调用dll时,一直失败,单不知道是什么原因,同事推荐使用GetLastError抓取错误。

四、如何导出类

函数导出比较常见。

(1)直接导出类,这种做法通用性很差,不同编译器加载可能会有问题

(2)通过抽象类的方式实现,然后导出接口类指针的方式。如下面的链接

https://blog.csdn.net/fantasysolo/article/details/88553742

通过接口可以保证数据的安全性,因为用户只可以调用接口函数,无法访问派生类中的内容。

接口类不应该包含数据。

(3)通过函数的方式,即将类中的函数全部导出,包括类的构建。

五、写接口的一些技巧

(1)在写通用接口时,输入参数接口可以写成如下形式

int MyClass::InputData(char * inputdata,int length)

这样的接口可以接收任何参数,如果只有一种数据类型,那么到时候将数据指针传递给inputdata,数据长度传递给length。函数内部进行实现时,将指针转换成相应的数据类型即可。

如果是多种数据类型的组合,那么定义结构体,然后转换成指针传递给inputdata,在函数内部使用时候再转换过来即可。此时length可以不用。

(2)在写通用输出数据接口时,接口可以使用两种方式

①int MyClass::OutPut(char **output,int &length)

②char * MyClass::OutPut(int &length)

两种写法的区别是①可以返回错误码,适用于有可能发生失败调用的场景②直接返回数据,在使用①时output由于是二维指针,外部调用者容易忽略二维指针的释放,容易造成内存泄漏。

六、动态链接库课程学习笔记

来自次课程https://edu.51cto.com/course/11883.html,个人感觉讲解一般。

1、extern "c"关键词,指示编译器这部分代码按照C语言方式进行编译,而不是C++的

(1)使用场景1,在c++环境下使用c语言方式变异dll,保证函数名称不变

(2)使用场景2,在c++中引用C语言中的函数和变量,在包含C语言头文件时,需进行如下处理

extern "C"

{

#include "cExample.h"

}

原因是c和c++生成的函数编译名称不一致,会导致无法调用函数。(上例中,cExample.h总函数的实现是在.c文件中,以c方式编译的)

C++文件中调用C头文件的标准写法:

#ifdef __cplusplus
extern "C"{
#endif

//函数声明等

#ifdef __cplusplus
}
#endif

2.__declspec(dllexport)和.__declspec(dllimport)

(1)使用宏定义,在dll的头文件中标准写法如下

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

在dll实现文件中包含上述的头文件后,实现了dllexport功能。

在用户调用dll文件时,引用头文件后,即变成了dllimport,

3.def的用处

实现到处函数名称、隐藏权限等变化。

4.调用约定

(1)在vs->c/c++->高级->调用约定中选择调用约定,vs中默认_cdecl方式

动态链接库知识汇总_第1张图片

5.DLL导出c++类

直接导出类的方式,如下图所示,

动态链接库知识汇总_第2张图片

这样做的缺点是会导出很多无用的函数,如类的两种默认构造函数和赋值函数,也因为是按照c++的方式导出,所以导出类函数名称不太可控,dll不太实用。

动态链接库知识汇总_第3张图片

动态链接库知识汇总_第4张图片

动态链接库知识汇总_第5张图片

动态链接库知识汇总_第6张图片

6.DLL入口函数DLLMain函数

分成四种情况,在开始或者结束的时候都会调用dllmain,可以做一些操作。

7.DLL导出变量

8.DLL分析工具——depends

可以查看dll的依赖项以及包含的函数名称、序号等。

 

 

参考文章:

(1)https://blog.csdn.net/hcmfys2009/article/details/83358500

(2)https://blog.csdn.net/xidaoqiong1985/article/details/2639563

你可能感兴趣的:(C++)