# C++的DLL封装以及调用(类、VS2019)

因工作需要,对C++部分代码进行DLL封装。涉及到几个问题:

  1. 拿到手的是已经完成的项目,要对其中部分代码进行DLL封装,代码主要是类内的成员函数。所以在不改变项目其他部分的情况下,只对成员函数的具体实现封装。这里涉及到在某一个类内对其他类的调用。
  2. 鉴于对DLL的显式调用有些繁琐,故采用隐式调用,隐式调用我知道的有两种方法,我只用了一种,出于可移植性考虑。

DLL的创建

这里直接在新建时选择创建动态链接库即可。
第一步:新建自己的 .h 和 .cpp 文件。格式大致如下:

  1. 其中__declspec(dllexport),是说明被该标识符声明的函数导出至DLL,__declspec(dllimport)是说明从DLL导入,更详细的功能可以上网查找(我理解的也不是很明白)。
  2. extern “C” { } 是说明这里的函数按照C语言的编译规则进行编译(我是这么理解的)。更详细的功能可以上网查找。
  3. 其实DLL还有很多的知识,比如_stdcall与_cdecl之间的纠葛,还有与之有关的 .def 文件等。这里提一嘴,省的自己给忘了。
// dll.h
#ifndef _DLL_H_
#define _DLL_H_

#ifdef DLLEXPORT
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif // 

extern "C"
{
	DLL_API Function_01();
	DLL_API Function_02();
}

#endif // !

这是我自己的
在这里插入图片描述

第二步:这里的cpp文件就和平常的一样,将函数实现放在这,不过记得要先#define DLLEXPORT,再包含头文件

// dll.cpp
#define DLLEXPORT
#include "dll.h"
DLL_API void Function_01()
{
}
DLL_API void Function_02()
{
}

这是我自己的
在这里插入图片描述

DLL的隐式调用——方法一

  1. 在生成DLL后,在项目文件中找到刚刚 .h 文件(就在项目文件夹中)、.lib、.dll 文件(这两个应该在x64下的Debug或者Release文件夹中,)。
  2. 将.h 和.lib文件复制到目标工程的项目文件夹下,然后将.dll文件复制到目标工程项目文件夹下的Debug文件夹(这个文件夹应该也在x64中)下,这个文件夹是要目标工程先运行才会产生。

DLL的隐式调用——方法二

点开目标工程的属性->配置属性->VC++目录->包含目录->添加DLL工程的 .h 文件所在路径;
点开目标工程的属性->配置属性->VC++目录->库->添加DLL工程的 .lib 文件所在路径;
点开目标工程的属性->链接器->输入->附加依赖项->手动输入添加DLL工程的 .lib 文件名;
然后将 .dll 文件复制到Debug文件夹中(同理,可能需要先运行才会产生)。
这里有个问题,那就是一旦工程移动了,路径发生了变化,编译就会出错。有个解决方法就是采用相对路径。

创建目标工程

与平常创建的工程一样,新建一个空项目即可,添加自己的头文件、源文件之类的。
首先在工程下的头文件中添加->现有项,选择刚刚复制进来的 .h 文件。
其次在资源文件中添加->现有项,选择刚刚复制进来的 .lib 文件。
最后将刚刚的 .dll 文件放入Debug文件夹中(这个文件夹应该也在x64中,Debug文件夹是要目标工程先运行才会产生)。
然后在自己的目标工程的代码处调用即可,更平时书写没什么太大区别,要注意传参问题。

#include <iostream>
#include "0315_dll_cpp_cpp.h"
using namespace std;

int main()
{
	cout << exportDate() << endl;

	Dog dog;
	dog.setHigh(10);
	dog.setWide(25);
	cout << dog.outDate() << endl;

	Cat cat;
	cat.setHigh(5);
	cat.setWide(10);
	cout << cat.outDate() << endl;

	system("pause");
	return 0;
}

DLL的显式调用

在这里插入图片描述
1、声明头文件<windows.h>,说明我想用windows32方法来加载和卸载DLL
2、然后用typedef定义一个指针函数类型.typedef void(*fun) //这个指针类型,要和你调用的函数类型和参数保持一致
3、定一个句柄实例用来取DLL的实例地址。格式为:HINSTANCE hdll;hdll=LoadLibrary(“DLL地址”);这里的地址字符串类型是LPSTR,当是unicode字符集的时候会不行,
因此要在配置-属性-常规里面把默认字符集“unicode”改成支持多字符扩展即可。
4、取的地址要判断,返回的句柄是否为空,如果为无效句柄,那么要释放加载DLL所占用的内存。
5、定义一个函数指针,用来获取你要用的函数地址。然后通过GetProcAdress来获取函数的地址,参数是DLL的句柄和你要调用的函数名:
比如:FUN=(fun)GetProcAdress(hdll,“sum”);
这里也要判断要函数指针是否为空,如果没取到要求的函数,那么要释放句柄。
6、然后通过函数指针来调用函数。
7、调用结束后,就释放句柄FreeLibrary(hdll);
注意:这里一定要先对句柄和函数指针进行判断,再执行其他步骤
原文链接:我是参考的这篇文章

/* Call_Dll_cpp_cpp02.cpp 对Dll_cpp_cpp02的显示调用
动态调用DLL封装的类:(太复杂了)
1.将要封装的代码按照正常DLL封装
2.新建一个接口源文件,里面存放接口函数,接口函数是定义一种新的函数,
  将类的方法拆开,从而进行调用,按照DLL封装的方式编写
3. 将要封装的头文件放入调用的代码的工程下
4. 在要调用的代码中采用显式调用的方式编写代码(另类复现)
*/
#define _AFXDLL
#include "afx.h"
#include "Windows.h"
#include "Dll_cpp_cpp02.h"
#include <iostream>
using namespace std;

typedef void(*call_InsertSort)(int*, int);
typedef void(*call_setWide)(Animal*, int);
typedef void(*call_setHigh)(Animal*, int);
typedef Cat* (*call_GenerateCat)(int h, int w);
typedef Dog* (*call_GenerateDog)(int h, int w);
typedef int (*call_OutDate)(Animal* animal);

int main()
{
	//call_InsertSort InsertSort;
	call_GenerateCat GenerateCat;
	call_GenerateDog GenerateDog;
	call_setHigh setHigh;
	call_setWide setWide;
	call_OutDate OutDate;
	HINSTANCE hdll = LoadLibrary(_T("Dll_cpp_cpp02.dll"));
	
	if (hdll == NULL)
	{
		cout << "dll不存在" << endl;
	}
	else
	{
		GenerateCat = (call_GenerateCat)GetProcAddress(hdll, "GenerateCat");
		if (GenerateCat == NULL)
		{
			FreeLibrary(hdll);
			cout << "GenerateCat函数不存在" << endl;
		}
		else
		{
			Cat* cat = GenerateCat(5, 1);
			OutDate = (call_OutDate)GetProcAddress(hdll, "OutDate");
			if (OutDate == NULL)
			{
				FreeLibrary(hdll);
				cout << "OutDate函数不存在" << endl;
			}
			else
			{
				cout << OutDate(cat);
			}
		}
	}
}
	*/
	system("pause");
	return 0;
}

记录一下我遇到的问题:

  1. #include “stdafx.h"的问题,为啥要先#define _AFXDLL
  2. 在封装的时候,如果要对封装函数外部的值进行改变,一定要记得传入引用或者指针。
  • 1
    点赞
  • 5
    收藏
  • 打赏
    打赏
  • 0
    评论
本支持库可以调用 DLL 封装,vc 与 e 语言的 都支持 非普通方法 取 this 计算 函数地址 然后汇编call 而是通过易语言的内存特性。。。具体看演示吧。 调用下方支持库实现: 支持库名称及版本:易神补刀 (1.0#0版) 调用格式: 〈整数型〉 VC与E转移 (通用型变量 目标变量,整数型变量 源指针) - 易神补刀->易神补刀 (VC++转移到易壳中)由于跨编译器,释放时需要转回来。本命令为初级命令。 参数的名称为“目标变量”,型为“通用型(all)”,提供参数数据时只能提供变量。方法(与数量)(基)需要与源指针指向的相同。 参数的名称为“源指针”,型为“整数型(int)”,提供参数数据时只能提供变量。vc++ 指针变量 转移后变成目标指针。 调用格式: 〈无返回值〉 转移 (通用型变量 目标变量,通用型变量 源变量) - 易神补刀->易神补刀 E转E用完不需要转移回去,暂时未发现BUG!。本命令为初级命令。 参数的名称为“目标变量”,型为“通用型(all)”,提供参数数据时只能提供变量。方法(与数量)(基)需要与源指针指向的相同。 参数的名称为“源变量”,型为“通用型(all)”,提供参数数据时只能提供变量。 还有两个命令 取指针 与 取回调指针 本支持库 静态版 很小 才 4KB非静态版本 很大 因为里面有很多 文本信息 与检查函数52KB 嫌弃的可以不下载。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JSBYWUYR

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值