一:Win32 Dynamic-Link Library 方式创建 Non-MFC DLL动态链接库
只有个简单的DllMain入口函数。
使用导出函数关键字_declspec(dllexport)创建DLL(对这个关键字的介绍参看下一遍博文吧)。因此,新生成Win32Dll.h文件,并在其中用关键字_declspec(dllexport)对要导出的函数进行声明。
在Win32Dll.cpp对其进行实现:
如上所示,我们想要导出的2个函数分别为JustSoSo和Max,他们都用关键字_declspec(dllexport)进行了修饰,但是不同的是JustSoSo还有用extern “C”进行修饰。有什么不同吗?extern “C”的修饰时必须的吗?我们具体来试验一下:
其中,FunctionFunc是JustSoSo的函数指针,而pMax是Max的函数指针。
对JustSoSo的调用:
调试结果,调用JustSoSo()成功!
对Max的调用:
调试结果,调用Max失败!原因是GetProcAddress返回的函数指针为0x00000000.
这是为什么呢?是extern “C”导致的吗?
在Win32Dll.h中修改Max的的声明为:
extern “C” _declspec(dllexport) int Max(int a,int b);
调试结果:调用Max成功!
那么,这个extern “C” 是个什么功效呢?如何理解这个状况呢?
答:在DLL的设计中,如果使用C++开发,通常在导出函数的定义中使用extern ”C“,为什么呢?其实是因为,当用户使用“运行时动态链接”的时候需要使用GetProcAddress函数来得到导出函数的地址,该函数是通过导出函数的函数名定位导出函数的,而C++编译器因为函数重载的原因会对开发者定义的函数名进行修饰,导致导出表中的函数名通常不是开发者使用的函数名,比如函数Max可能被修饰成??Max@QAEX.所以使用extern “C”通知编译器按照C的格式进行编译,而不是使用C++的方式进行编译。(使用VS提供的一个工具Dependency Walker可以查看DLL的导出函数)
首先,将DLL和LIB文件放置于调用者同一目录下。
包含入lib文件:
方式一:
#pragma comment (lib,“Win32Dll.lib”)
方式二:
Project->setting->link->Object/library modules 添加 Win32Dll.lib
声明导出函数:
extern “C” _declspec(dllexport) int Max(int a,int b);
使用:
int temp = Max(5,8);
得到 temp 为 8,使用正确,调试成功~!
二:MFC AppWizard[dll]方式生成常规DLL
首先,打开VC++,选择File->New创建工程,选择MFC AppWizard[dll]方式,设置Project Name为MFCdll:
点击“OK”按钮,选择我们要生成的DLL类型:
如上图所示,对于“What type of DLL would you like to create?”提示,我们可以选择要创建的DLL为:
A.常规DLL静态链接到MFC
B.常规DLL动态链接到MFC
C.MFC扩展DLL
在此我们选择第二个选项。
不选择任何选项来回应“What features would you like in your DLL?”
然后点击“Finish”按钮。到此,我们的工程创建就完毕了。
现在我们想把一个add函数作为在DLL外部可以调用的导出函数,这次我们采用.def的方式来创建DLL。
那么首先在.def中田间add函数名:
然后,在类中添加add方法:
注意:在函数定义的起始需要此语句用来正确地切换MFC模块状态
创建的是动态DLL:
AFX_MANAGE_STATE(AfxGetAppModuleState());
创建的是静态DLL:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
现在我们用显示连接来调用这个DLL:
先进行指针函数的定义,然后进行寻址等操作。
调试结果:失败了!!在dlladd(2,3)这一步!!这是为什么呢??
报错如下:
查询资料,有解决办法如下:
1.
要将导出函数声明为WINAPI,如:
void WINAPI AutoChess(BOOL, BOOL, BOOL);
使用时,函数指针的定义也需要注为WINAPI:
typedef void (WINAPI*AutoChess)(BOOL, BOOL, BOOL);
2.
是由于调用的接口与原接口参数不一致导致的,比如参数不符合或少参数输入导致.
但是,我按照提示进行修改声明及函数指针的定义
函数声明.h
函数指针定义:
依然是报错,这是为什么啊??
继续查询错误原因资料:
网络上搜索,出现此错误的原因如下:(和调用约定相关)
1、dll调用时,调用了dll中不存在的一个方法。出现此种情况,一般是在使用dll时没有把版本搞清楚。
2、由于调用的接口与原接口参数不一致导致的,比如参数不符合或少参数输入导致。这种错误方式比较常见
3、在dll中导出函数必须通过def文件来设定(__declspec(dllexport)这样的方式是为用.LIB连接准备的),且要声明为WINAPI,如:
void WINAPI AutoChess(char board[][15], char color, int &x, int &y);
4、Dll导出函数声明导出方法,与主模块中声明的导入方法不一致。使得调用时参数的传递中,破坏了调用堆栈,出现错误。
解决方法:请确定导出方(Dll等)与导入方(Exe等)的声明保持一致。
5、Dll导出函数本身破坏了调用堆栈。编码中最一般的错误比如:对象(如CString)等。
解决方法:保证产生的对象都被安全的释放。
进行调用的单步调试,发现确实是在传递调用参数时出现问题:
调用时,我传入的参数为整数2和3:
F11单步调试进入DLL函数中,传入的实参发生变化:
这是怎么回事呢?
参数类型和个数没有误差,那是哪里出问题了?聪明的你一定发现了,在DLL中我对all函数的声明进行了修改,但是定义没有修改,依然是:
这就是问题所在,定义的时候我依然让他带有"CMFCdll::”,因此,进行修改如下(或是 int add(int a ,int b)也可):
进行调试,完全正确~~!!!
关于WINAPI:
WINAPI 含义:
#define WINAPI __stdcall
默认情况下,我们的函数调用都是遵循__stdcall这个规则的。当然,也有诸如__cdecl、__pascal等规则。
使用__stdcall还是__cdecl或__pascal,在纯Windows编程下并非特别需要。
__stdcall:
1、进行函数调用,函数参数的入栈方式是最右边先入栈。
2、同时__stdcall规定,子函数负责栈的回收(调用者只负责压栈). 题外话:__pascal的调用规则是从左到右,正好与__stdcall相反。
3、C调用约定(即用__cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。
MFC的缺省调用:
DLL与WINAPI:
在一些地方windows要求必须使用winapi标准,比如说在dll中的输出函数(
制作dll 是为了 让其他的语言可以调用, 但是呢, 有的语言 如delphe 的参数调用方法就是 _stadcall……所以,为了dll 有更好的通用性, 一般 都用 WINAPI
)。
现在试试用隐式方式来调用DLL:
首先将DLL和LIB文件放到与调用DLL的EXE同级目录下。
在调用中引入lib库:
声明导出函数:
使用:
结果得出:temp为5,正确~!