您的位置:首页 > 其它

简单Demo:动态调用自己编写的动态链接库

2017-07-19 10:19 417 查看

一、引言

在上一篇博客里,我主要实现了使用 C++ 自行编写和静态调用动态链接库的功能。这里附上上一篇博客的地址:

简单 Demo:C++编写、调用动态链接库

动态链接库除了可以静态调用外,还可以动态调用。

可能这里对术语不熟悉的同学就会搞混了以下这几个概念:



这里通过查找资料,找到了以下的这四个术语的定义:

静态链接库:就是把 (lib) 文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件。

动态链接库:就是把调用的函数所在文件模块 (dll) 和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从 DLL 中寻找相应函数代码,因此需要相应 DLL 文件的支持。

静态调用DLL:也称为隐式调用,由编译系统完成对 DLL 的加载和应用程序结束时 DLL 卸载的编码(Windows 系统负责对 DLL 的调用次数的计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态链接库时产生的 .LIB 文件加入到应用程序的工程中,想使用 DLL 的函数时,只需在源文件中声明一下。 LIB 文件包含了每一个 DLL 导出函数的符号名和可选择的标识号以及 DLL 文件名,不含有实际的代码。 Lib 文件包含的信息进入到生成的应用程序中,被调用的 DLL 文件会在应用程序加载时同时加载到内存中。

动态调用DLL:也称为显示调用方式,是由编程者用 API 函数加载和卸载 DLL 来达到调用 DLL 的目的,比较复杂,但能更加有效的使用内存,是编制大型应用程序时的重要方式。在 Windows 系统中,与动态调用有关的函数包括:

1. LoadLibrary:装载动态库

2. GetProcAddress:获取要引入的函数,将符号名或标识号转换为 DLL 地址

3. FreeLibrary:释放动态链接库

ps:上述引用了两篇博客的内容:

静态链接库和动态链接库的区别及优缺点

dll动态调用和静态调用有什么区别

二、开始:动态调用自己编写的动态链接库

这里承接上一篇博客的内容,在上一篇博客中,我建立了一个静态调用动态链接库的解决方案,我们可以接着这个解决方案进行修改,使之变成动态调用的案例。

以下的步骤,假定你已经按照上一篇博客中,建立了一个可以静态调用动态链接库的解决方案。(这是前提,否则请看我的上一篇博客:http://blog.csdn.net/u012814856/article/details/75331441

1. 在 MyDll 项目中的 MyDll.h 中添加一个全局函数,用来返回我们需要调用的类的一个实例。只要我们拿到了类的实例,就可以调用其内部方法了:

// MyDll.h 增加内容
__declspec(dllexport) HelloDll* GetInstance()
{
return new HelloDll;
}


注意:这里这个 GetInstance() 函数只能声明为
__declspec(dllexport)
的。

2. 编写 .def 文件,定义模块定义文件。之所以要定义这个文件,是因为要解决不同编译器之间,导出模块名称可能不一样的问题,在这里我们需要手动指定其导出名称一致:

// MyDll.def
LIBRARY "MyDll"
EXPORTS
GetInstance = GetInstance


这个文件需要放在 MyDll 项目目录下,其他的配置并不需要,直接参与编译即可。

3. 为了方便客户端程序调用,我们还需要在 MyDll 项目中添加一个最高层级的虚基类:

// MyDllBase.h
class MyDllBase {
public:
virtual ~MyDllBase() {}
virtual void hello() = 0;
};


可以看到,这个文件内容非常简略,其中虚析构函数被定义为了内联函数,之所以要定义一个虚析构函数,是为了让客户端可以使用基类指针来释放掉派生类的对象(达到真正的拿到一个基类,千军万马都由我来指挥的目的)。

4. 虚基类都变了,MyDll.h 中定义的类也要继承自 MyDllBase 类:

// MyDll.h
class MY_DLL_EXP HelloDll : public MyDllBase {
public:
HelloDll();
~HelloDll();
void hello();
};


注意:这里因为继承了虚基类,子类的构造和析构函数就不能省略了(定义为空也好,但是不能省略,否则编译不过)。

5. 客户端调用代码:此时,我们就可以新建一个项目(这里可以直接使用我们之前建立的 TestDll 项目,或者自己新建一个也可,定义项目模板类型
空项目
即可,除此之外不需要再多配置,毕竟不是静态调用 DLL)

// DynamicCallDll.cpp
#include <windows.h>
#include <cstdlib>
#include <iostream>
#include "MyDll.h"

// using pfGetInst = MyDllBase* (*)()
// pfGetInst 是一个指针,指向一个函数,这个函数返回 MyDllBase 的指针
typedef MyDllBase* (*pfGetInst)();

int main()
{
HMODULE hMod = LoadLibrary("MyDll.dll");
if (hMod) {
pfGetInst pfGetInstance = (pfGetInst)GetProcAddress(hMod, "GetInstance");
if (pfGetInstance) {
// 通过基类指针指向了派生类的对象
MyDllBase* pInst = pfGetInstance();
if (NULL != pInst) {
pInst->hello();
}
if (NULL == pInst) {
std::cout << "Dinamic call MyDll failed T_T" << std::endl;
delete pInst;
}
}
}
system("pause");
return 0;
}


这段代码里,首先宏定义了一个拿到类实例的指向函数的指针,这个指针用来接收需要调用模块类的实例(其本身为基类类型,从而达到了多态调用)。

另外,调用了
LoadLirary()
GetProcAddress()
函数,代码语义比较简单,比较易懂。

7. 最后,编译运行:



至此,完美撒花 ^_^

三、总结

最后,还是总结下四个概念的区别:

静态链接和动态链接的区别(参考自博客 静态链接库和动态链接库的区别及优缺点

静态链接动态链接
代码装载速度快,执行速度略比动态链接库快更加节省内存并减少页面交换
只需保证在开发者的计算机中有正确 .LIB 文件,在以二进制发布程序时不需考虑在用户的计算机上 .LIB 文件是否存在版本问题,可避免 DLL 地狱等问题DLL 文件与 EXE 文件独立,只要输出接口不边(即名称、参数、返回值类型和调用约定不变),更换 DLL 文件不会对 EXE 文件造成任何影响,因而极大地提高了可维护性和可扩展性
-不同编程语音编写的程序只需要按照调用约定就可以调用同一个 DLL 函数
-适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试
缺点:生成的可执行文件体积较大,包含相同的公共代码,造成浪费缺点:使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。这在早期Windows中很常见
动态链接中的静态调用 DLL 和动态调用 DLL 的区别(参考自博客 dll动态调用和静态调用有什么区别

静态调用DLL动态调用DLL
调用方式简单调用方式复杂
比较浪费内存能更加有效地使用内存
由编译系统完成 DLL 的加载和应用程序结束时 DLL 卸载的编码由编程者用 API 函数加载和卸载 DLL
通过这两篇博客,静态调用 DLL 和动态调用 DLL 的实践过程中,熟悉了理论,掌握了实践技能。(官话说的太多)总之就是棒棒的~~~ :)

完成了这一篇博客,终于可以开始自己的库的编写之路啦 (ÒωÓױ)

ps:同样附上本博客的实验代码下载地址

wangying2016/DynamicCallDll
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息