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


    vs2010生成DLL

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

    以Dll1项目为例,项目默认调用约定为__stdcall, 使用extern “C” 限定C编译环境,使用上述vs2010版本生成.
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    //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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    编译生成后使用dumpbin命令查看导出函数有发生名字改编,规则也符合 动态链接库(二)–动态链接库的创建 中提到的C编译器下__stdcall的改编规则:
    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 不一致:
    3

    在属性配置-》链接器-》输入-》附加依赖项中添加导入库文件Dll1.lib,如下:
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    编译发现有以下错误提示:
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    再次编译运行:
    6

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

    这里使用dumpbin –imports CTest.exe 查看CTest.exe中从其他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 不一致:
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里可以提一下自己测试的时候遇到的小问题,在手动在Dll1.h中添加显示的__stdcall调用声明后,编译会有如下错误提示:
    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,如下:
    10

    编译运行是可以正常调用的:
    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项目对话框如下:
    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    编译运行,点击add 和 subtract按钮,会弹出对应接口的运行结果的窗口:
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    手动在Dll1.h的函数声明中添加显示的__stdcall调用约定后,编译运行可正常调用:
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    手动在Dll1.h的函数声明中添加显示的__stdcall调用约定后,编译运行可正常调用:
    15

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

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

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

    新建完后依旧先编译运行一次,然后配置Dll1.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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    手动在Dll1.h的函数声明中添加显示的__stdcall调用约定后,编译运行也可正常调用:
    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版本生成.
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    编译生成, 使用dumpbin命令查看导出函数信息:
    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项目中使用

    20

    在vs2010的C++项目中使用

    21

    在vs2010的MFC项目中使用

    22

    在vs2013的C项目中使用

    23

    在vs2013的C++项目中使用

    24

    在vs2013的MFC项目中使用

    25

    总结

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

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

  • 相关阅读:
    Redis高可用(主从复制、哨兵模式和Cluster集群)
    C++中返回类型与return语句
    《006.Springboot+vue之旅游信息推荐系统》【有文档】
    【C++】多态 ⑤ ( 虚析构函数 | 虚析构函数语法 | 虚析构函数意义 | 父类指针指向子类对象情况下父类和子类使用 virtual 虚析构函数 | 代码示例 )
    CA证书和openssl介绍
    HashMap源码验证,在JDK8中新增红黑树详解
    《Java》图书管理系统
    从pcap文件中提取pcma音频
    C#三层架构
    Java#26(常见算法: 排序算法)
  • 原文地址:https://blog.csdn.net/SNAKEpc12138/article/details/126273520