C#中深度解析BinaryFormatter序列化生成的二进制文件

news2025/2/10 23:48:14

C#中深度解析BinaryFormatter序列化生成的二进制文件

BinaryFormatter序列化时,对象必须有 可序列化特性[Serializable]

一.新建窗体测试程序BinaryDeepAnalysisDemo,将默认的Form1重命名为FormBinaryDeepAnalysis

二.新建测试类Test

Test.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BinaryDeepAnalysisDemo
{
    /// <summary>
    /// 一定要标记该类是可序列化的
    /// </summary>
    [Serializable]
    public class Test
    {
        public byte ByteData { get; set; }
        public short Int16Data { get; set; }
        public int Id { get; set; }
        public long Int64Data { get; set; }
        public float FloatData { get; set; }
        public double DoubleData { get; set; }
        public string TestName { get; set; }
    }
}

三.新建字典与类相互转化类ConversionUtil:

ConversionUtil.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
 
namespace BinaryDeepAnalysisDemo
{
    /// <summary>
    /// 字典Dictionary与自定义类CustomClass属性之间的转化
    /// 斯内科 2024-05-10
    /// </summary>
    public class ConversionUtil
    {
        /// <summary>
        /// 将一个实体类的属性转化为键值对集合,键为属性名PropertyName,值为对应的值
        /// </summary>
        /// <typeparam name="T">一种自定义类,该类必须已实例化</typeparam>
        /// <param name="obj">实例化后的类对象</param>
        /// <returns>返回转化后的字典</returns>
        public static Dictionary<string, object> CustomClassToDictionary<T>(T obj) where T : class, new()
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            Type type = typeof(T);
            PropertyInfo[] propertyInfos = type.GetProperties();
            for (int i = 0; i < propertyInfos.Length; i++)
            {
                string key = propertyInfos[i].Name;
                object val = propertyInfos[i].GetValue(obj);
                dict.Add(key, val);
            }
            return dict;
        }

        /// <summary>
        /// 将字典转化为实例化类,如果类的属性名称在字典中存在该键key,就使用该键对应的值为类的属性key赋值【注意,转化可能因值的类型、范围等不同而导致失败】
        /// 如果类的属性名称在字典中不存在该键key,则不进行赋值,只是按属性对应类型的默认值处理
        /// </summary>
        /// <typeparam name="T">要转化的目标类型</typeparam>
        /// <param name="dict">键值对字典</param>
        /// <returns>要获取的目标实例化类对象,如果字典为空,则按类的默认值处理</returns>
        public static T DictionaryToCustomClass<T>(Dictionary<string, object> dict) where T : class
        {
            T obj = default(T);
            if (dict == null || dict.Count == 0)
            {
                return obj;
            }
            Type type = typeof(T);
            obj = (T)Activator.CreateInstance(type);
            PropertyInfo[] propertyInfos = type.GetProperties();
            for (int i = 0; i < propertyInfos.Length; i++)
            {
                string key = propertyInfos[i].Name;
                if (dict.ContainsKey(key))
                {
                    propertyInfos[i].SetValue(obj, dict[key]);
                }
            }
            return obj;
        }
    }
}

四.测试窗体设计器相关代码如下

文件FormBinaryDeepAnalysis.Designer.cs


namespace BinaryDeepAnalysisDemo
{
    partial class FormBinaryDeepAnalysis
    {
        /// <summary>
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows 窗体设计器生成的代码

        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.panel1 = new System.Windows.Forms.Panel();
            this.rtxtMessage = new System.Windows.Forms.RichTextBox();
            this.btnSerial = new System.Windows.Forms.Button();
            this.btnDeserial = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // panel1
            // 
            this.panel1.Location = new System.Drawing.Point(12, 12);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(300, 532);
            this.panel1.TabIndex = 0;
            // 
            // rtxtMessage
            // 
            this.rtxtMessage.Location = new System.Drawing.Point(511, 12);
            this.rtxtMessage.Name = "rtxtMessage";
            this.rtxtMessage.ReadOnly = true;
            this.rtxtMessage.Size = new System.Drawing.Size(482, 532);
            this.rtxtMessage.TabIndex = 1;
            this.rtxtMessage.Text = "";
            // 
            // btnSerial
            // 
            this.btnSerial.Location = new System.Drawing.Point(360, 78);
            this.btnSerial.Name = "btnSerial";
            this.btnSerial.Size = new System.Drawing.Size(63, 23);
            this.btnSerial.TabIndex = 2;
            this.btnSerial.Text = "序列化";
            this.btnSerial.UseVisualStyleBackColor = true;
            this.btnSerial.Click += new System.EventHandler(this.btnSerial_Click);
            // 
            // btnDeserial
            // 
            this.btnDeserial.Location = new System.Drawing.Point(360, 155);
            this.btnDeserial.Name = "btnDeserial";
            this.btnDeserial.Size = new System.Drawing.Size(63, 23);
            this.btnDeserial.TabIndex = 3;
            this.btnDeserial.Text = "反序列化";
            this.btnDeserial.UseVisualStyleBackColor = true;
            this.btnDeserial.Click += new System.EventHandler(this.btnDeserial_Click);
            // 
            // FormBinaryDeepAnalysis
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1001, 556);
            this.Controls.Add(this.btnDeserial);
            this.Controls.Add(this.btnSerial);
            this.Controls.Add(this.rtxtMessage);
            this.Controls.Add(this.panel1);
            this.Name = "FormBinaryDeepAnalysis";
            this.Text = "序列化生成的二进制进行深度解析";
            this.Load += new System.EventHandler(this.FormBinaryDeepAnalysis_Load);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.RichTextBox rtxtMessage;
        private System.Windows.Forms.Button btnSerial;
        private System.Windows.Forms.Button btnDeserial;
    }
}

五.窗体FormBinaryDeepAnalysis相关示例代码如下

文件FormBinaryDeepAnalysis.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BinaryDeepAnalysisDemo
{
    public partial class FormBinaryDeepAnalysis : Form
    {
        public FormBinaryDeepAnalysis()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 显示文本
        /// </summary>
        /// <param name="contents"></param>
        private void DisplayMessage(string contents) 
        {
            if (!IsHandleCreated)
                return;
            this.BeginInvoke(new Action(() =>
            {
                if (rtxtMessage.TextLength >= 20480) 
                {
                    rtxtMessage.Clear();
                }
                rtxtMessage.AppendText($"{contents}\n");
                rtxtMessage.ScrollToCaret();
            }));
        }

        private void FormBinaryDeepAnalysis_Load(object sender, EventArgs e)
        {
            Test test = new Test()
            {
                ByteData = 234,
                Int16Data = 23456,
                Id = 12345678,
                Int64Data = 9876543210123L,
                FloatData = 60.25F,
                DoubleData = -87654321.3125,
                TestName = "ABC123abc"
            };
            Dictionary<string, object> dict = ConversionUtil.CustomClassToDictionary(test);
            for (int i = 0; i < dict.Count; i++)
            {
                KeyValuePair<string, object> keyValuePair = dict.ElementAt(i);
                Label label = new Label();
                label.Name = $"lbl{keyValuePair.Key}";
                label.Text = keyValuePair.Key;
                label.Location = new Point(3, 3 + 30 * i);

                TextBox textBox = new TextBox();
                textBox.Name = $"txb{keyValuePair.Key}";
                textBox.Text = Convert.ToString(keyValuePair.Value);
                textBox.Location = new Point(100, 3 + 30 * i);
                textBox.Size = new Size(160, 25);
                textBox.Tag = keyValuePair.Key;

                panel1.Controls.AddRange(new Control[] { label, textBox });
            }
        }

        private void btnSerial_Click(object sender, EventArgs e)
        {
            try
            {
                Dictionary<string, object> dict = new Dictionary<string, object>();
                for (int i = 0; i < panel1.Controls.Count; i++)
                {
                    Control control = panel1.Controls[i];
                    if (control is TextBox)
                    {
                        object val = control.Text;
                        switch (Convert.ToString(control.Tag))
                        {
                            case "ByteData":
                                val = byte.Parse(control.Text);
                                break;
                            case "Int16Data":
                                val = short.Parse(control.Text);
                                break;
                            case "Id":
                                val = int.Parse(control.Text);
                                break;
                            case "Int64Data":
                                val = long.Parse(control.Text);
                                break;
                            case "FloatData":
                                val = float.Parse(control.Text);
                                break;
                            case "DoubleData":
                                val = double.Parse(control.Text);
                                break;
                        }
                        dict.Add(Convert.ToString(control.Tag), val);
                    }
                }
                Test test = ConversionUtil.DictionaryToCustomClass<Test>(dict);
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                string path = $"{AppDomain.CurrentDomain.BaseDirectory}test.bin";
                using (System.IO.FileStream stream = new System.IO.FileStream(path, System.IO.FileMode.Create))
                {
                    binaryFormatter.Serialize(stream, test);
                }
                byte[] buffer = System.IO.File.ReadAllBytes(path);
                DisplayMessage($"长度:{buffer.Length}\n字节流:\n{string.Join(",", buffer)}");
                DisplayMessage("------打印每个属性的字节流编码,以及查找序列化的二进制数据------");
                SeekData(TypeCode.Byte, "ByteData", test.ByteData, buffer);
                SeekData(TypeCode.Int16, "Int16Data", test.Int16Data, buffer);
                SeekData(TypeCode.Int32, "Id", test.Id, buffer);
                SeekData(TypeCode.Int64, "Int64Data", test.Int64Data, buffer);
                SeekData(TypeCode.Single, "FloatData", test.FloatData, buffer);
                SeekData(TypeCode.Double, "DoubleData", test.DoubleData, buffer);
                SeekData(TypeCode.String, "TestName", test.TestName, buffer);

                DisplayMessage("------打印可转换为ASCII的字符,如果是看不到的特殊字符,显示源字节------");
                string ascii = "";
                for (int i = 0; i < buffer.Length; i++)
                {
                    if (buffer[i] >= 32 && buffer[i] <= 126)
                    {
                        ascii += (char)buffer[i];
                    }
                    else
                    {
                        ascii += $"【{buffer[i]}】";
                    }
                }
                DisplayMessage(ascii);
            }
            catch (Exception ex) 
            {
                DisplayMessage($"序列化时出现异常:【{ex.Message}】");
            }
        }

        /// <summary>
        /// 从字节流中查找数据
        /// </summary>
        /// <param name="typeCode"></param>
        /// <param name="propertyName"></param>
        /// <param name="val"></param>
        /// <param name="buffer"></param>
        private void SeekData(TypeCode typeCode, string propertyName, object val, byte[] buffer) 
        {
            int byteCount = 0;//类型所占用的字节个数
            byte[] dataBuffer = new byte[0];
            switch (typeCode)
            {
                case TypeCode.Byte:
                    byteCount = sizeof(byte);//类型所占用的字节个数
                    dataBuffer = new byte[] { (byte)val };
                    break;
                case TypeCode.Int16:
                    byteCount = sizeof(short);
                    dataBuffer = BitConverter.GetBytes((short)val);
                    break;
                case TypeCode.Int32:
                    byteCount = sizeof(int);
                    dataBuffer = BitConverter.GetBytes((int)val);
                    break;
                case TypeCode.Int64:
                    byteCount = sizeof(long);
                    dataBuffer = BitConverter.GetBytes((long)val);
                    break;
                case TypeCode.Single:
                    byteCount = sizeof(float);
                    dataBuffer = BitConverter.GetBytes((float)val);
                    break;
                case TypeCode.Double:
                    byteCount = sizeof(double);
                    dataBuffer = BitConverter.GetBytes((double)val);
                    break;
                default:
                    dataBuffer = Encoding.ASCII.GetBytes(Convert.ToString(val));
                    byteCount = dataBuffer.Length;
                    break;
            }
            DisplayMessage($"{propertyName}属性的值为【{val}】,字节流【{string.Join(",", dataBuffer)}】");
            List<int> listIndex = new List<int>();
            for (int i = 0; i < buffer.Length - byteCount; i++)
            {
                bool[] bArray = new bool[byteCount];
                for (int cnt = 0; cnt < byteCount; cnt++)
                {
                    bArray[cnt] = buffer[i + cnt] == dataBuffer[cnt];
                }
                //查找所有元素是否都为true
                if (Array.FindAll(bArray, x => x).Length == byteCount) 
                {
                    listIndex.Add(i);
                }
            }
            DisplayMessage($"\x20\x20\x20【{propertyName}】数据的地址查找到的起始索引有【{string.Join(",", listIndex)}】");
        }

        private void btnDeserial_Click(object sender, EventArgs e)
        {
            Test test = null;
            string path = $"{AppDomain.CurrentDomain.BaseDirectory}test.bin";
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            using (System.IO.FileStream stream = new System.IO.FileStream(path, System.IO.FileMode.Open))
            {
                test = binaryFormatter.Deserialize(stream) as Test;
            }
            if(test == null) 
            {
                return;
            }
            Dictionary<string, object> dict = ConversionUtil.CustomClassToDictionary(test);
            for (int i = 0; i < panel1.Controls.Count; i++)
            {
                Control control = panel1.Controls[i];
                if (control is TextBox)
                {
                    control.Text = "";
                    switch (Convert.ToString(control.Tag))
                    {
                        case "ByteData":
                            control.Text = Convert.ToString(test.ByteData);
                            break;
                        case "Int16Data":
                            control.Text = Convert.ToString(test.Int16Data);
                            break;
                        case "Id":
                            control.Text = Convert.ToString(test.Id);
                            break;
                        case "Int64Data":
                            control.Text = Convert.ToString(test.Int64Data);
                            break;
                        case "FloatData":
                            control.Text = Convert.ToString(test.FloatData);
                            break;
                        case "DoubleData":
                            control.Text = Convert.ToString(test.DoubleData);
                            break;
                        default:
                            control.Text = test.TestName;
                            break;
                    }
                }
            }
        }
    }
}

六.窗体运行如图:

我们可以发现 生成的字节码中 含有 程序集和类信息,以及属性名和对应的值

程序集和版本信息

类和属性字段以及相关数据类型信息

<TestName> 这里的TestName就是属性字段,如果需要获取该字段的类型,可以使用System.Reflection反射来获取具体的数据类型

具体数值区域

这里的数据大多都是使用BitConverter.GetBytes(数据值)进行字节流转化

七.BinaryFormatter序列化的不安全性

 如果将二进制文件读取出来,然后篡改指定的字节码,生成新的二进制文件,然后重新读取,结果就会发现数据被篡改了.很多单机游戏的存档文件修改就是这么干的.

这也是不提倡使用BinaryFormatter类进行序列化的原因,可能被劫持篡改二进制文件,因此在最新的net 5.0中会被标记为不安全的和过时的原因

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

51单片机之引脚图(详解)

8051单片机引脚分类与功能笔记 1. 电源引脚 VCC&#xff08;第40脚&#xff09;&#xff1a;接入5V电源&#xff0c;为单片机提供工作电压。GND&#xff08;第20脚&#xff09;&#xff1a;接地端&#xff0c;确保电路的电位参考点。 2.时钟引脚 XTAL1&#xff08;第19脚&a…

jupyterLab插件开发

jupyter lab安装、配置&#xff1a; jupyter lab安装、配置教程_容器里装jupyterlab-CSDN博客 『Linux笔记』服务器搭建神器JupyterLab_linux_布衣小张-腾讯云开发者社区 Jupyter Lab | 安装、配置、插件推荐、多用户使用教程-腾讯云开发者社区-腾讯云 jupyterLab插件开发教…

配置#include “nlohmann/json.hpp“,用于处理json文件

#include “nlohmann/json.hpp” // 需要安装 nlohmann/json.hpp 头文件 using json = nlohmann::json; 下载链接:https://github.com/nlohmann/json/tree/develop 1.下载并解压:首先,需要从nlohmann/json的GitHub仓库下载源代码,并解压得到的文件。 地址: nlohmann/json…

MATLAB | 基于Theil-Sen斜率和Mann-Kendall检验的栅格数据趋势分析

最近看到一些博主分享关于 SenMK 检验的代码&#xff0c;对于新手来说可能有点复杂。我们编写了一段 MATLAB 代码&#xff0c;能够一次性解决这些问题&#xff0c;简化操作流程。我们还准备了几个关于趋势检验的空间分布图&#xff0c;供大家参考。 一、Sens Slope和Mann-Kenda…

Windows 系统下使用 Ollama 离线部署 DeepSeek - R1 模型指南

引言 随着人工智能技术的飞速发展&#xff0c;各类大语言模型层出不穷。DeepSeek - R1 凭借其出色的语言理解和生成能力&#xff0c;受到了广泛关注。而 Ollama 作为一款便捷的模型管理和部署工具&#xff0c;能够帮助我们轻松地在本地环境中部署和使用模型。本文将详细介绍如…

Docker、Ollama、Dify 及 DeepSeek 安装配置与搭建企业级本地私有化知识库实践

在现代企业中&#xff0c;管理和快速访问知识库是提升工作效率、促进创新的关键。为了满足这些需求&#xff0c;企业越来越倾向于构建本地私有化的知识库系统&#xff0c;这样可以更好地保护企业数据的安全性和隐私性。本文将介绍如何利用 **Docker**、**Ollama**、**Dify** 和…

【漫话机器学习系列】087.常见的神经网络最优化算法(Common Optimizers Of Neural Nets)

常见的神经网络优化算法 1. 引言 在深度学习中&#xff0c;优化算法&#xff08;Optimizers&#xff09;用于更新神经网络的权重&#xff0c;以最小化损失函数&#xff08;Loss Function&#xff09;。一个高效的优化算法可以加速训练过程&#xff0c;并提高模型的性能和稳定…

【JVM详解四】执行引擎

一、概述 Java程序运行时&#xff0c;JVM会加载.class字节码文件&#xff0c;但是字节码并不能直接运行在操作系统之上&#xff0c;而JVM中的执行引擎就是负责将字节码转化为对应平台的机器码让CPU运行的组件。 执行引擎是JVM核心的组成部分之一。可以把JVM架构分成三部分&am…

route 与 router 之间的差别

简述&#xff1a; router&#xff1a;主要用于处理一些动作&#xff0c; route&#xff1a;主要获得或处理一些数据&#xff0c;比如地址、参数等 例&#xff1a; videoInfo1.vue&#xff1a; <template><div class"video-info"><h3>二级组件…

SamWaf开源轻量级的网站应用防火墙(安装包),私有化部署,加密本地存储的数据,易于启动,并支持 Linux 和 Windows 64 位和 Arm64

一、SamWaf轻量级开源防火墙介绍 &#xff08;文末提供下载&#xff09; SamWaf网站防火墙是一款适用于小公司、工作室和个人网站的开源轻量级网站防火墙&#xff0c;完全私有化部署&#xff0c;数据加密且仅保存本地&#xff0c;一键启动&#xff0c;支持Linux&#xff0c;Wi…

极客说|利用 Azure AI Agent Service 创建自定义 VS Code Chat participant

作者&#xff1a;卢建晖 - 微软高级云技术布道师 「极客说」 是一档专注 AI 时代开发者分享的专栏&#xff0c;我们邀请来自微软以及技术社区专家&#xff0c;带来最前沿的技术干货与实践经验。在这里&#xff0c;您将看到深度教程、最佳实践和创新解决方案。关注「极客说」&a…

windows + visual studio 2019 使用cmake 编译构建静、动态库并调用详解

环境 windows visual studio 2019 visual studio 2019创建cmake工程 1. 静态库.lib 1.1 静态库编译生成 以下是我创建的cmake工程文件结构&#xff0c;只关注高亮文件夹部分 libout 存放编译生成的.lib文件libsrc 存放编译用的源代码和头文件CMakeLists.txt 此次编译CMak…

【kafka实战】05 Kafka消费者消费消息过程源码剖析

1. 概述 Kafka消费者&#xff08;Consumer&#xff09;是Kafka系统中负责从Kafka集群中拉取消息的客户端组件。消费者消费消息的过程涉及多个步骤&#xff0c;包括消费者组的协调、分区分配、消息拉取、消息处理等。本文将深入剖析Kafka消费者消费消息的源码&#xff0c;并结合…

[EAI-033] SFT 记忆,RL 泛化,LLM和VLM的消融研究

Paper Card 论文标题&#xff1a;SFT Memorizes, RL Generalizes: A Comparative Study of Foundation Model Post-training 论文作者&#xff1a;Tianzhe Chu, Yuexiang Zhai, Jihan Yang, Shengbang Tong, Saining Xie, Dale Schuurmans, Quoc V. Le, Sergey Levine, Yi Ma 论…

算法与数据结构(字符串相乘)

题目 思路 这道题我们可以使用竖式乘法&#xff0c;从右往左遍历每个乘数&#xff0c;将其相乘&#xff0c;并且把乘完的数记录在nums数组中&#xff0c;然后再进行进位运算&#xff0c;将同一列的数进行相加&#xff0c;进位。 解题过程 首先求出两个数组的长度&#xff0c;…

DeepSeek从入门到精通:全面掌握AI大模型的核心能力

文章目录 一、DeepSeek是什么&#xff1f;性能对齐OpenAI-o1正式版 二、Deepseek可以做什么&#xff1f;能力图谱文本生成自然语言理解与分析编程与代码相关常规绘图 三、如何使用DeepSeek&#xff1f;四、DeepSeek从入门到精通推理模型推理大模型非推理大模型 快思慢想&#x…

【异常解决】在idea中提示 hutool 提示 HttpResponse used withoud try-with-resources statement

博主介绍&#xff1a;✌全网粉丝22W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…

【Uniapp-Vue3】UniCloud云数据库获取指定字段的数据

使用where方法可以获取指定的字段&#xff1a; let db uniCloud.database(); db.collection("数据表").where({字段名1:数据, 字段名2:数据}).get({getOne:true}) 如果我们不在get中添加{getOne:true}&#xff0c;在只获取到一个数据res.result.data将会是一个数组&…

信息科技伦理与道德3-2:智能决策

2.2 智能推荐 推荐算法介绍 推荐系统&#xff1a;猜你喜欢 https://blog.csdn.net/search_129_hr/article/details/120468187 推荐系统–矩阵分解 https://blog.csdn.net/search_129_hr/article/details/121598087 案例一&#xff1a;YouTube推荐算法向儿童推荐不适宜视频 …

Visual Studio 2022 中使用 Google Test

要在 Visual Studio 2022 中使用 Google Test (gtest)&#xff0c;可以按照以下步骤进行&#xff1a; 安装 Google Test&#xff1a;确保你已经安装了 Google Test。如果没有安装&#xff0c;可以通过 Visual Studio Installer 安装。在安装程序中&#xff0c;找到并选择 Googl…