当前位置: 首页 > news >正文

Linux驱动开发实战(一):LED控制驱动详解

Linux驱动开发野火实战(一):LED控制驱动详解


文章目录

  • Linux驱动开发野火实战(一):LED控制驱动详解
  • 引言
  • 一、基础知识
    • 1.1 什么是字符设备驱动
    • 1.2 重要的数据结构
      • read 函数
      • write 函数
      • open 函数
      • release 函数
  • 二、 驱动程序实现
    • 2.1 完整的驱动代码示例
    • 2.2 整体流程(图解)
    • 2.3 用户空间与内核空间交互(图解)
    • 2.4 驱动模块初始化
      • 虚拟地址映射
    • 2.5 拷贝数据
    • 2.6 控制GPIO输出的LED开关状态
    • 2.7 LED驱动程序的退出函数
  • 三、实验过程
    • 项目编译
    • 连接开发板
      • 挂载NFS文件系统
    • 加载驱动(点灯!)
  • 总结


引言

在Linux设备驱动开发中,字符设备驱动是最基础也是最常见的驱动类型。本文将从理论到实践,详细讲解字符设备驱动的开发流程,帮助读者掌握驱动开发的核心知识


一、基础知识

1.1 什么是字符设备驱动

字符设备(Character Device)是Linux中最基本的设备类型之一,它的特点是数据以字符流的方式被访问,像串口、键盘、LED等都属于字符设备。与块设备不同,字符设备不能随机访问,只能顺序读写。

1.2 重要的数据结构

在开发字符设备驱动之前,我们需要了解几个关键的数据结构:

struct file_operations {struct module *owner;ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);...
};

这个结构体定义了驱动程序需要实现的接口函数。

read 函数

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

作用: 响应用户空间的读取请求
参数:

  • filp:文件结构体指针
  • buf:用户空间缓冲区指针
  • cnt:要读取的字节数
  • offt:文件位置指针
    返回值:
  • 成功返回实际读取的字节数
  • 失败返回负错误码
  • 使用场景:
  • 读取设备状态
  • 获取设备数据
  • 将数据从内核空间复制到用户空间

write 函数

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

作用: 响应用户空间的写入请求
参数:

  • filp:文件结构体指针
  • buf:用户空间数据缓冲区指针
  • cnt:要写入的字节数
  • offt:文件位置指针
    返回值:
  • 成功返回实际写入的字节数
  • 失败返回负错误码
    使用场景:
  • 向设备发送控制命令
  • 更新设备状态
  • 将数据从用户空间复制到内核空间

open 函数

static int led_open(struct inode *inode, struct file *filp)

作用: 当用户空间调用 open() 打开设备文件时被调用
参数:

  • inode:包含文件系统信息的结构体,如设备号等
  • filp:文件结构体,包含文件操作方法、私有数据等
    返回值:
  • 成功返回0
  • 失败返回负错误码
    使用场景:
  • 初始化设备
  • 检查设备状态
  • 分配资源
  • 增加使用计数

release 函数

static int led_release(struct inode *inode, struct file *filp)

作用: 当最后一个打开的文件被关闭时调用
参数:

  • inode:索引节点结构体指针
  • filp:文件结构体指针
    返回值:
  • 成功返回0
  • 失败返回负错误码
    使用场景:
  • 释放资源
  • 清理设备状态
  • 减少使用计数

二、 驱动程序实现

2.1 完整的驱动代码示例

代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>#define DEV_MAJOR 0		   /* 动态申请主设备号 */
#define DEV_NAME "red_led" /*led设备名字 *//* GPIO虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO04;
static void __iomem *SW_PAD_GPIO1_IO04;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;static int led_open(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return -EFAULT;
}static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{unsigned char databuf[10];if (cnt > 10)cnt = 10;/*从用户空间拷贝数据到内核空间*/if (copy_from_user(databuf, buf, cnt)){return -EIO;}if (!memcmp(databuf, "on", 2)){iowrite32(0 << 4, GPIO1_DR);}else if (!memcmp(databuf, "off", 3)){iowrite32(1 << 4, GPIO1_DR);}/*写成功后,返回写入的字数*/return cnt;
}static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 自定义led的file_operations 接口*/
static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};int major = 0;
static int __init led_init(void)
{/* GPIO相关寄存器映射 */IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4);SW_MUX_GPIO1_IO04 = ioremap(0x20e006c, 4);SW_PAD_GPIO1_IO04 = ioremap(0x20e02f8, 4);GPIO1_GDIR = ioremap(0x0209c004, 4);GPIO1_DR = ioremap(0x0209c000, 4);/* 使能GPIO1时钟 */iowrite32(0xffffffff, IMX6U_CCM_CCGR1);/* 设置GPIO1_IO04复用为普通GPIO*/iowrite32(5, SW_MUX_GPIO1_IO04);/*设置GPIO属性*/iowrite32(0x10B0, SW_PAD_GPIO1_IO04);/* 设置GPIO1_IO04为输出功能 */iowrite32(1 << 4, GPIO1_GDIR);/* LED输出高电平 */iowrite32(1 << 4, GPIO1_DR);/* 注册字符设备驱动 */major = register_chrdev(DEV_MAJOR, DEV_NAME, &led_fops);printk(KERN_ALERT "led major:%d\n", major);return 0;
}static void __exit led_exit(void)
{/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO04);iounmap(SW_PAD_GPIO1_IO04);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注销字符设备驱动 */unregister_chrdev(major, DEV_NAME);
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("led_module");
MODULE_ALIAS("led_module");

2.2 整体流程(图解)

在这里插入图片描述

2.3 用户空间与内核空间交互(图解)

在这里插入图片描述

2.4 驱动模块初始化

虚拟地址映射

  1. ioremap 函数
void __iomem *ioremap(unsigned long phys_addr, unsigned long size);

作用: 将物理地址映射到虚拟地址空间
参数:

  • phys_addr:物理地址
  • size:映射的大小(字节数)
    返回值: 映射后的虚拟地址指针
  • void * 类型的指针,指向被映射的虚拟地址
  • __iomem 主要是用于编译器的检查地址在内核空间的有效性
    为什么要用: Linux内核出于安全考虑,不允许直接访问物理地址,必须先映射到虚拟地址

GPIO相关寄存器映射

	IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4);SW_MUX_GPIO1_IO04 = ioremap(0x20e006c, 4);SW_PAD_GPIO1_IO04 = ioremap(0x20e02f8, 4);GPIO1_GDIR = ioremap(0x0209c004, 4);GPIO1_DR = ioremap(0x0209c000, 4);
  1. 虚拟地址读写
void iowrite32(u32 b, void __iomem *addr)   //写入一个双字(32bit)unsigned int ioread32(void __iomem *addr)   //读取一个双字(32bit)
/* 使能GPIO1时钟 */iowrite32(0xffffffff, IMX6U_CCM_CCGR1);/* 设置GPIO1_IO04复用为普通GPIO*/iowrite32(5, SW_MUX_GPIO1_IO04);/*设置GPIO属性*/iowrite32(0x10B0, SW_PAD_GPIO1_IO04);/* 设置GPIO1_IO04为输出功能 */iowrite32(1 << 4, GPIO1_GDIR);/* LED输出高电平 */iowrite32(1 << 4, GPIO1_DR);
  1. register_chrdev 函数
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

作用: 注册字符设备驱动
参数:

major:主设备号(0表示动态分配)
name:设备名称
fops:文件操作结构体
次设备号为0,次设备号数量为256
返回值: 成功返回主设备号,失败返回负值
为什么要用: 向Linux系统注册一个字符设备,使系统能够识别和管理该设备

在这里插入图片描述

2.5 拷贝数据

copy_from_user函数

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

作用: 将数据从用户空间复制到内核空间
参数:

  • to:内核空间目标地址
  • from:用户空间源地址
  • n:复制的字节数
    返回值: 成功返回0,失败返回未复制的字节数
    为什么要用: 内核空间和用户空间是隔离的,需要专门的函数来安全地传输数据,要是有野指针会导致整个系统的崩溃,所以是起到一个安全的作用。

2.6 控制GPIO输出的LED开关状态

if (!memcmp(databuf, "on", 2))  // 比较是否接收到"on"命令
{iowrite32(0 << 4, GPIO1_DR); // GPIO1_4输出低电平,LED亮
}
else if (!memcmp(databuf, "off", 3)) // 比较是否接收到"off"命令
{iowrite32(1 << 4, GPIO1_DR); // GPIO1_4输出高电平,LED灭
}
  • memcmp()函数:
int memcmp(const void *str1, const void *str2, size_t n)
// 比较两个内存区域的前n个字节
// 返回0表示相等

2.7 LED驱动程序的退出函数

static void __exit led_exit(void)
{// 1. 取消IO内存映射iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO04);iounmap(SW_PAD_GPIO1_IO04);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);// 2. 注销字符设备unregister_chrdev(major, DEV_NAME);
}
  • iounmap 函数
void iounmap(void __iomem *addr);

作用: 解除I/O内存映射
参数:

addr: 要解除映射的虚拟地址
为什么要用: 释放ioremap占用的资源,防止内存泄漏

  • unregister_chrdev 函数
void unregister_chrdev(unsigned int major, const char *name);

作用: 注销字符设备驱动
参数:

major:设备的主设备号
name:设备名称
为什么要用: 在驱动卸载时清理系统资源

三、实验过程

项目编译

在这里插入图片描述
然后make
在这里插入图片描述

连接开发板

打开手机热点并连上
让电脑跟手机在同一个局域网内

  • ubuntu端
    在这里插入图片描述

  • 开发板端
    在这里插入图片描述

挂载NFS文件系统

sudo mount -t nfs ”NFS服务端IP”:/home/embedfire/workdir /mnt

我们ubuntu的IP为192.168.46.118
所以为

sudo mount -t nfs 192.168.46.118:/home/embedfire/workdir /mnt

在这里插入图片描述
挂载成功后进入共享文件夹查看

ubuntu把ko文件复制到共享文件夹中
在这里插入图片描述

我们到共享文件夹ls查看
在这里插入图片描述

加载驱动(点灯!)

在这里插入图片描述
244是设备号(动态分配)
0是次设备号
为什么是0
因为在register_chrdev函数定义了
次设备号在(0~256之间随便选)
ebf-buster-linux/include/linux/fs.h

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{return __register_chrdev(major, 0, 256, name, fops);
}

创建设备文件
在这里插入图片描述
利用echo应用打开灯的命令
在这里插入图片描述
请添加图片描述

利用echo应用关灯的命令
在这里插入图片描述
卸载模块
在这里插入图片描述


总结

本文详细介绍了Linux字符设备驱动的开发流程,包括:

  • 基本概念和原理
  • 完整的代码实现
  • 详细的流程图解
  • 实际操作
    通过本文的学习,大家应该能够掌握字符设备驱动的开发方法,并能够开发简单的字符设备驱动程序。

相关文章:

Linux驱动开发实战(一):LED控制驱动详解

Linux驱动开发野火实战&#xff08;一&#xff09;&#xff1a;LED控制驱动详解 文章目录 Linux驱动开发野火实战&#xff08;一&#xff09;&#xff1a;LED控制驱动详解引言一、基础知识1.1 什么是字符设备驱动1.2 重要的数据结构read 函数write 函数open 函数release 函数 二…...

windows下安装pyenv+virtualenv+virtualenvwrapper

1、下载pyenv 进入git官网&#xff0c;打包下载zip到本地 2、解压到安装目录 解压下载好的pyenv-win-master.zip到自己的安装目录&#xff0c;如D:\Program Files 3、配置环境变量 右击桌面 此电脑 --> 属性 --> 高端系统设置 --> 环境变量 --> 新建系统变量…...

Cherno 游戏引擎笔记(91~111)

好久不见&#xff01; 个人库的地址&#xff1a;&#xff08;GitHub - JJJJJJJustin/Nut: The game_engine which learned from Cherno&#xff09;&#xff0c;可以看到我及时更新的结果。 -------------------------------Saving & Loading scene-----------------------…...

0x02 js、Vue、Ajax

文章目录 js核心概念js脚本引入html的方式基础语法事件监听 Vuevue简介v-forv-bindv-if&v-showv-model&v-on Ajax js 核心概念 JavaScript&#xff1a;是一门跨平台、面向对象的脚本语言&#xff0c;用来控制网页行为实现交互效果&#xff0c;由ECMAScript、BOM、DOM…...

Windows 11【1001问】删除Win11左下角小组件的6种方法

在Windows 11中&#xff0c;左下角的小组件功能虽然提供了天气、新闻等实用信息&#xff0c;但对于一些用户来说可能显得多余或干扰视线。因此&#xff0c;微软提供了多种方式让用户能够自定义是否显示这些小组件。以下是 6 种常见的设置方法来隐藏或关闭Windows 11左下角的小组…...

【动手学深度学习】基于Python动手实现线性神经网络

深度学习入门&#xff1a;基于Python动手实现线性回归 1&#xff0c;走进深度学习2&#xff0c;配置说明3&#xff0c;线性神经网络4&#xff0c;线性回归从0开始实现4.1&#xff0c;导入相关库4.2&#xff0c;生成数据4.3&#xff0c;读取数据集4.4&#xff0c;初始化模型参数…...

leetcode 912. 排序数组

912. 排序数组 912. 排序数组 题目 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 你必须在 不使用任何内置函数 的情况下解决问题&#xff0c;时间复杂度为 O(nlog(n))&#xff0c;并且空间复杂度尽可能小。 示例 1&#xff1a; 输入&#xff1a;nums [5,2,3,1…...

【芯片设计】NPU芯片前端设计工程师面试记录·20250227

应聘公司 某NPU/CPU方向芯片设计公司。 小声吐槽两句,前面我问了hr需不需要带简历,hr不用公司给打好了,然后我就没带空手去的。结果hr小姐姐去开会了,手机静音( Ĭ ^ Ĭ )面试官、我、另外的hr小姐姐都联系不上,结果就变成了两个面试官和我一共三个人在会议室里一人拿出…...

BUU40 [CSCCTF 2019 Qual]FlaskLight1【SSTI】

模板&#xff1a; {{.__class__.__base__.__subclasses__()[80].__init__.__globals__[__builtins__].eval("__import__(os).popen(type flag.txt).read()")}} 是个空字符串&#xff0c;.__class__代表这个空字符串的类是什么&#xff08;这里是单引号双引号都行&a…...

WiFi IEEE 802.11协议精读:IEEE 802.11-2007,6,MAC service definition MAC服务定义

继续精读IEEE 802.11-2007 6&#xff0c;MAC service definition MAC服务定义 6.1 MAC服务概述 6.1.1 数据服务 此服务为对等逻辑链路控制&#xff08;LLC&#xff09;实体提供交换MAC服务数据单元&#xff08;MSDU&#xff09;的能力。为支持此服务&#xff0c;本地媒体访…...

2025学年安徽省职业院校技能大赛 “信息安全管理与评估”赛项 比赛样题任务书

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;五&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务1 &#xff1a;内存取证&…...

VAE变分自编码器的初步理解

VAE的结构和原理 VAE由两部分组成&#xff1a; 编码器&#xff08;Encoder&#xff09;&#xff1a; 编码器负责将输入数据&#xff08;例如图像&#xff09;压缩成一个潜在空间&#xff08;latent space&#xff09;的表示。这个潜在空间不是一个固定的值&#xff0c;而是一个…...

2025 最新版鸿蒙 HarmonyOS 开发工具安装使用指南

为保证 DevEco Studio 正常运行&#xff0c;建议电脑配置满足如下要求&#xff1a; Windows 系统 操作系统&#xff1a;Windows10 64 位、Windows11 64 位内存&#xff1a;16GB 及以上硬盘&#xff1a;100GB 及以上分辨率&#xff1a;1280*800 像素及以上 macOS 系统 操作系统…...

Rider 安装包 绿色版 Win/Mac/Linux 适合.NET和游戏开发者使用 2025全栈开发终极指南:从零配置到企业级实战

下载链接&#xff1a; https://pan.baidu.com/s/1cfkJf6Zgxc1XfYrVpwtHkA?pwd1234 导语&#xff1a;JetBrains Rider以跨平台支持率100%、深度.NET集成和智能代码分析能力&#xff0c;成为2025年全栈开发者的首选工具。本文涵盖环境配置、核心功能、框架集成、性能调优、团队…...

Python常见面试题的详解24

1. 如何对关键词触发模块进行测试 要点 功能测试&#xff1a;验证正常关键词触发、边界情况及大小写敏感性&#xff0c;确保模块按预期响应不同输入。 性能测试&#xff1a;关注响应时间和并发处理能力&#xff0c;保证模块在不同负载下的性能表现。 兼容性测试&#xff1a;测…...

手机打电话时如何识别对方按下的DTMF按键的字符-安卓AI电话机器人

手机打电话时如何识别对方按下的DTMF按键的字符 --安卓AI电话机器人 一、前言 前面的篇章中&#xff0c;使用蓝牙电话拦截手机通话的声音&#xff0c;并对数据加工&#xff0c;这个功能出来也有一段时间了。前段时间有试用的用户咨询说&#xff1a;有没有办法在手机上&#xff…...

RabbitMQ操作实战

1.RabbitMQ安装 RabbitMQ Windows 安装、配置、使用 - 小白教程-腾讯云开发者社区-腾讯云下载erlang&#xff1a;http://www.erlang.org/downloads/https://cloud.tencent.com/developer/article/2192340 Windows 10安装RabbitMQ及延时消息插件rabbitmq_delayed_message_exch…...

IDEA 2024.1 最新永久可用(亲测有效)

今年idea发布了2024.1版本&#xff0c;这个版本带来了一系列令人兴奋的新功能和改进。最引人注目的是集成了更先进的 AI 助手&#xff0c;它现在能够提供更复杂的代码辅助功能&#xff0c;如代码自动补全、智能代码审查等&#xff0c;极大地提升了开发效率。此外&#xff0c;用…...

【R包】pathlinkR转录组数据分析和可视化利器

介绍 通常情况下&#xff0c;基因表达研究如微阵列和RNA-Seq会产生数百到数千个差异表达基因&#xff08;deg&#xff09;。理解如此庞大的数据集的生物学意义变得非常困难&#xff0c;尤其是在分析多个条件和比较的情况下。该软件包利用途径富集和蛋白-蛋白相互作用网络&…...

RPA 与 AI 结合:开启智能自动化新时代

RPA 与 AI 结合&#xff1a;开启智能自动化新时代 在当今数字化快速发展的时代&#xff0c;企业面临着海量的数据处理和复杂的业务流程&#xff0c;如何提高效率、降低成本、优化业务流程成为了企业关注的焦点。而 RPA&#xff08;Robotic Process Automation&#xff0c;机器…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

麒麟系统使用-进行.NET开发

文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的&#xff0c;如果需要进行.NET开发&#xff0c;则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET&#xff0c;所以要进…...

网页端 js 读取发票里的二维码信息(图片和PDF格式)

起因 为了实现在报销流程中&#xff0c;发票不能重用的限制&#xff0c;发票上传后&#xff0c;希望能读出发票号&#xff0c;并记录发票号已用&#xff0c;下次不再可用于报销。 基于上面的需求&#xff0c;研究了OCR 的方式和读PDF的方式&#xff0c;实际是可行的&#xff…...

qt+vs Generated File下的moc_和ui_文件丢失导致 error LNK2001

qt 5.9.7 vs2013 qt add-in 2.3.2 起因是添加一个新的控件类&#xff0c;直接把源文件拖进VS的项目里&#xff0c;然后VS卡住十秒&#xff0c;然后编译就报一堆 error LNK2001 一看项目的Generated Files下的moc_和ui_文件丢失了一部分&#xff0c;导致编译的时候找不到了。因…...