因工作需要,对C++部分代码进行DLL封装。涉及到几个问题:
- 拿到手的是已经完成的项目,要对其中部分代码进行DLL封装,代码主要是类内的成员函数。所以在不改变项目其他部分的情况下,只对成员函数的具体实现封装。这里涉及到在某一个类内对其他类的调用。
- 鉴于对DLL的显式调用有些繁琐,故采用隐式调用,隐式调用我知道的有两种方法,我只用了一种,出于可移植性考虑。
DLL的创建
这里直接在新建时选择创建动态链接库即可。
第一步:新建自己的 .h 和 .cpp 文件。格式大致如下:
- 其中__declspec(dllexport),是说明被该标识符声明的函数导出至DLL,__declspec(dllimport)是说明从DLL导入,更详细的功能可以上网查找(我理解的也不是很明白)。
- extern “C” { } 是说明这里的函数按照C语言的编译规则进行编译(我是这么理解的)。更详细的功能可以上网查找。
- 其实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的隐式调用——方法一
- 在生成DLL后,在项目文件中找到刚刚 .h 文件(就在项目文件夹中)、.lib、.dll 文件(这两个应该在x64下的Debug或者Release文件夹中,)。
- 将.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;
}
记录一下我遇到的问题:
- #include “stdafx.h"的问题,为啥要先#define _AFXDLL
- 在封装的时候,如果要对封装函数外部的值进行改变,一定要记得传入引用或者指针。