c++中创建与调用dll

2023-11-09


好处想必不用说了,所谓的黑盒复用,实现模块化的同时避免源代码暴露等。可以将某一通用功能做成模块,方便复用,同时软件更新时如果只更新了几个模块,可以更换dll即可,无需完整地更新,便于程序拓展。以下教程基于VS2015,其他的版本类似。文章参考 链接

1.dll的创建
  1. 创建项目。在vs中新建win32应用程序,名称为任意给定,如MyDll之类的,概述点击下一步,应用程序设置中的应用程序类型选择DLL(D),完成。

  2. 添加头文件。在项目-添加新项-**Visaul C++**中选择头文件,设置文件名如MyDll.h。

  3. 在头文件中添加如下代码:

    #ifdef MYDLL_EXPORTS
    #define MYDLL_API __declspec(dllexport)//注意decl前面是两个下划线
    #else
    #define MYDLL_API __declspec(dllimport)
    #endif
    
    namespace MyDllSpace
    {
    	//导出类
    	class MyDllClass
    	{
    	private:
    		double a;
    	public:
    		//导出的函数
    		MYDLL_API MyDllClass();
    		MYDLL_API MyDllClass(double a_);
    		MYDLL_API double multiply(double b);
    		MYDLL_API void display();
    		MYDLL_API static void conbine(MyDllClass m1, MyDllClass m2);
    	};
    }
    

    注意: 因为该头文件既被dll的源文件包含,又被使用方包含,所以开头使用条件编译——当被dll源文件包含时(#ifdef MYDLL_EXPORTS,因为vs生成的DLL项目默认都定义了DLL名称_EXPORTS这个宏),导出的函数前要加__declspec(dllexport)宏;如果是被使用方包含,则导出函数前要加__declspec(dllimport)

  4. 在源文件中添加如下代码:

    #include "MyDll.h"
    #include <iostream>
    
    MyDllSpace::MyDllClass::MyDllClass()
    {
    	a = 0;
    }
    
    MyDllSpace::MyDllClass::MyDllClass(double a_)
    {
    	a = a_;
    }
    
    MYDLL_API double MyDllSpace::MyDllClass::multiply(double b)
    {
    	return a*b;
    }
    
    MYDLL_API void MyDllSpace::MyDllClass::display()
    {
    	std::cout << "a=" << a << std::endl;
    }
    
    MYDLL_API void MyDllSpace::MyDllClass::conbine(MyDllClass m1, MyDllClass m2)
    {
    	std::cout << "(" << m1.a << "," << m2.a << ")" << std::endl;
    }
    

    注意: 这里的函数是通过在头文件对函数快速操作在源文件里自动生成的定义(当然函数体还是得自己写),自己码的话可以用命名空间MyDllSpace把这些定义括起来,这样不必每次都用命名空间限定是哪一个空间里的函数定义了

  5. 点击菜单栏的生成-生成解决方案,即创建dll完成,在项目的Debug目录下可以看到生成的dll和lib文件。

2.dll的使用
  1. 点击文件-新建-项目,选择win32控制台应用程序,名称任意设,如UseMyDll,确定,下一步,在应用程序设置中将附加选项中的预编译头多选框去除选中,完成。

  2. 将上一步生成的dll、lib、.h头文件复制到项目目录中,在解决方案资源管理器中添加现有项将其加入项目。

  3. 在UseMyDll.cpp文件中添加如下代码:

    #include <iostream>
    #include "MyDll.h"//加入模块的头文件
    
    using namespace std;
    
    int main()
    {
    	double a = 8;
    	MyDllSpace::MyDllClass c(4);//创建模块里的类实例
    	MyDllSpace::MyDllClass c2;
    	c.display();//使用实例方法
    	cout << "8*4=" << c.multiply(a) << endl;
    	MyDllSpace::MyDllClass::conbine(c, c2);//使用模块里的类的静态方法
    	system("pause");//暂停以便能看到输出
        return 0;
    }
    

    输出如下:
    在这里插入图片描述

注意:

  1. 如果新建项目时选择将项目添加到解决方案,即在dll解决方案下新建方案,则可以添加dll的引用——直接右击解决方案资源管理器里的引用,添加引用,将之前创建的dll添加引用(这也是官方教程里的用法)——而不用将dll放到当前文件夹下,并添加到项目里。但是因为我们假设的是更一般的情况,复用的代码要求对方非得在自己项目下不太现实。
  2. 头文件也不是必须添加到当前项目里,只是为了方便找到。也可以在项目-属性-C/C++-常规下的附加包含目录里添加头文件所在目录,甚至可以把头文件放在系统包含目录里,只要包含头文件时能找到就可以。另外.lib文件也不是必须加到项目下,也可以通过添加.lib所在文件夹为库目录,然后将.lib文件添加为附加依赖项。
3.仅使用dll(显式链接)

上述方法为使用自己写的dll,但是如果只有别人给的一个dll不含其它任何信息怎么使用?步骤1,2中DLL调用方式为隐式链接(调用),另外有一种显式链接,不需要头文件和lib文件。显式链接可以在程序执行过程中随时加载DLL也可以随时卸载,更具有灵活性,适合解释型语言,当然使用也要更麻烦些。VS在VC\bin目录下提供了一个dumpbin.exe程序来查看DLL文件中的函数结构。参考链接

  1. 如步骤1创建一个dll项目

    头文件代码如下:

    //FuncOnlyDll.h
    #ifdef FUNCONLYDLL_EXPORTS
    #define FUNCONLYDLL_API __declspec(dllexport)
    #else
    #define FUNCONLYDLL_API __declspec(dllimport)
    #endif
    
    namespace Funcs
    {
    	FUNCONLYDLL_API double minValue(double a, double b);
    	FUNCONLYDLL_API double maxValue(double a, double b);
    	FUNCONLYDLL_API double add(double a, double b);
    	FUNCONLYDLL_API double subtract(double a, double b);
    }
    

    源代码如下:

    //FuncOnlyDll.cpp
    #include "FuncOnlyDll.h"
    
    FUNCONLYDLL_API double Funcs::minValue(double a, double b)
    {
    	return a < b ? a : b;
    }
    
    FUNCONLYDLL_API double Funcs::maxValue(double a, double b)
    {
    	return a > b ? a : b;
    }
    
    FUNCONLYDLL_API double Funcs::add(double a, double b)
    {
    	return a + b;
    }
    
    FUNCONLYDLL_API double Funcs::subtract(double a, double b)
    {
    	return a - b;
    }
    

    点击菜单栏中生成-生成解决方案,打开项目目录生成dll所在的文件夹,使用dumpbin.exe查看下dll中函数结构,如下图:
    在这里插入图片描述

    可以看到有四个函数,但是显示的就像乱码,顶多只能看出每个函数叫啥,这样对于使用的用户来说是不友好的,因为看一堆乱码是无法知道这些函数是干啥的,有哪些参数和返回值,这样是无法使用的,我们可以在菜单栏项目-添加新项-Visaul C++-代码选择模块定义文件(.def),设置文件名,在添加的def文件中给出如下代码:

    LIBRARY
    EXPORTS
    double_add_double_double=?add@Funcs@@YANNN@Z
    double_maxValue_double_double=?maxValue@Funcs@@YANNN@Z
    double_minValue_double_double=?minValue@Funcs@@YANNN@Z
    double_subtract_double_double=?subtract@Funcs@@YANNN@Z
    

    其中后面的乱码对应之前dump出来的dll函数名乱码,写这个def文件相当于把乱码重新定义名字,让它更好识别,这里我定义的规则是返回类型_函数名_参数列表类型,当然你可以定义自己的规则,这样使用者一看就知道这个函数干啥的,有啥参数和返回值,再dump一下dll看看:

在这里插入图片描述

发现后面四个函数输出了我们想要的结果,但是前面仍然有四个乱码函数(其实是同样四个函数),可以将头文件中的*#define FUNCONLYDLL_API __declspec(dllexport)改成#define FUNCONLYDLL_API*这样就不会输出前面的乱码(但是这样会使得dll使用隐式链接出错):

在这里插入图片描述

至此,dll创建完毕。

  1. 新建一个项目使用创建的dll

    显式链接是不用配置项目属性的,直接将dll文件放在项目目录下,或是其他能查找到的目录比如系统path路径之类的,代码如下:

    #include <iostream>
    //为了使用LoadLibrary和FreeLibrary等函数以及LPCWSTR和LPCSTR类型加入的头文件
    #include <Windows.h>
    using namespace std;
    //定义要加载的各种函数指针,通过dumpbin查看dll函数中函数签名从而得知,或者查看API
    typedef double(*pMin)(double, double);
    typedef double(*pMax)(double, double);
    typedef double(*pAdd)(double, double);
    typedef double(*pSub)(double, double);
    int main()
    {
    	LPCWSTR dllName = TEXT("FuncOnlyDll.dll");
    	LPCSTR funName1 = "double_minValue_double_double";
    	LPCSTR funName2 = "double_maxValue_double_double";
    	LPCSTR funName3 = "double_add_double_double";
    	LPCSTR funName4 = "double_subtract_double_double";
    	HMODULE hDll = LoadLibrary(dllName);//加载dll
    	if (hDll != NULL) {
    		pMin fp1 = (pMin)(GetProcAddress(hDll, funName1));//加载函数
    		if (fp1 != NULL) {
    			cout << "min value of 5 and 6:" << fp1(5, 6) << endl;
    		}
    		else {
    			cout << "函数min加载失败" << endl;
    		}
    		pMax fp2 = (pMax)(GetProcAddress(hDll, funName2));
    		if (fp2 != NULL) {
    			cout << "max value of 5 and 6:" << fp2(5, 6) << endl;
    		}
    		else {
    			cout << "函数max加载失败" << endl;
    		}
    		pAdd fp3 = (pAdd)(GetProcAddress(hDll, funName3));
    		if (fp3 != NULL) {
    			cout << "sum value of 5 and 6:" << fp3(5, 6) << endl;
    		}
    		else {
    			cout << "函数add加载失败" << endl;
    		}
    		pSub fp4 = (pSub)(GetProcAddress(hDll, funName4));
    		if (fp2 != NULL) {
    			cout << "difference of 5 and 6:" << fp4(5, 6) << endl;
    		}
    		else {
    			cout << "函数sub加载失败" << endl;
    		}
    		FreeLibrary(hDll);//卸载dll,一般用完就卸载,避免占用内存
    	}
    	else {
    		cout << "加载dll文件失败" << endl;
    	}
    	system("pause");
        return 0;
    }
    

    从上述使用方法可以看出,因为我们之前创建dll文件的时候贴心地把导出的函数名设置了下,才可以在dumpbin中查看到比较有意义的函数名信息,所以如果第三方给的dll没有这么做,那么你就根据dump出来的函数名乱码猜吧,祝你好运。

  2. 显式链接的不足

    显然,如果只给了dll没给接口(头文件或者相应的API文档),而且dump出来函数名是乱码,是没法复用dll的。另外dll的显式链接是不支持导出类和变量的,至少没人建议这么做。


4.一点小的建议

经过看完网上各种教程强烈建议如果dll复用的话一定使用.h,.lib,.dll三件套,即隐式链接方式

其他任何解决办法都不好使,因为dll隐藏了文件中的函数结构,不知道函数结构是没法用的

即使通过dumbin.exe也只能探知到一些看起来像乱码的函数名,是无法了解这个函数具体干啥的

除非已经知道了接口,比如隐式链接,使用了dll对应源码的头文件,显然是暴露接口的一种方式

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++中创建与调用dll 的相关文章

随机推荐

  • 【概率论与数理统计】猴博士 笔记 p15-16 一、二维连续型求概率

    一维连续型求概率 题型如下 解题步骤如下 其实就是求积分 举例1的例子 例2 解 例3 解 注意 要把Y变为X计算 且要分类讨论y是否大于0 例4 解 去掉max和min的方法 去掉多余项的方法 假设要求AB两项同时发生的概率 当多余项 A
  • kafka使用_使用多线程增加kafka消费能力

    原创 小姐姐味道 微信公众号ID xjjdog 欢迎分享 转载请保留出处 前提 本例适合那些没有顺序要求的消息主题 kafka通过一系列优化 写入和读取速度能够达到数万条 秒 通过增加分区数量 能够通过部署多个消费者增加并行消费能力 但还是
  • order by产生的 Using temporary的优化

    今天遇到一个慢查询的sql sql如下 EXPLAIN SELECT tas f year tc pk id tas f sex tas f rank score tas f age tas f km five tas f km ten t
  • filter过滤器实现权限访问控制以及同一账号只能登录一台设备

    需求 如题目所意 未登录用户不能浏览访问项目内部的资源 对访问的请求和响应进行拦截 且一个用户只能在一台设备登录 权限访问控制功能可以通过过滤器或者拦截器去实现 在这里我用的是过滤器 过滤器可以过滤全部action请求 拦截器则更有针对性
  • unity通过键盘控制物体移动,大小的缩放

    拖动距离 private float distance 10 缩放量 float scale 0 2f 通过键盘 Q 或者 E 控制物体的缩放 通过键盘 w s a d 控制物体上下左右的移动 private void Update if
  • PWM实现线性调光

    1 PWM调光原理 PWM全称为脉宽调制技术 是通过高精度的计数器对方波的占空比进行编码 就是这个东西 其实很好理解 高电平的时候才会做功 低电平的时候肯定不亮啊 PWM就是调制高电平的占比 其实一般是低电平才有效 因为会外接一个12V的电
  • XFocus Windows Internet 服务器安全配置

    Windows 2003版本区别 1 Windows Server 2003 Standard Edition 标准版 针对中小型企业的核心产品 他也是支持双路处理器 4GB的内存 它除了具备 Windows Server 2003 Web
  • Revit SDK下载地址

    20190325更新 共享了如下sdk REVIT 2014 SDK exe REVIT2015SDK SubscriptionRelease msi REVIT 2016 SDK msi Revit 2017 1 SDK Update O
  • 树莓派OpenWrt SD扩展问题

    树莓派OpenWrt磁盘扩展 1 查看问题 前几天给树莓派4B刷了OpenWrt当做软路由来使用 发现树莓派的SD卡空间没有完全被使用 有一部分未分区 已用大概只有2GB df h查看 2 fdisk 命令查看磁盘 3 按p查看分区情况 发
  • ansible自动化运维工具上部署lnmp架构

    ansible自动化运维工具上部署lnmp架构 ansible安装 通过ansible连接到192 168 228 20配置nginx安装 本地也要安装nginx 步骤略 安装mysql 安装PHP ansible自动化运维工具上部署lnm
  • Spring默认使用的JSON工具--Jackson

    Spring默认使用的JSON工具 Jackson 一 Jackson介绍 我们常用的json转换工具包括fastJson Gson Jackson等 其中Gson是Google所维护 功能全 fastJson特点是快 但是爆出几次的重大b
  • 2021最新版IDEA右侧Maven模块以及View下的Maven Project不见了解决方法

    问题描述 重新启动了一下IDEA后发现许多jar包找不到 想使用Maven进行依赖刷新 却找不到项目右侧的Maven Project 网上搜索了很多解决方法 如清除IDEA缓存 重启电脑 删除隐藏文件等等 都没有效果 将正确的解决方法记录在
  • Redis集群模式使用Lua脚本的限制

    问题复现 ERR bad lua script for redis cluster all the keys that the script uses should be passed using the KEYS array and KE
  • chapter15:springboot与监控管理

    Spring Boot与监控管理视频 1 简介 通过引入spring boot starter actuator 可以使用SpringBoot为我们提供的准生产环境下的应用监控和管理功能 我们可以通过http jmx ssh协议来进行操作
  • js宏观任务、微观任务

    js运行机制分为同步异步 异步又分为宏观事件和微观事件 同步异步 js是一门单线程语言 因此js在同一个时间里只能做一件事 单线程意味着 如果在同个时间有多个任务的话 这些任务就需要排队 前一个执行完成才能执行下一个任务 同步任务 同步任务
  • Linux之内核级防火墙selinux模块

    一 什么是selinux SELinux Security Enhanced Linux 是美国国家安全局 NSA 对于强制访问控制的实现 是 Linux历史上最杰出的新安全子系统 NSA是在Linux社区的帮助下开发了一种访问控制体系 在
  • c语言小游戏——扫雷

    扫雷是一款经典的单人益智游戏 玩家需要在一个由许多方块组成的棋盘上找出所有的地雷 而不触发任何一颗地雷 int input 0 do menu printf 请选择 gt scanf d input 输入1进入游戏 输入0退出游戏 输入其他
  • 我们总结了每个技术开发团队都会遇到的 4 个难题

    我们整理了一篇 每个技术团队都会遇到的4个难题 帮助即将从校园进入公司实习的后端程序员 以实践的视角 看看一个后端技术团队会遇到的一些难题 虽然 技术上的难题远不止于此 但如果能从这篇文章中获得一些职业体感 也许对你的实习面试会有所帮助 从
  • Python图像处理-3.pil裁剪、旋转粘贴图片

    from PIL import Image import matplotlib pyplot as plt pil im1 Image open pic1 png plt figure girlfriend1 plt imshow pil
  • c++中创建与调用dll

    文章目录 1 dll的创建 2 dll的使用 3 仅使用dll 显式链接 4 一点小的建议 好处想必不用说了 所谓的黑盒复用 实现模块化的同时避免源代码暴露等 可以将某一通用功能做成模块 方便复用 同时软件更新时如果只更新了几个模块 可以更