我想在visual studio2022中使用c语言制作dll应该怎么办,同时如果我写了另一个c语言程序想要使用这个dll中的函数应该怎么编写代码?

好的,在 Visual Studio 2022 中使用 C 语言创建和使用 DLL 的过程可以分为两大步。我会为你提供详细的步骤和代码示例。


第一部分:创建 DLL

步骤 1:创建新项目

  1. 打开 Visual Studio 2022。
  2. 点击“创建新项目”。
  3. 在搜索框中,输入 dynamic,然后选择“动态链接库 (DLL)”模板。如果找不到,也可以选择“空项目”,然后手动配置。
  4. 点击“下一步”,为项目命名(例如 MyMathDLL),选择位置,然后点击“创建”。

步骤 2:编写 DLL 代码

DLL 需要明确指定哪些函数是可供外部程序(称为“客户端”)使用的。我们通过 __declspec(dllexport) 关键字来实现。

在“解决方案资源管理器”中,右键点击“源文件” -> “添加” -> “新建项…”,创建一个名为 mymath.c 的文件。

将以下代码写入 mymath.c

// mymath.c - DLL 源文件#include <stdio.h>// 声明函数为导出函数
// __declspec(dllexport) 是关键,它告诉编译器这个函数需要被导出
__declspec(dllexport) int add(int a, int b) {return a + b;
}__declspec(dllexport) int subtract(int a, int b) {return a - b;
}// 一个只在DLL内部使用的函数(辅助函数),不被导出
void internal_helper_function() {printf("This is an internal function, not exported.\n");
}

步骤 3:创建头文件(可选但强烈推荐)

为了让使用你 DLL 的程序员知道有哪些函数可用以及它们的参数,最好创建一个头文件。

右键点击“头文件” -> “添加” -> “新建项…”,创建一个名为 mymath.h 的文件。

将以下代码写入 mymath.h

// mymath.h - DLL 的头文件// 为了防止头文件被多次包含时重复定义
#pragma once#ifdef __cplusplus
extern "C" {  // 如果被C++程序使用,确保以C语言的方式编译和链接
#endif// 声明导出函数。// 注意:这里用的是 `__declspec(dllexport)`,因为当前在编译DLL本身。__declspec(dllexport) int add(int a, int b);__declspec(dllexport) int subtract(int a, int b);#ifdef __cplusplus
}
#endif

步骤 4:生成 DLL

  1. 在顶部菜单栏,确保解决方案配置是“Release”和“x64”(或者 x86,根据你的目标平台选择,建议初学用 x64)。
  2. 点击“生成” -> “生成解决方案”。
  3. 如果成功,你会在项目目录下的 x64/Release/ 文件夹中找到两个重要文件:
    • MyMathDLL.dll:这就是动态链接库本身,包含了编译后的二进制代码。
    • MyMathDLL.lib:这是导入库,客户端程序在链接时需要这个文件。它包含了帮助程序找到DLL中函数的信息。

第二部分:创建客户端程序使用 DLL

现在,我们来创建另一个程序来调用刚刚生成的 DLL 中的函数。

方法一:隐式链接(最常用、最简单)

这种方法需要在编译时提供 .lib 文件,并在运行时需要 .dll 文件。

步骤 1:创建新的客户端项目

  1. 在当前的 Visual Studio 解决方案中:
    • 右键点击“解决方案 ‘MyMathDLL’” -> “添加” -> “新建项目”。
  2. 选择“控制台应用”模板,命名为 TestDLL,点击“下一步”创建。
  3. 确保新项目 TestDLL 也被设置为 Releasex64(与 DLL 项目保持一致)。

步骤 2:配置客户端项目

我们需要告诉客户端项目在哪里可以找到 DLL 的头文件和导入库(.lib)。

  1. 添加头文件路径:

    • 右键点击 TestDLL 项目 -> “属性”。
    • 在左侧,找到“C/C++” -> “常规”。
    • 在“附加包含目录”中,添加 mymath.h 所在的路径(即 DLL 项目的路径)。例如:..\MyMathDLL
  2. 添加导入库(.lib)路径和文件名:

    • 在属性页,找到“链接器” -> “常规”。
    • 在“附加库目录”中,添加 MyMathDLL.lib 所在的路径(即 x64/Release/)。例如:..\MyMathDLL\x64\Release
    • 找到“链接器” -> “输入”。
    • 在“附加依赖项”中,添加 MyMathDLL.lib

步骤 3:编写客户端代码

TestDLL 项目的源文件(如 TestDLL.c)中,写入以下代码:

// TestDLL.c - 客户端程序// 包含DLL的头文件,这样我们就知道add和subtract函数的原型
#include "mymath.h"
#include <stdio.h>int main() {int a = 10, b = 5;// 直接调用DLL中的函数,就像调用普通函数一样int sum = add(a, b);int difference = subtract(a, b);printf("a = %d, b = %d\n", a, b);printf("a + b = %d\n", sum);printf("a - b = %d\n", difference);return 0;
}

步骤 4:运行客户端程序

  1. 右键点击 TestDLL 项目 -> “设为启动项目”。
  2. Ctrl + F5 运行。
  3. 重要: 确保 MyMathDLL.dll 文件在客户端程序(TestDLL.exe)可以找到的地方。通常有几种方法:
    • MyMathDLL.dll 复制到 TestDLL.exe 所在的目录(即 TestDLL\x64\Release\)。
    • 或者更简单:在 TestDLL 项目的“属性” -> “调试” -> “工作目录”中,设置为DLL所在的路径(..\MyMathDLL\x64\Release)。

方法二:显式链接(更灵活,更复杂)

这种方法在运行时通过 Windows API(LoadLibrary, GetProcAddress)手动加载 DLL 和获取函数地址,不需要 .lib 和头文件(但你需要知道函数的原型)。

客户端代码示例(不需要配置链接器):

#include <windows.h>
#include <stdio.h>// 定义函数指针类型,必须与DLL中的函数原型完全匹配
typedef int(*ADD_FUNC)(int, int);
typedef int(*SUBTRACT_FUNC)(int, int);int main() {HINSTANCE hDll;ADD_FUNC add_func;SUBTRACT_FUNC subtract_func;int a = 10, b = 5;// 1. 加载DLLhDll = LoadLibrary(TEXT("MyMathDLL.dll"));if (hDll == NULL) {printf("Failed to load DLL!\n");return 1;}// 2. 获取函数地址add_func = (ADD_FUNC)GetProcAddress(hDll, "add");subtract_func = (SUBTRACT_FUNC)GetProcAddress(hDll, "subtract");if (add_func == NULL || subtract_func == NULL) {printf("Failed to get function address!\n");FreeLibrary(hDll);return 1;}// 3. 使用函数int sum = add_func(a, b);int difference = subtract_func(a, b);printf("a = %d, b = %d\n", a, b);printf("a + b = %d\n", sum);printf("a - b = %d\n", difference);// 4. 卸载DLLFreeLibrary(hDll);return 0;
}

使用这种方法,你只需要确保 MyMathDLL.dll 在程序可找到的路径(如同一目录)即可,不需要 .lib 文件和头文件(在编译时)。


总结

特性隐式链接显式链接
易用性,像调用普通函数一样,需要手动处理加载和函数指针
所需文件需要头文件 (.h) 和导入库 (.lib) 用于编译只需要知道函数原型
灵活性低,启动时如果找不到DLL则失败,可以在运行时决定加载哪个DLL,优雅处理错误
性能稍快(函数调用直接)稍慢(需要通过指针调用)

对于大多数情况,隐式链接是更直接和首选的方法。显式链接则在需要动态加载插件、处理不同版本DLL或需要更精细的错误控制时非常有用。

HINSTANCE是什么?

HINSTANCE 是 Windows API 中的一个核心数据类型,全称是 Handle to an Instance(实例句柄)。

本质是什么?

HINSTANCE 实际上是一个句柄(Handle),它本质上是一个数值(在32位系统中是32位,64位系统中是64位),用来唯一标识和引用一个加载到内存中的模块(如DLL、EXE)。

主要用途

在显式链接DLL时,HINSTANCE 用来:

  1. 代表已加载的DLL模块

    HINSTANCE hDll = LoadLibrary(TEXT("MyMathDLL.dll"));
    // 此时 hDll 就是 MyMathDLL.dll 在内存中的"身份证"
    
  2. 作为参数传递给 GetProcAddress() 来获取函数地址

    ADD_FUNC add_func = (ADD_FUNC)GetProcAddress(hDll, "add");
    // 告诉系统:在 hDll 这个模块中找名为 "add" 的函数
    
  3. 最后用于卸载DLL

    FreeLibrary(hDll);  // 通过句柄告诉系统卸载哪个DLL
    

底层细节

  • 32位系统HINSTANCEvoid* 类型,占4字节
  • 64位系统HINSTANCEvoid* 类型,占8字节
  • 实际定义:typedef HMODULE HINSTANCE;HMODULE 是模块句柄)

简单类比

你可以把 HINSTANCE 理解为:

  • 文件操作中的文件句柄(就像 FILE*
  • 指针的指针:它不直接指向DLL代码,而是指向一个系统内部管理该DLL的数据结构

实际使用示例

#include <windows.h>
#include <stdio.h>typedef int(*ADD_FUNC)(int, int);int main() {// 1. 加载DLL,返回该DLL的实例句柄HINSTANCE hMyDll = LoadLibrary(TEXT("MyMathDLL.dll"));if (hMyDll == NULL) {printf("无法加载DLL! 错误代码: %d\n", GetLastError());return 1;}// 2. 使用句柄来获取函数地址ADD_FUNC add = (ADD_FUNC)GetProcAddress(hMyDll, "add");if (add == NULL) {printf("找不到函数! \n");FreeLibrary(hMyDll);return 1;}// 3. 使用获取到的函数int result = add(10, 5);printf("10 + 5 = %d\n", result);// 4. 卸载DLL,传入要卸载的DLL的句柄FreeLibrary(hMyDll);return 0;
}

总结

HINSTANCE 是 Windows 系统中用来标识和管理加载到内存中的模块(如DLL)的句柄,在显式链接DLL时起着关键作用,相当于你与操作系统之间关于该DLL的"通信凭证"。

客户端代码中,下面两行代码是什么意思:
ADD_FUNC add_func;
SUBTRACT_FUNC subtract_func;

这两行代码是函数指针变量的声明

详细解释

1. 代码含义

ADD_FUNC add_func;
SUBTRACT_FUNC subtract_func;
  • 声明了两个变量add_funcsubtract_func
  • 变量类型ADD_FUNCSUBTRACT_FUNC(之前用 typedef 定义的类型)
  • 用途:这两个变量将用来存储从DLL中获取的函数地址

2. 类比理解

可以类比为普通的变量声明:

int number;          // 声明一个整型变量
ADD_FUNC add_func;   // 声明一个函数指针变量
普通变量声明函数指针声明含义
int number;ADD_FUNC add_func;声明一个变量
number 存储整数add_func 存储函数地址变量存储的内容
number = 10;add_func = GetProcAddress(...);给变量赋值

3. 在完整代码中的角色

// 1. 定义函数指针类型(蓝图)
typedef int(*ADD_FUNC)(int, int);
typedef int(*SUBTRACT_FUNC)(int, int);int main() {// 2. 声明函数指针变量(创建容器)ADD_FUNC add_func;SUBTRACT_FUNC subtract_func;// 3. 给变量赋值(获取真正的函数地址)add_func = (ADD_FUNC)GetProcAddress(hDll, "add");subtract_func = (SUBTRACT_FUNC)GetProcAddress(hDll, "subtract");// 4. 使用变量(通过指针调用函数)int result = add_func(10, 5);return 0;
}

4. 另一种写法:声明时直接赋值

你也可以在声明的同时赋值:

// 声明并立即赋值
ADD_FUNC add_func = (ADD_FUNC)GetProcAddress(hDll, "add");
SUBTRACT_FUNC subtract_func = (SUBTRACT_FUNC)GetProcAddress(hDll, "subtract");

5. 为什么要这样写?

这样设计的好处是:

  • 类型安全:编译器会检查函数签名是否匹配
  • 代码清晰:通过类型名就知道这个指针指向什么类型的函数
  • 易于使用:可以像调用普通函数一样调用 add_func(10, 5)

总结

ADD_FUNC add_func; 这行代码的意思是:声明一个名为 add_func 的变量,这个变量的类型是 ADD_FUNC(即指向一个接受两个int参数并返回int的函数的指针),用来存储从DLL中获取的 add 函数的地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/98112.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/98112.shtml
英文地址,请注明出处:http://en.pswp.cn/web/98112.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch集群节点部署与索引策略对比分析及性能优化

Elasticsearch集群节点部署与索引策略对比分析及性能优化 本文深入探讨了Elasticsearch在生产环境中不同集群节点部署模式与索引策略的多种方案&#xff0c;比较了各自优缺点&#xff0c;并结合真实业务场景的性能测试结果&#xff0c;提出选型建议与优化实践&#xff0c;帮助有…

神经网络与深度学习基础:从线性回归到分类模型

主题12​​核心模型​​线性回归 → 神经网络Softmax回归​​解决问题​​回归问题&#xff08;预测连续值&#xff09;分类问题&#xff08;预测离散类别&#xff09;​​关键创新​​引入激活函数解决线性不可分问题引入独热编码和交叉熵损失解决分类问题​​优化算法​​梯度…

前端开发的破局与突围:AI赋能、全栈架构与跨端开发新路径

作为一名前端开发者,你是否曾感到焦虑:技术迭代太快,竞争越来越激烈?别担心,新的机遇正在涌现!本文将带你探索前端开发的新出路。 这里写目录标题 前言:前端开发的现状与挑战 一、AI赋能:从"代码编写"到"智能协作" 1. AI代码助手大幅提升开发效率 …

Java 在 Excel 中查找并高亮数据:详细教程

在日常的开发工作中&#xff0c;我们经常需要处理各种格式的数据&#xff0c;其中 Excel 文件因其广泛的应用而占据重要地位。面对海量的 Excel 数据&#xff0c;如何高效地查找特定内容并进行标记&#xff08;如高亮显示&#xff09;&#xff0c;成为了许多开发者和办公自动化…

Tessent_ijtag_ug——第 5 章IJTAG 网络插入 (1)

第 5 章IJTAG 网络插入 IJTAG 网络插入功能使您能够连接现有的instrument&#xff0c;并插入 SIB、TDR 和 ScanMux 以创建您自己的 IJTAG 网络。 IJTAG 网络插入功能使您能够将网络连接到 TAP 控制器或设计中已有的 TAP 控制器。IJTAG 网络插入的原理是使用 create_dft_specifi…

同步与互斥学习笔记

一、基本概念同步与互斥是多任务/多线程编程中的两个核心机制&#xff1a;同步&#xff1a;指多个任务之间存在明确的先后顺序&#xff0c;一个任务必须等待另一个任务完成某些操作后才能继续执行。互斥&#xff1a;指多个任务在同一时刻争抢使用同一资源&#xff08;临界资源&…

Tomcat 启动流程与类加载机制

Tomcat 启动流程与类加载机制1. 引言 Tomcat 的启动不仅仅是简单的 java -jar 或 catalina.sh start。 它背后包含 Bootstrap 启动器、Catalina 控制器、Server/Service/Connector/Container 初始化 等关键步骤。 另一方面&#xff0c;Tomcat 为了支持 热部署、不同应用间类隔离…

MTK Linux Charger驱动分析(十二)- mtk_pd_adapter.c

1. 代码整体分析 mtk_pd_adapter.c(源文件) 主要内容: 该文件实现了MediaTek平台的USB PD(Power Delivery)适配器驱动,基于Linux内核的电源管理和Type-C端口控制器(TCPC)框架。 它处理PD协议事件,包括PD连接状态、Type-C状态、水检测(WD_STATUS)、Sink VBUS变化等。…

Spring Boot Logback 日志配置详解:从基础到分布式追踪

日志是应用程序不可或缺的组成部分&#xff0c;它不仅能帮助我们调试问题&#xff0c;还能监控系统运行状态。在 Spring Boot 生态中&#xff0c;Logback 凭借其高性能和灵活性成为首选的日志框架。本文将通过一个实际的 Logback 配置文件&#xff0c;详细解析其各个组件的功能…

软件体系结构——后端三层架构

三层架构——Controller、Service、Dao 不仅是对代码进行的逻辑分层。其真正的本质&#xff0c;是将业务、技术和数据剥离。搞业务的专心做业务&#xff0c;搞技术的专心搞技术&#xff0c;做数据存储的专心做数据存储。三方通过接口进行对接&#xff0c;任一部分重构&#xff…

QML学习笔记(一)基本了解和工程配置

前言&#xff1a; 已经从事QT开发几年了&#xff0c;但对于QML这个东西始终是没有彻底掌握&#xff0c;一方面实际工作中没有用到过&#xff0c;其次它的语法对我来说是全新的东西&#xff0c;不像QWidget那一套可以直接在C中去写。这就是为什么网上都说qml更简单&#xff0c;我…

SAP HANA Scale-out 04:缓存

结果缓存静态结果缓存 Vs 动态结果缓存FeatureStatic Result CacheDynamic Result CacheTarget Scenario对复杂视图&#xff08;通常是顶层视图&#xff09;的查询频繁更新的大表&#xff08;例如ACDOCA&#xff09;上的聚合查询Query result非实时数据实时数据ScopeTarget obj…

嘉兴禾润 HTR7216 (S) LED 驱动芯片:特性与应用

在如今智能设备飞速普及的时代&#xff0c;无论是智能家居的氛围营造、IoT 设备的状态提示&#xff0c;还是个人消费电子的视觉呈现&#xff0c;都离不开高性能 LED 驱动芯片的支撑。嘉兴禾润推出的 HTR7216 (S) LED 驱动芯片&#xff0c;凭借丰富的功能、精准的控制以及出色的…

Python实现剑龙优化算法 (Stegosaurus Optimization Algorithm, SOA)优化函数(付完整代码)

Python实现剑龙优化算法 (Stegosaurus Optimization Algorithm, SOA)优化函数&#xff08;付完整代码&#xff09;1.剑龙优化算法介绍剑龙优化算法&#xff08;Stegosaurus Optimization Algorithm&#xff0c;SOA&#xff09;是一种受剑龙独特生理结构和行为模式启发而设计的元…

分布式拜占庭容错算法——权益证明(PoS)算法详解

Java 实现权益证明&#xff08;PoS&#xff09;算法详解 一、PoS 核心机制 #mermaid-svg-Sbj0HU6MjOl1yo5L {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Sbj0HU6MjOl1yo5L .error-icon{fill:#552222;}#mermaid-s…

【论文阅读】谷歌:生成式数据优化,只需请求更好的数据

谷歌DeepMind团队通过Generative Data Refinement&#xff08;GDR&#xff09;技术&#xff0c;成功将极端有毒的4chan讨论数据转化为安全且语义丰富的训练素材&#xff0c;推动了LLM训练数据净化的新范式&#xff1a; • GDR利用预训练大模型对原始数据进行“重写”&#xff0…

C++ 多线程实战 10|C++20 的信号量、闩锁与屏障

目录 前言 学习目标 1. 信号量&#xff08;Semaphore&#xff09; 示例&#xff1a;限制并发下载任务 2. 闩锁&#xff08;Latch&#xff09; 示例&#xff1a;赛跑 3. 屏障&#xff08;Barrier&#xff09; 示例&#xff1a;图像处理流水线 4. 常见坑与对策 5. 实践作…

【Java SE】01. 初识Java

1. 认识Java Java是一种优秀的程序设计语言&#xff0c;它具有令人赏心悦目的语法和易于理解的语义。Java还是一个有一系列计算机软件和规范形成的技术体系&#xff0c;这个技术体系提供了完整的用于软件开发和跨平台部署的支持环境&#xff0c;并广泛应用于嵌入式系统、移动终…

解锁仓储智能调度、运输路径优化、数据实时追踪,全功能降本提效的智慧物流开源了

AI 视频监控平台&#xff1a;全链路协同驱动的智能监控解决方案AI 视频监控平台是一款融合高性能功能与轻量化操作的实时算法驱动型视频监控系统&#xff0c;其核心愿景在于深度破除不同芯片厂商间的技术壁垒&#xff0c;省去冗余重复的适配环节&#xff0c;最终达成芯片、算法…