vs2010版本:
Microsoft Visual Studio 2010
版本 10.0.30319.1 RTMRel
以Dll1项目为例,项目默认调用约定为__stdcall, 使用extern “C” 限定C编译环境,使用上述vs2010版本生成.
代码如下:
//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的改编规则:
这样就生成了一个C编译器下__stdcall调用约定的动态链接库Dll1.dll了,下面将在不同项目中使用该dll.
对外提供文件:Dll1.h、Dll1.lib、Dll1.dll
放置路径: Dll1.h和Dll1.lib可放在项目(.vcxproj)同级目录下, Dll1.dll可放在输出exe文件的Debug目录下.
lib导入方式:统一为在项目属性-》链接器-》输入-》附加依赖项中配置.
使用:包含Dll1.h,调用dll中函数.
先将Dll1.h、Dll1.lib、Dll1.dll添加到C项目中相应目录下,再看下C项目CTest的属性配置中默认调用约定为__cdecl, 即和从Dll1.dll中的导出函数的调用约定__stdcall 不一致:
在属性配置-》链接器-》输入-》附加依赖项中添加导入库文件Dll1.lib,如下:
在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;
}
编译发现有以下错误提示:
这里编译失败是因为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
结论:
在vs2010中,使用C编译生成Dll1.dll,即使发生名字改编,也可在不同的调用约定的C项目中,使用DLL1.h中声明的原始函数名调用.
这里使用dumpbin –imports CTest.exe 查看CTest.exe中从其他dll导入了哪些函数:
发现从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使用者函数的具体调用约定.
同上面的C项目一样,先将Dll1.h、Dll1.lib、Dll1.dll添加到C++项目中相应目录下,再看下C++项目CplusTest的属性配置中默认调用约定为__cdecl, 即和从Dll1.dll中的导出函数的调用约定__stdcall 不一致:
在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调用声明后,编译会有如下错误提示:
_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,如下:
结论:
在vs2010的C++项目中,使用vs2010的C编译生成Dll1.dll。即使发生名字改编,也可在不同的调用约定的C++项目中,使用DLL1.h中声明的原始函数名调用。
为什么可以? 理由同前, 因为在cplusTest项目包含的Dll1.h头文件中, 显式的指定了extern “C” 编译和__stdcall调用约定.
因为后面的vs2010的MFC项目, vs2013的C, C++, MFC项目包含的Dll1.h头文件都有显式指定, 所以都是能通过原始函数名正常调用的!
新建一个简单的对话框程序,默认调用约定为__cdecl, 在对话框中添加add 和 subtract按钮,在这两个按钮的消息响应函数(窗口过程)中调用从dll导入的add和subtract.
首先依旧是将Dll1.dll配置到MFC项目中,步骤同前,这里不再赘述.
两个按钮的响应函数如下:
#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按钮,会弹出对应接口的运行结果的窗口:
结论:
在vs2010的MFC项目中,也可使用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调用约定后,编译运行可正常调用:
结论:
在vs2013的C项目中,也可使用vs2010用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调用约定后,编译运行可正常调用:
结论:
在vs2013的C++项目中,也可使用vs2010用C编译生成的不同调用约定的Dll1.dll.
即使调用约定不同发生了名字改编,也可通过原始函数名正常调用.
在vs2013中新建一个基于对话框的MFC程序,默认调用约定为__cdecl, 对话框中添加add和subtract按钮,在两个按钮的消息响应函数中调用对应的从dll中导入的接口.
新建完后依旧先编译运行一次,然后配置Dll1.dll到对话框项目中.
响应函数如下:
#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调用约定后,编译运行也可正常调用:
结论:
在vs2013的MFC项目中,也可使用vs2010用C编译生成的不同调用约定的Dll1.dll.
即使调用约定不同发生了名字改编,也可通过原始函数名正常调用.
VS2013版本
Microsoft Visual Studio Ultimate 2013
版本 12.0.40629.00 Update 5
新建一个DLL项目,命名为CPlusDll,项目默认调用约定为__stdcall, 使用extern “C” 限定C编译环境,使用上述vs2013版本生成.
代码如下:
//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;
}
同使用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还是vs2013,只要是C编译生成的Dll,即使发生了不同调用约定导致的名字改编,也都能在其他编译器的项目中通过原始函数名调用.
这是因为在CTest项目包含的Dll1.h头文件中,显式的指明了C编译和__stdcall调用约定, 因此通过原始函数名调用时就会以 C编译__stdcall调用约定的改编规则名_add@8 和 _subtract@8 去dll查找对应实现.