当前位置: 首页 > 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…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...