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

Win32 命名管道

命名管道简单封装

CNamedPipe.h

#pragma once
#include <string>
#include <windows.h>
#include <tchar.h>#pragma warning(disable:4200)class CNamedPipe
{
public:CNamedPipe();~CNamedPipe();CNamedPipe(const CNamedPipe& r) = delete;CNamedPipe& operator = (const CNamedPipe& r) = delete;//// @brief: 创建命名管道// @param: lpName           管道名// @ret: bool               true: 创建成功 false: 创建失败bool Create(LPCTSTR lpName);//// @brief: 等待客户端连接命名管道// @param: nTimeOut         超时等待(毫秒)// @ret: bool               true: 连接成功 false: 连接失败bool WaitConnect(DWORD nTimeOut = INFINITE);//// @brief: 关闭由Create 创建的管道// @param: void// @ret: bool               true: 关闭 成功 false: 关闭 失败bool Disconnect();//// @brief: 打开已存在的命名管道// @param: lpName           管道名// @ret: bool               true: 打开成功 false: 打开失败bool Open(LPCTSTR lpName, DWORD nTimeOut = INFINITE);//// @brief: 管道是否有效// @param: void// @ret: bool               true: 可用 false: 无效bool IsValid();//// @brief: 关闭管道// @param: void// @ret: voidvoid Close(void);//// @brief: 从读取管道数据// @param: lpData           数据存放缓冲// @param: nSize            缓冲大小(字节)// @param: lpBytesRead      指向实际读取大小(字节)的指针// @param: nTimeOut         读取超时(毫秒)// @ret: bool               true: 读取成功 false: 读取失败bool Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead = nullptr, DWORD nTimeOut = INFINITE);//// @brief: 向管道写入数据// @param: lpData           写入数据指针// @param: nSize            写入数据大小(字节)// @param: lpBytesWritten   指向实际写入大小(字节)的指针// @param: nTimeOut         写入超时(毫秒)// @ret: bool               true: 写入成功 false: 写入失败bool Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten = nullptr, DWORD nTimeOut = INFINITE);private://// @brief: 初始化对象占用// @param: void// @ret: voidbool Initialize();//// @brief: 释放对象占用// @param: void// @ret: voidvoid Uninitialize();private:HANDLE m_hNamedPipe;HANDLE m_hReadEvent;HANDLE m_hWriteEvent;LPVOID m_pBuffer;bool m_bInit;bool m_bConnected;
};

CNamedPipe.cpp

#include "CNamedPipe.h"
#include <StrSafe.h>
#include <tchar.h>#define PIPE_NAME_PREFIX             TEXT(R"(\\.\pipe\)")    //管道前缀名
#define PIPE_MAX_TIMEOUT             (3000)                  //管道打开超时
#define PIPE_BUF_MAX_SIZE            (1024 * 1024)           //管道发送缓冲大小(字节)
#define PIPE_MAX_CONNECT             (16)                    //IPC最大连接数typedef struct _PIPE_DATA
{DWORD dwSize = 0;BYTE data[0];
}PIPE_DATA, * PPIPE_DATA;CNamedPipe::CNamedPipe():m_hNamedPipe(INVALID_HANDLE_VALUE),m_hReadEvent(NULL),m_hWriteEvent(NULL),m_bConnected(false),m_bInit(false)
{//初始化读写缓冲与事件句柄Initialize();
}CNamedPipe::~CNamedPipe()
{//释放读写缓冲与事件句柄Uninitialize();
}bool CNamedPipe::Create(LPCTSTR lpName)
{TCHAR szPipeName[MAX_PATH];SECURITY_ATTRIBUTES sa = { 0 };SECURITY_DESCRIPTOR sd = { 0 };bool isSuccess = false;sa.nLength = sizeof(sa);sa.bInheritHandle = FALSE;sa.lpSecurityDescriptor = &sd;if (INVALID_HANDLE_VALUE != m_hNamedPipe){return true;}//设置权限, 防止低权限进程不能打开高权限进程创建的管道(void)::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);(void)::SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);(void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);do{m_hNamedPipe = ::CreateNamedPipe(szPipeName,PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,PIPE_MAX_CONNECT,PIPE_BUF_MAX_SIZE,PIPE_BUF_MAX_SIZE,PIPE_MAX_TIMEOUT,&sa);if (INVALID_HANDLE_VALUE == m_hNamedPipe){break;}isSuccess = true;} while (false);if (!isSuccess){this->Close();}return isSuccess;
}bool CNamedPipe::Open(LPCTSTR lpName, DWORD nTimeOut/* = INFINITE*/)
{TCHAR szPipeName[MAX_PATH] = { 0 };bool isSuccess = false;(void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);if (INVALID_HANDLE_VALUE != m_hNamedPipe){return true;}ULONGLONG ullCurTick = ::GetTickCount64();do{m_hNamedPipe = ::CreateFile(szPipeName,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);//管道句柄有效则终止循环if (INVALID_HANDLE_VALUE != m_hNamedPipe){isSuccess = true;break;}//若错误原因不是因为所有管道范例都在使用中, 则退出循环if (ERROR_PIPE_BUSY != ::GetLastError()){break;}//等待命名管道的实例可用于连接if (::WaitNamedPipe(szPipeName, 1000)){continue;}//无限等待则不需要检查超时if (INFINITE == nTimeOut){continue;}//执行操作超时则退出循环if (::GetTickCount64() - ullCurTick > nTimeOut){break;}} while (INVALID_HANDLE_VALUE == m_hNamedPipe);if (!isSuccess){this->Close();}return isSuccess;
}bool CNamedPipe::WaitConnect(DWORD nTimeOut)
{OVERLAPPED Overlapped = { 0 };bool isConnected = false;if (INVALID_HANDLE_VALUE == m_hNamedPipe){return false;}Overlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);if (NULL == Overlapped.hEvent){return false;}isConnected = ::ConnectNamedPipe(m_hNamedPipe, &Overlapped);if (!isConnected){DWORD dwError = ::GetLastError();//管道关闭中if (ERROR_NO_DATA == dwError){isConnected = false;}//操作处于挂起状态if (ERROR_IO_PENDING == dwError){if (WAIT_OBJECT_0 == ::WaitForSingleObject(Overlapped.hEvent, nTimeOut)){isConnected = true;}}//管道已经连接if (ERROR_PIPE_CONNECTED == dwError){isConnected = true;}}if (NULL != Overlapped.hEvent){::CloseHandle(Overlapped.hEvent);}m_bConnected = isConnected;return isConnected;
}bool CNamedPipe::Disconnect()
{if (INVALID_HANDLE_VALUE == m_hNamedPipe){return false;}//参数句柄必须由 CreateNamedPipe 函数创建return ::DisconnectNamedPipe(m_hNamedPipe);
}void CNamedPipe::Close()
{if (INVALID_HANDLE_VALUE != m_hNamedPipe){if (m_bConnected){::FlushFileBuffers(m_hNamedPipe);::DisconnectNamedPipe(m_hNamedPipe);m_bConnected = false;}::CloseHandle(m_hNamedPipe);m_hNamedPipe = INVALID_HANDLE_VALUE;}
}bool CNamedPipe::IsValid()
{return INVALID_HANDLE_VALUE != m_hNamedPipe;
}bool CNamedPipe::Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead/* = nullptr*/, DWORD nTimeOut)
{OVERLAPPED Overlapped = { 0 };Overlapped.hEvent = m_hReadEvent;DWORD dwBytesTransferred = 0;bool isSuccess = false;if (nullptr == m_pBuffer ||nullptr == lpData ||0 == nSize ||nSize > PIPE_BUF_MAX_SIZE){return false;}PPIPE_DATA pData = (PPIPE_DATA)m_pBuffer;if (!::ReadFile(m_hNamedPipe, &pData->dwSize, sizeof(PIPE_DATA), NULL, &Overlapped)){//管道已结束if (ERROR_BROKEN_PIPE == ::GetLastError()){return false;}if (ERROR_IO_PENDING != ::GetLastError()){return false;}if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut)){return false;}}if (pData->dwSize > PIPE_BUF_MAX_SIZE){return false;}if (!::ReadFile(m_hNamedPipe, pData->data, pData->dwSize, NULL, &Overlapped)){if (ERROR_IO_PENDING != ::GetLastError()){return false;}if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut)){return false;}}if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true)){isSuccess = true;if (lpBytesRead){*lpBytesRead = dwBytesTransferred;}}if (isSuccess){if (nSize < pData->dwSize){::memcpy_s(lpData, nSize, pData->data, nSize);}else{::memcpy_s(lpData, nSize, pData->data, pData->dwSize);}}return isSuccess;
}bool CNamedPipe::Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten/* = nullptr*/, DWORD nTimeOut)
{OVERLAPPED Overlapped = { 0 };Overlapped.hEvent = m_hWriteEvent;DWORD dwBytesTransferred = 0;bool isSuccess = false;if (nullptr == m_pBuffer ||nullptr == lpData ||0 == nSize ||nSize > PIPE_BUF_MAX_SIZE){return false;}PPIPE_DATA pData = (PPIPE_DATA)m_pBuffer;DWORD dwBytesToWrite = nSize + sizeof(PIPE_DATA);pData->dwSize = nSize;::memcpy_s(pData->data, PIPE_BUF_MAX_SIZE, lpData, nSize);if (::WriteFile(m_hNamedPipe, pData, dwBytesToWrite, NULL, &Overlapped)){return true;}//管道正在被关闭if (ERROR_NO_DATA == ::GetLastError()){return false;}//管道已结束if (ERROR_BROKEN_PIPE == ::GetLastError()){return false;}//重叠if (ERROR_IO_PENDING != ::GetLastError()){return false;}if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut)){return false;}if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true)){isSuccess = true;if (lpBytesWritten){*lpBytesWritten = dwBytesTransferred;}}return isSuccess;
}bool CNamedPipe::Initialize()
{bool isSuccess = false;if (m_bInit){return true;}do{if (nullptr == m_pBuffer){m_pBuffer = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, PIPE_BUF_MAX_SIZE + sizeof(PIPE_DATA));}if (nullptr == m_pBuffer){break;}if (NULL == m_hReadEvent){m_hReadEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);}if (NULL == m_hReadEvent){break;}if (NULL == m_hWriteEvent){m_hWriteEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);}if (NULL == m_hWriteEvent){break;}isSuccess = true;} while (false);if (!isSuccess){Uninitialize();}m_bInit = isSuccess;return m_bInit;
}void CNamedPipe::Uninitialize()
{if (!m_bInit){return;}//关闭管道this->Close();//释放读写缓冲if (nullptr != m_pBuffer){::HeapFree(::GetProcessHeap(), 0, m_pBuffer);m_pBuffer = nullptr;}//关闭事件if (m_hReadEvent){CloseHandle(m_hReadEvent);m_hReadEvent = NULL;}if (m_hWriteEvent){CloseHandle(m_hWriteEvent);m_hWriteEvent = NULL;}m_bInit = false;
}

main.cpp

#include <iostream>
#include "Utils/CNamedPipe.h"
#include <thread>
#include <future>int main()
{std::promise<bool> p;std::future<bool> f = p.get_future();std::thread([&p]() {static char szBuf[1024 * 64] = { 0 };CNamedPipe pipe;if (!pipe.Create(_T("FlameCyclone"))){std::cout << "Create failed!" << std::endl;p.set_value(false);return;}p.set_value(true);if (!pipe.WaitConnect()){std::cout << "WaitConnect failed!" << std::endl;return;}while (true){bool isSuccess = pipe.Read(szBuf, sizeof(szBuf));if (isSuccess){std::cout << "recv: " << szBuf << std::endl;}//另一端断开则重新等待连接if (ERROR_BROKEN_PIPE == ::GetLastError()){pipe.Disconnect();if (!pipe.WaitConnect()){std::cout << "WaitConnect failed!" << std::endl;return;}}}}).detach();if (!f.get()){return -1;}CNamedPipe pipe;if (!pipe.Open(_T("FlameCyclone"), 5000)){std::cout << "Open failed!" << std::endl;return -1;}std::string strMsg;while (true){std::cin >> strMsg;std::cout << "send: " << strMsg << std::endl;if (!pipe.Write(strMsg.c_str(), strMsg.size() + 1)){std::cout << "Write failed!" << std::endl;break;}}system("pause");return 0;
}

相关文章:

Win32 命名管道

命名管道简单封装 CNamedPipe.h #pragma once #include <string> #include <windows.h> #include <tchar.h>#pragma warning(disable:4200)class CNamedPipe { public:CNamedPipe();~CNamedPipe();CNamedPipe(const CNamedPipe& r) delete;CNamedPipe&…...

Flutter 填坑录 (不定时更新)

一&#xff0c;内存爆表 > 图片缓存 /// State基类 class BaseState<T extends StatefulWidget> extends State<T>withAutomaticKeepAliveClientMixin,WidgetHelper,DialogHelper,EventListener {mustCallSupervoid initState() {if (isListenEvent()) {EventMa…...

如何提高webpack的构建速度?

一、背景 随着我们的项目涉及到页面越来越多&#xff0c;功能和业务代码也会随着越多&#xff0c;相应的 webpack 的构建时间也会越来越久 构建时间与我们日常开发效率密切相关&#xff0c;当我们本地开发启动 devServer 或者 build 的时候&#xff0c;如果时间过长&#xff…...

Linux:实用操作

Linux&#xff1a;实用操作 1. 各类小技巧1.1 controlc(ctrl c) 强制停止1.2 可以通过快捷键&#xff1a;control d(ctrl d)&#xff0c;退出账户的登录1.3 历史命令搜索1.4 光标移动快捷键 2. 软件安装2.1 介绍2.2 yum命令(需要root权限)在这里插入图片描述 3. systemctl4.…...

【Linxu工具】:vim使用及简单配置

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux工具&#xff1a;vim的使用&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从…...

众和策略:题材股什么意思?

题材股是股票商场上的一个术语&#xff0c;许多刚接触股票出资的人可能对它不太熟悉。那么&#xff0c;题材股什么意思呢&#xff1f;在本文中&#xff0c;咱们将从多个角度剖析这个问题&#xff0c;帮忙读者更好地了解。 一、什么是题材股 题材股是指某个工作或主题的股票集结…...

npm 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

一、报错&#xff1a; npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c; 然后再试一次。 所在位置 行:1 字符: 1npm init -y~~~ CategoryInfo : ObjectNotFo…...

港联证券:短债基金收益?

跟着人们对理财的需求不断增加&#xff0c;短债基金成为了许多出资者关注的焦点。那么&#xff0c;短债基金可以带来什么样的收益呢&#xff1f;本文将从多个角度剖析短债基金的收益。 一、短债基金的概念 短债基金是一种基金类型&#xff0c;风险相对较低&#xff0c;一般出资…...

每日一题 2316. 统计无向图中无法互相到达点对数(中等,图连通分量)

题目很简单&#xff0c;只要求出每个连通分量有多少个节点即可首先通过建立一个字典来表示每个节点的邻接关系遍历每个节点&#xff0c;并通过邻接关系标记在当前连通分量内的所有的点&#xff0c;这样就可以知道一个连通分量内有多少个点在这里我陷入了一个误区&#xff0c;导…...

Centos 无法连接 WIFI

环境 硬件&#xff1a;ASUS X550VC, x86_64系统&#xff1a;CentOS 7.9 现象 系统安装后无法上网&#xff0c;终端命令提示符为shadow3dlocalhost&#xff0c;我的疑问是这里不是应该显示我的主机名吗&#xff0c;为什么是localhost呢&#xff1f;但是有些时候&#xff0c;又…...

whois人员信息python批处理读入与文本输出

使用pytho读取一个ip列表文本&#xff0c;批量获取whois输出并写入到一个文本 import socketif __name__ __main__:# 江苏电信DNS地址mylog open(whois.log, mode a,encodingutf-8)for line in open("ip.txt"):s socket.socket(socket.AF_INET, socket.SOCK_STR…...

阿里云服务器续费流程_一篇文章搞定

阿里云服务器如何续费&#xff1f;续费流程来了&#xff0c;在云服务器ECS管理控制台选择续费实例、续费时长和续费优惠券&#xff0c;然后提交订单&#xff0c;分分钟即可完成阿里云服务器续费流程&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云服务器详细续费方法&am…...

TCP的三次握手、四次挥手!就像打电话一样简单!

目录 学前必会 三次握手详解 和打电话一样 为什么必须要三次&#xff1f; 四次挥手详解 和挂电话一样 为什么要四次挥手&#xff1f; 第四次为何要等待 2*MSL&#xff1f; 相关面试题&#xff1a; 说一下三次握手、四次挥手的过程三次握手四次挥手的目的是什么&#x…...

自动巡查、自动换充电……浙江这两台无人机“巢穴”派大用场

浙江省积极探索利用高科技的无人机技术提高森林防火效率。在杭州市西湖区的西山国家森林公园和绍兴市柯桥区的大香林风景区&#xff0c;部署了两台复亚智能全自动无人机飞行系统&#xff0c;实现了火情的自动检测、定期自动巡查以及迅速响应。该技术的应用标志着杭州从传统的“…...

数据结构题型20-第七章 查找

文章目录 1 考察重点2 知识框架3 考察重点4 顺序查找和折半查找4.1 顺序查找4.1.1 一般线性表的顺序查找4.1.2 有序表的顺序查找 4.2 折半查找4.3 分块查找 1 考察重点 2 知识框架 3 考察重点 4 顺序查找和折半查找 4.1 顺序查找 4.1.1 一般线性表的顺序查找 4.1.2 有序表的顺…...

页面查询多项数据组合的线程池设计 | 京东云技术团队

背景 我们应对并发场景时一般会采用下面方式去预估线程池的线程数量&#xff0c;比如QPS需求是1000&#xff0c;平均每个任务需要执行的时间是t秒&#xff0c;那么我们需要的线程数是t * 1000。 但是在一些情况下&#xff0c;这个t是不好估算的&#xff0c;即便是估算出来了&…...

设计模式:策略模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

简介&#xff1a; 策略模式&#xff0c;它是一种行为型设计模式&#xff0c;它定义了算法族&#xff0c;分别封装起来&#xff0c;让它们之间可以互相替换。策略模式让算法的变化独立于使用算法的客户&#xff0c;降低了耦合&#xff0c;增加了系统的可维护性和可扩展性。 策…...

详解如何快速查询Node.js版本信息(六种方法)

Node.js是一款基于Chrome V8引擎的快速、轻量级的JavaScript运行时。随着应用程序规模越来越庞大&#xff0c;Node.js版本的更新也日益频繁。这篇文章旨在帮助开发者们快速查询Node.js版本信息。 一、使用node命令查询版本信息 node命令提供了一个参数-v&#xff0c;可以在控…...

YOLOV8目标检测——最全最完整模型训练过程记录

文章目录 前言1 下载yolov8&#xff08;[网址](https://github.com/ultralytics/ultralytics)&#xff09;2 配置conda环境3 用pycharm打开文件3 训练自己的YOLOV8数据集4 run下运行完了之后没有best.pt文件5 导出为onnx文件6 yolov8应用完整案例&#xff08;免费且包含源代码、…...

【C语言】用函数实现模块化程序设计

前言&#xff1a;如果把所有的程序代码都写在一个主函数(main函数)中&#xff0c;就会使主函数变得庞杂、头绪不清&#xff0c;使阅读和维护程序变得困难。此外&#xff0c;有时程序中要多次实现某一功能&#xff0c;如果重新编写实现此功能就会使得程序冗长、不精炼。 &#x…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...