动态链接库--dll使用示例

vs2010生成DLL

vs2010版本:
Microsoft Visual Studio 2010
版本 10.0.30319.1 RTMRel

以Dll1项目为例,项目默认调用约定为__stdcall, 使用extern “C” 限定C编译环境,使用上述vs2010版本生成.
动态链接库--dll使用示例_第1张图片

代码如下:

//Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API __declspec(dllimport) 
#endif

#ifdef __cplusplus
extern "C"
{
#endif
//项目属性中指定了默认调用约定为__stdcall, 因此这里无需再次指定
	int DLL1_API /*__stdcall*/ add(int a, int b);	
	int DLL1_API /*__stdcall*/ subtract(int a, int b);

#ifdef __cplusplus
};
#endif

//Dll.cpp
#define DLL1_API __declspec(dllexport)
#include "Dll1.h"

//项目属性中指定了默认调用约定为__stdcall, 因此这里无需再次指定
int /*__stdcall*/ add(int a, int b)
{
	return a + b;
}

int  /*__stdcall*/ subtract(int a, int b)
{
	return a - b;
}

编译生成后使用dumpbin命令查看导出函数有发生名字改编,规则也符合 动态链接库(二)–动态链接库的创建 中提到的C编译器下__stdcall的改编规则:
动态链接库--dll使用示例_第2张图片

这样就生成了一个C编译器下__stdcall调用约定的动态链接库Dll1.dll了,下面将在不同项目中使用该dll.

对外提供文件:Dll1.h、Dll1.lib、Dll1.dll
放置路径: Dll1.h和Dll1.lib可放在项目(.vcxproj)同级目录下, Dll1.dll可放在输出exe文件的Debug目录下.
lib导入方式:统一为在项目属性-》链接器-》输入-》附加依赖项中配置.
使用:包含Dll1.h,调用dll中函数.

在vs2010的C项目中使用Dll1.dll

先将Dll1.h、Dll1.lib、Dll1.dll添加到C项目中相应目录下,再看下C项目CTest的属性配置中默认调用约定为__cdecl, 即和从Dll1.dll中的导出函数的调用约定__stdcall 不一致:
动态链接库--dll使用示例_第3张图片

在属性配置-》链接器-》输入-》附加依赖项中添加导入库文件Dll1.lib,如下:
动态链接库--dll使用示例_第4张图片

在main.c 中调用如下:

#include 
#include 
#include "Dll1.h"

int main()
{
	printf("2 + 3 = %d\n",add(2, 3));
	printf("5 - 2 = %d\n", subtract(5, 2));

	system("pause");
	return;
}

编译发现有以下错误提示:
动态链接库--dll使用示例_第5张图片
这里编译失败是因为DLL1项目默认的调用约定是__stdcall, 而C项目CTest默认的调用约定是__cdecl, 所以这里在CTest项目中调用add和subtract时,会以__cdecl调用约定去调用, 相当于在CTest项目中, Dll1.h中的函数声明被理解成是__cdecl调用约定的, 这里就和DLL1项目中实现不一致了,前面有说过全局函数声明和实现的调用约定必须一致, 这里不一致, 所以导致就找不到add和subtract两个函数的实现了.

这里可以通过修改CTest项目中包含的Dll1.h头文件,显示的指定两个从Dll1.dll中导入函数的调用约定为_stdcall, 如下:

//CTest项目中的Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API __declspec(dllimport) 
#endif

#ifdef __cplusplus
extern "C"
{
#endif
	int DLL1_API __stdcall add(int a, int b);
	
	int DLL1_API __stdcall subtract(int a, int b);

#ifdef __cplusplus
};
#endif

再次编译运行:
动态链接库--dll使用示例_第6张图片

结论:
在vs2010中,使用C编译生成Dll1.dll,即使发生名字改编,也可在不同的调用约定的C项目中,使用DLL1.h中声明的原始函数名调用.

这里使用dumpbin –imports CTest.exe 查看CTest.exe中从其他dll导入了哪些函数:
动态链接库--dll使用示例_第7张图片
发现从Dll1.dll导入的函数确实是有发生不同调用约定导致的名字改编的,说明确实是能通过原始函数名调用发生名字改编后的函数的.

这是因为在CTest项目包含的Dll1.h头文件中,显式的指明了C编译和__stdcall调用约定, 因此通过原始函数名调用时就会以 C编译__stdcall调用约定的改编规则名_add@8 和 _subtract@8 去dll查找对应实现.

*下面示例都可通过dumpbin –imports .exe 查看导入函数.

需要注意的是:
上面的DLL1项目因为在项目属性中指定了默认的__stdcall调用约定,因此在Dll1.h 和 Dll1.cpp的声明实现中就没有指定__stdcall了.

这样的问题就是在使用Dll1.dll的项目中需人为手动的在包含的Dll1.h的函数声明中显示的添加__stdcall调用约定,因为我们指定该函数是以__stdcall调用约定实现的.

因此后续的示例中都需人为手动的在包含的Dll1.h的函数声明中显示的添加__stdcall调用约定!

这里我们就可以预想到,即使在DLL项目的属性中指定了默认的调用约定,在函数声明实现时也应该显示的添加调用约定声明(因为也可声明实现其他调用约定的函数,例默认__stdcall,也可在该DLL项目中声明实现__cdecl的导出函数),以告知使用该函数的DLL使用者函数的具体调用约定.

在vs2010的C++项目中使用Dll1.dll

同上面的C项目一样,先将Dll1.h、Dll1.lib、Dll1.dll添加到C++项目中相应目录下,再看下C++项目CplusTest的属性配置中默认调用约定为__cdecl, 即和从Dll1.dll中的导出函数的调用约定__stdcall 不一致:
动态链接库--dll使用示例_第8张图片

在CplusTest项目的main.cpp中添加调用代码:

#include 
using namespace std;

#include "Dll1.h"
//#pragma comment(lib, "Dll1.lib")

int main()
{
	cout << "add: " << add(5, 3) << endl;
	cout << "subtract: " << subtract(5, 3) << endl;

	system("pause");
	return 0;
}

这里可以提一下自己测试的时候遇到的小问题,在手动在Dll1.h中添加显示的__stdcall调用声明后,编译会有如下错误提示:
动态链接库--dll使用示例_第9张图片
_subtract@8 和 _add@8 是改编后的函数名,这里提示无法解析的外部符号,表明没有找到两个函数的实现,刚开始还以为在C++项目中没法使用C编译生成的dll中有发生名字改编的导出函数,后来又用vs2013新建了一个C++项目,同样的dll和调用代码,在vs2013中就能够正常调用.

这里就迷糊了,为什么都是同一个dll,都有发生名字改编,为什么vs2013中的C++项目就可以通过原始函数名正常调用,而vs2010就不能.

当时甚至怀疑是不同的VS版本导致的,当然这无从查证,好在后面在C项目注释掉的通过#pragma commnet 导入库文件的语句,才想起来vs2010的C++项目中没有导入lib库文件,因为当时弄了很多个测试项目,一会用#pragma导入,一会在项目属性的链接器中导入,给自己弄迷糊了,哈哈哈,所有后面就统一成通过项目属性中的链接器中导入了.

回到正文,这里在属性配置-》链接器-》输入-》附加依赖项中添加导入库文件Dll1.lib,如下:
动态链接库--dll使用示例_第10张图片

编译运行是可以正常调用的:
动态链接库--dll使用示例_第11张图片

结论:
在vs2010的C++项目中,使用vs2010的C编译生成Dll1.dll。即使发生名字改编,也可在不同的调用约定的C++项目中,使用DLL1.h中声明的原始函数名调用。

为什么可以? 理由同前, 因为在cplusTest项目包含的Dll1.h头文件中, 显式的指定了extern “C” 编译和__stdcall调用约定.

因为后面的vs2010的MFC项目, vs2013的C, C++, MFC项目包含的Dll1.h头文件都有显式指定, 所以都是能通过原始函数名正常调用的!

在vs2010的MFC项目中使用Dll1.dll

新建一个简单的对话框程序,默认调用约定为__cdecl, 在对话框中添加add 和 subtract按钮,在这两个按钮的消息响应函数(窗口过程)中调用从dll导入的add和subtract.

首先依旧是将Dll1.dll配置到MFC项目中,步骤同前,这里不再赘述.

MFC项目对话框如下:
动态链接库--dll使用示例_第12张图片

两个按钮的响应函数如下:

#include "Dll1.h"

void CDllTestDlg::OnBnClickedBtnAdd()
{
	CString strAdd;
	strAdd.Format(_T("add: %d"), add(2, 3));
	AfxMessageBox(strAdd);
}

void CDllTestDlg::OnBnClickedBtnSub()
{
	CString strSub;
	strSub.Format(_T("subtract: %d"), subtract(5, 2));
	AfxMessageBox(strSub);
}

编译运行,点击add 和 subtract按钮,会弹出对应接口的运行结果的窗口:
动态链接库--dll使用示例_第13张图片

结论:
在vs2010的MFC项目中,也可使用C编译生成的不同调用约定的Dll1.dll。
即使调用约定不同发生了名字改编,也可通过原始函数名正常调用。

在vs2013的C项目中使用Dll1.dll

在vs2013中新建一个空项目,和在vs2010创建c项目一样,在添加新建项时选择 cpp文件,输入main.c即可,默认调用约定为__cdecl.

新建好项目后先编译运行生成Debug目录用以存放Dll1.dll,将Dll1.lib和Dll1.h两个文件也放置到相应目录后,在项目属性-》链接器 –》输入-》附加依赖项中添加lib文件地址,然后在main.c中添加以下测试代码:

#include 
#include "Dll1.h"
#include "CPlusDll.h"

int main()
{
	printf("add: %d\n", add(5, 3));
	printf("sub: %d\n", subtract(5, 3));
	getchar();
	return 0;
}

手动在Dll1.h的函数声明中添加显示的__stdcall调用约定后,编译运行可正常调用:
动态链接库--dll使用示例_第14张图片

结论:
在vs2013的C项目中,也可使用vs2010用C编译生成的不同调用约定的Dll1.dll。
即使调用约定不同发生了名字改编,也可通过原始函数名正常调用。

在vs2013的C++项目中使用Dll1.dll

在vs2013中新建一个C++项目CPlusTest,默认调用约定为__cdecl, 新建后编译运行一次,再将Dll1配置CPlusTest项目中,步骤同前,在main.cpp中添加如下代码:

#include 
using namespace std;
#include "Dll1.h"
int main()
{
	cout << "add: " << add(5, 3) << endl;
	cout << "sub: " << subtract(5, 3) << endl;

	system("pause");
	return 0;
}

手动在Dll1.h的函数声明中添加显示的__stdcall调用约定后,编译运行可正常调用:
动态链接库--dll使用示例_第15张图片

结论:
在vs2013的C++项目中,也可使用vs2010用C编译生成的不同调用约定的Dll1.dll.
即使调用约定不同发生了名字改编,也可通过原始函数名正常调用.

在vs2013的MFC项目中使用Dll1.dll

在vs2013中新建一个基于对话框的MFC程序,默认调用约定为__cdecl, 对话框中添加add和subtract按钮,在两个按钮的消息响应函数中调用对应的从dll中导入的接口.

新建完后依旧先编译运行一次,然后配置Dll1.dll到对话框项目中.
动态链接库--dll使用示例_第16张图片

响应函数如下:

#include "Dll1.h"
void CMFCTestDlg::OnBnClickedBtnAdd()
{
	// TODO:  在此添加控件通知处理程序代码
	CString strAdd;

	strAdd.Format(_T("add: %d"), add(2, 3));
	AfxMessageBox(strAdd);
}


void CMFCTestDlg::OnBnClickedBtnSubtract()
{
	// TODO:  在此添加控件通知处理程序代码
	CString strSub;
	strSub.Format(_T("subtract: %d"), subtract(5, 2));
	AfxMessageBox(strSub);
}

手动在Dll1.h的函数声明中添加显示的__stdcall调用约定后,编译运行也可正常调用:
动态链接库--dll使用示例_第17张图片

结论:
在vs2013的MFC项目中,也可使用vs2010用C编译生成的不同调用约定的Dll1.dll.
即使调用约定不同发生了名字改编,也可通过原始函数名正常调用.

vs2013生成的DLL

VS2013版本
Microsoft Visual Studio Ultimate 2013
版本 12.0.40629.00 Update 5

新建一个DLL项目,命名为CPlusDll,项目默认调用约定为__stdcall, 使用extern “C” 限定C编译环境,使用上述vs2013版本生成.
动态链接库--dll使用示例_第18张图片

代码如下:

//CPlusDll.h
#ifdef CPLUS_DLL_API
#else
#define CPLUS_DLL_API __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C"
{
#endif
	int CPLUS_DLL_API __stdcall add_cplus(int a, int b);
	int CPLUS_DLL_API __stdcall sub_cplus(int a, int b);


#ifdef __cplusplus
};
#endif

//CPlusDll.cpp
#define CPLUS_DLL_API __declspec(dllexport)
#include "CPlusDll.h"

int CPLUS_DLL_API __stdcall add_cplus(int a, int b)
{
	return a + b;
}

int CPLUS_DLL_API __stdcall sub_cplus(int a, int b)
{
	return a - b;
}

编译生成, 使用dumpbin命令查看导出函数信息:
动态链接库--dll使用示例_第19张图片

同使用vs2010的C编译生成的Dll1.dll一样,下面分别在vs2010的C、C++以及MFC项目中,vs2013的C、C++以及MFC项目中调用该dll,测试使用原始函数名能否成功调用发生名字改编的_add_cplus@8和_sub_cplus@8函数(同Dll1.dll一样也都是可正常调用的).

直接借助上面的2010的C、C++、MFC以及vs2013的C、C++、MFC项目即可,将CPlusDll配置到这六个项目中去.

区别是CPlusDll.h 中已经显示的添加__stdcall 调用声明了,因此后面无需在相应项目的CPlusDll.h中手动添加.

因配置、代码等同前类似,因此下面都不再赘述

在vs2010的C项目中使用

动态链接库--dll使用示例_第20张图片

在vs2010的C++项目中使用

动态链接库--dll使用示例_第21张图片

在vs2010的MFC项目中使用

动态链接库--dll使用示例_第22张图片

在vs2013的C项目中使用

动态链接库--dll使用示例_第23张图片

在vs2013的C++项目中使用

动态链接库--dll使用示例_第24张图片

在vs2013的MFC项目中使用

动态链接库--dll使用示例_第25张图片

总结

无论是vs2010还是vs2013,只要是C编译生成的Dll,即使发生了不同调用约定导致的名字改编,也都能在其他编译器的项目中通过原始函数名调用.

这是因为在CTest项目包含的Dll1.h头文件中,显式的指明了C编译和__stdcall调用约定, 因此通过原始函数名调用时就会以 C编译__stdcall调用约定的改编规则名_add@8 和 _subtract@8 去dll查找对应实现.

你可能感兴趣的:(动态链接库,C++,microsoft,c++,windows)