楼主: W161203002417jP
165 0

[学科前沿] .NET Core与Windows硬件交互技术全景 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-7-20
最后登录
2018-7-20

楼主
W161203002417jP 发表于 2025-11-24 18:58:43 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

1. ???? Windows注册表操作

Windows 注册表是操作系统中用于集中存储配置信息的层次化数据库,涵盖了系统设置与应用程序参数。通过 .NET Core 中的 Microsoft.Win32 命名空间,开发者可以对注册表进行读写操作,从而实现对系统行为的调整,例如更改显示配置或电源策略等,间接完成硬件相关控制。

原理解说

注册表作为 Windows 系统的核心配置仓库,支持运行时动态修改系统及软件行为。借助 .NET 提供的 API,程序可在具备权限的前提下访问特定键值,实现配置持久化或获取环境信息。

核心组件

  • Registry 类:提供对注册表根键的静态引用,如 HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE 等。
  • RegistryKey 类:表示一个具体的注册表项,可用于创建、打开、读取、写入和删除子项与值。
Registry.LocalMachine
Registry.CurrentUser

实战示例

using Microsoft.Win32;
using System;

public class RegistryOperations
{
    public static void ReadWriteRegistry()
    {
        try
        {
            // 在 HKEY_CURRENT_USER 下创建或打开 Software\MyApp 子项
            using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\MyApp"))
            {
                if (key != null)
                {
                    // 写入多种类型的数据
                    key.SetValue("Version", "1.0.0", RegistryValueKind.String);
                    key.SetValue("LastRun", DateTime.Now.ToString(), RegistryValueKind.String);
                    key.SetValue("StartWithOS", 1, RegistryValueKind.DWord);

                    // 读取已设置的值
                    string version = key.GetValue("Version") as string;
                    string lastRun = key.GetValue("LastRun") as string;
                    int startWithOS = (int)key.GetValue("StartWithOS", 0);

                    Console.WriteLine($"版本: {version}, 最后运行: {lastRun}, 随系统启动: {startWithOS}");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"注册表操作失败: {ex.Message}");
        }
    }

    // 示例:读取系统基本信息
    public static void ReadSystemInfo()
    {
        try
        {
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))
            {
                if (key != null)
                {
                    string productName = key.GetValue("ProductName") as string;
                    string currentVersion = key.GetValue("CurrentVersion") as string;
                    Console.WriteLine($"操作系统: {productName}");
                    Console.WriteLine($"系统版本: {currentVersion}");
                }
            }
        }
        catch (UnauthorizedAccessException)
        {
            Console.WriteLine("权限不足,无法访问系统注册表");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"发生异常: {ex.Message}");
        }
    }
}

关键注意事项

  • 操作注册表需要适当的权限(尤其是 LocalMachine 分支),否则会抛出 UnauthorizedAccessException
  • 错误的修改可能导致系统不稳定,建议在修改前备份关键键值。
  • 应始终使用 using 语句确保资源正确释放。
Registry
RegistryKey

2. ???? WMI硬件信息查询

WMI(Windows Management Instrumentation)是微软提供的系统管理框架,允许应用程序查询本地或远程计算机的硬件与系统状态,如 CPU 使用率、内存容量、磁盘型号、网络适配器信息等。

原理解说

基于 COM 技术构建,WMI 提供标准化的对象模型(CIM),通过 SQL 类似的 WQL 查询语言获取系统数据。它适用于监控工具、诊断程序和资产管理场景。

核心组件

  • ManagementObjectSearcher:执行 WQL 查询并返回结果集合。
  • ManagementObject:代表单个查询到的实例对象(如一个物理磁盘)。
  • WqlObjectQuery:封装 WQL 查询语句。

实战示例

using System.Management;

public static void QueryCPUInfo()
{
    try
    {
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor");
        foreach (ManagementObject obj in searcher.Get())
        {
            Console.WriteLine($"处理器名称: {obj["Name"]}");
            Console.WriteLine($"核心数: {obj["NumberOfCores"]}");
            Console.WriteLine($"最大时钟频率: {obj["MaxClockSpeed"]} MHz");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"WMI 查询失败: {ex.Message}");
    }
}

关键注意事项

  • 仅支持数据查询,不能用于修改或控制硬件状态。
  • 性能开销较大,频繁调用可能影响应用响应速度。
  • 需引用 System.Management NuGet 包,并注意目标平台兼容性。
ManagementObjectSearcher

3. ???? .NET IoT库硬件协议控制

.NET IoT 库(IoT.Device.Bindings)为物联网开发提供了跨平台支持,允许通过标准通信协议直接与外部硬件设备交互,适用于树莓派、工业控制器等嵌入式场景。

原理解说

该库基于 .NET Standard 构建,利用 Linux 的 sysfs 或 Windows 的 HID/串口驱动,实现对 GPIO、I2C、SPI 和 UART 接口的统一访问,常配合 USB 转串口适配器或传感器模块使用。

核心组件

  • GpioController:管理通用输入输出引脚的状态。
  • I2cDevice:用于连接 I2C 总线上的设备(如温度传感器)。
  • SpiDevice:支持高速全双工通信,常用于显示屏或 ADC 芯片。

实战示例

using System.Device.Gpio;
using Iot.Device.Mcp23xxx;

// 初始化GPIO控制器
using GpioController controller = new();
var mcp = new Mcp23017(0x20, new I2cConnectionSettings(1, 0x20));

// 控制扩展IO口上的LED
mcp.Write(Mcp23017Port.PortA, 0xFF); // 输出高电平

关键注意事项

  • 需要硬件支持(如 USB-I2C 适配器或开发板)。
  • 部分功能依赖操作系统底层接口,在 Windows 上可用性受限。
  • 推荐在 Linux ARM 平台(如 Raspberry Pi)上使用以获得最佳体验。
Iot.Device.FtCommon
Ft232HDevice

4. ?? P/Invoke原生API调用

P/Invoke(Platform Invocation Services)允许托管代码调用非托管的 Windows 原生 DLL 函数,实现对低级系统功能的直接访问,如触控板输入、设备驱动交互等。

核心技术

  • 通过 [DllImport] 特性导入外部函数。
  • 手动定义结构体与函数签名,匹配原生 API。

交互方法

典型流程包括:声明外部方法 → 定义所需数据结构 → 调用 API → 处理返回结果与错误码。

典型应用场景

  • 访问未被 .NET 封装的 Windows API。
  • 与专用硬件驱动通信(如工业设备、生物识别模块)。
  • 实现原始输入设备监听(如多点触控、笔输入)。

关键组件/类

  • user32.dll:窗口与消息处理。
  • kernel32.dll:文件、内存与进程操作。
  • hid.dll:人机接口设备通信。

优势

  • 可访问最底层系统功能,灵活性极高。
  • 性能接近原生代码,适合高性能需求场景。

局限性

  • 代码复杂度高,需精确匹配数据类型与调用约定。
  • 跨平台兼容性差,通常仅限 Windows 使用。
  • 容易引发内存泄漏或崩溃,调试困难。

实战示例:触控板访问

[DllImport("user32.dll")]
public static extern bool RegisterRawInputDevices(RawInputDevice[] pRawInputDevices, uint uiNumDevices, uint cbSize);

// 结构体定义省略...
// 注册接收来自触控板的原始输入事件
DllImport

2. WMI硬件信息查询详解

原理解析:WMI(Windows Management Instrumentation)是Windows操作系统中用于系统管理的核心组件,它提供了一套统一的接口,允许程序访问本地或远程计算机的软硬件信息。通过使用类似SQL的查询语言——WQL(WMI Query Language),开发者可以灵活地检索所需的系统数据。

核心类说明

  • ManagementObjectSearcher:用于执行WMI查询,并获取结果集合。
  • ManagementObjectCollection:表示由查询返回的一组WMI对象。
  • ManagementObject:代表一个具体的WMI对象实例,包含其属性和可调用的方法。
HKEY_LOCAL_MACHINE

实际应用示例

以下代码展示了如何利用C#结合WMI技术获取关键硬件信息:


using System;
using System.Management;
using System.Collections.Generic;

public class HardwareInfoQuery
{
    public static void QueryHardwareInfo()
    {
        // 获取CPU相关信息
        QueryCPUInfo();
        
        // 获取磁盘驱动器详情
        QueryDiskInfo();
        
        // 查询网络适配器配置
        QueryNetworkAdapters();
    }

    public static void QueryCPUInfo()
    {
        try
        {
            string query = "SELECT * FROM Win32_Processor";
            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
            {
                ManagementObjectCollection results = searcher.Get();
                Console.WriteLine("=== CPU信息 ===");
                foreach (ManagementObject mo in results)
                {
                    string name = mo["Name"] as string;
                    string manufacturer = mo["Manufacturer"] as string;
                    string maxClockSpeed = mo["MaxClockSpeed"] as string;
                    string cores = mo["NumberOfCores"] as string;
                    Console.WriteLine($"处理器: {name}");
                    Console.WriteLine($"制造商: {manufacturer}");
                    Console.WriteLine($"最大主频: {maxClockSpeed} MHz");
                    Console.WriteLine($"核心数: {cores}");
                    Console.WriteLine("------------------------");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"查询CPU信息失败: {ex.Message}");
        }
    }

    public static void QueryDiskInfo()
    {
        try
        {
            string query = "SELECT * FROM Win32_LogicalDisk WHERE DriveType = 3";
            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
            {
                ManagementObjectCollection results = searcher.Get();
                Console.WriteLine("=== 磁盘信息 ===");
                foreach (ManagementObject mo in results)
                {
                    string deviceId = mo["DeviceID"] as string;
                    string size = mo["Size"] as string;
                    string freeSpace = mo["FreeSpace"] as string;
                    if (long.TryParse(size, out long totalSize) &&
                        long.TryParse(freeSpace, out long free))
                    {

RegistryKey

重要注意事项

权限管理:大多数WMI操作需要管理员权限才能正常运行,尤其是在访问敏感硬件信息或远程系统时,请确保当前执行账户具备足够的权限。

IDisposable

资源释放机制:所有实现了IDisposable接口的对象(如ManagementObjectSearcher),必须通过using语句或显式调用Dispose()方法来及时释放非托管资源,防止内存泄漏。

using

数据类型处理:在读取WMI返回的数据时,应注意字段的实际类型。部分数值型字段以字符串形式返回,需进行正确解析(如使用long.TryParse),避免转换异常。

RegistryValueKind

异常处理策略:WMI操作可能抛出多种异常类型,例如COMException、UnauthorizedAccessException等。建议在每个查询模块中都加入try-catch结构,提升程序健壮性。

SecurityException
UnauthorizedAccessException
// 计算磁盘总容量(GB),并保留两位小数
double totalGB = Math.Round(totalSize / (1024.0 * 1024.0 * 1024.0), 2);

// 计算可用空间(GB),保留两位小数
double freeGB = Math.Round(free / (1024.0 * 1024.0 * 1024.0), 2);

// 计算已使用空间的百分比,保留两位小数
double usedPercent = Math.Round((totalSize - free) * 100.0 / totalSize, 2);

// 输出设备信息
Console.WriteLine($"磁盘: {deviceId}");
Console.WriteLine($"总大小: {totalGB} GB");
Console.WriteLine($"可用空间: {freeGB} GB");
Console.WriteLine($"使用率: {usedPercent}%");
Console.WriteLine("------------------------");
}
}
}
}
catch (Exception ex)
{
    Console.WriteLine($"查询磁盘信息失败: {ex.Message}");
}
}

/// 
/// 查询当前启用的网络适配器信息
/// 
public static void QueryNetworkAdapters()
{
    try
    {
        // WMI 查询语句:获取所有已启用的网络适配器
        string query = "SELECT * FROM Win32_NetworkAdapter WHERE NetEnabled = True";
        
        using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
        {
            ManagementObjectCollection results = searcher.Get();
            Console.WriteLine("=== 网络适配器 ===");

            foreach (ManagementObject mo in results)
            {
                string name = mo["Name"] as string;
                string adapterType = mo["AdapterType"] as string;
                string macAddress = mo["MACAddress"] as string;
                string netEnabled = mo["NetEnabled"]?.ToString();

                // 只输出具有有效 MAC 地址的适配器
                if (!string.IsNullOrEmpty(macAddress))
                {
                    Console.WriteLine($"适配器名称: {name}");
                    Console.WriteLine($"类型: {adapterType}");
                    Console.WriteLine($"MAC地址: {macAddress}");
                    Console.WriteLine("------------------------");
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"查询网络适配器失败: {ex.Message}");
    }
}

关键注意事项

  • 性能考虑:WMI 查询操作可能耗时较长,建议避免在高频执行或对响应速度要求高的代码路径中频繁调用。
  • 异常处理:由于系统权限限制或 WMI 服务未启动等原因,可能导致查询失败,需做好异常捕获与容错处理。
  • 字段验证:从 WMI 获取的对象属性可能存在 null 值,访问前应进行空值判断以防止运行时异常。
  • 命名空间引用:使用 WMI 功能需要引入相应的程序集和命名空间支持。
System.Management

.NET IoT 库与硬件协议控制

原理说明

.NET IoT Libraries 提供了一套跨平台的 API 接口,用于控制常见的硬件通信协议,如 GPIO、I2C 和 SPI。通过 USB 转串口适配器(例如 FTDI FT232H),开发者可以在普通 PC 上实现与外部硬件设备的数据交互。

核心组件介绍

  • FtCommon 类:负责检测和枚举系统中连接的 FTDI 设备。
  • Ft232HDevice 类:表示一个 FT232H 适配器实例,可用于创建 GPIO、I2C 或 SPI 控制器对象。
  • GpioController 类:提供对通用输入输出引脚的读写控制功能。
  • I2cDevice 类:封装 I2C 通信逻辑,便于与支持该协议的传感器或外设通信。

实际应用示例

using System;
using System.Device.Gpio;
using System.Device.I2c;
using System.Threading;
using Iot.Device.Ft232H;
using Iot.Device.FtCommon;
using Iot.Device.Bmxx80;
using Iot.Device.Bmxx80.PowerMode;

public class IotHardwareControl
{
    public static void ControlGpioAndI2c()
    {
        // 枚举当前可用的 FTDI 设备
        var devices = FtCommon.GetDevices();
        if (devices.Count == 0)
        {

Console.WriteLine("未找到FTDI设备,请确保设备已连接且驱动程序已安装");
return;

Console.WriteLine($"找到 {devices.Count} 个FTDI设备");
// 选取列表中的首个设备进行操作
var device = devices[0];
Console.WriteLine($"使用设备: {device.Description}");

// 执行GPIO控制演示
GpioControlExample(device);

// 演示I2C设备通信
I2cDeviceExample(device);

GPIO 控制示例说明

public static void GpioControlExample(FtDevice device)
{
    try
    {
        using (var ft232h = new Ft232HDevice(device))
        using (var gpio = ft232h.CreateGpioController())
        {
            // 根据引脚名称获取对应的编号(D7为FT232H上的物理引脚)
            int ledPin = Ft232HDevice.GetPinNumberFromString("D7");

            // 配置该引脚为输出模式并开启
            gpio.OpenPin(ledPin, PinMode.Output);
            Console.WriteLine("GPIO控制示例: LED闪烁(按任意键停止)");

            // 实现LED的周期性亮灭
            bool ledOn = true;
            while (!Console.KeyAvailable)
            {
                gpio.Write(ledPin, ledOn ? PinValue.High : PinValue.Low);
                Thread.Sleep(500);
                ledOn = !ledOn;
            }
            Console.ReadKey(); // 清除输入缓存

            // 停止闪烁,关闭LED并释放引脚资源
            gpio.Write(ledPin, PinValue.Low);
            gpio.ClosePin(ledPin);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"GPIO控制失败: {ex.Message}");
    }
}

I2C 设备操作示例

public static void I2cDeviceExample(FtDevice device)
{
    try
    {
        using (var ft232h = new Ft232HDevice(device))
        {
            // 设置I2C连接参数,地址为0x76
            var i2cSettings = new I2cConnectionSettings(0, 0x76);

            // 初始化I2C设备实例
            using (var i2cDevice = ft232h.CreateI2cDevice(i2cSettings))
            using (var bme280 = new Bme280(i2cDevice))
            {
                // 配置传感器进入强制测量模式
                bme280.SetPowerMode(Bmx280PowerMode.Forced);
                int measurementTime = bme280.GetMeasurementDuration();
                Console.WriteLine("I2C传感器读取示例(BME280):");

                // 连续读取5次环境数据
                for (int i = 0; i < 5; i++)
                {
                    bme280.SetPowerMode(Bmx280PowerMode.Forced);
                    Thread.Sleep(measurementTime);

                    // 尝试获取温湿度和气压数据
                    if (bme280.TryReadTemperature(out var temperature) &&
                         bme280.TryReadPressure(out var pressure) &&
                         bme280.TryReadHumidity(out var humidity))
                    {
                        Console.WriteLine($"温度: {temperature.DegreesCelsius:0.##}°C");
                        Console.WriteLine($"压力: {pressure.Hectopascals:0.##} hPa");
                        Console.WriteLine($"湿度: {humidity.Percent:0.##}%");
                        Console.WriteLine("------------------------");
                    }
                    Thread.Sleep(2000);
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"I2C设备操作失败: {ex.Message}");
    }
}

关键注意事项

  • 驱动程序:必须预先安装FTDI D2XX驱动程序以确保设备正常识别。
  • 硬件连接:请正确连接各物理引脚,并确认I2C或SPI模式开关配置无误。
  • 资源释放:在操作结束后应及时关闭并释放GPIO引脚及相关设备资源。
  • 电压匹配:确保目标外设与FT232H模块之间的电平兼容,避免因电压不匹配造成损坏。

为保证设备安全,需确保逻辑电平的一致性,防止因电平不匹配造成硬件损坏。

P/Invoke调用原生API详解

在.NET环境中,部分Windows API并未提供托管封装。此时可借助P/Invoke(平台调用服务)机制,直接调用存在于动态链接库(DLL)中的非托管函数。该方法特别适用于需要与底层系统功能或硬件进行深度交互的应用场景。

实际应用案例:触控板数据获取

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class RawInputTouchpad
{
    // 声明必要的Windows API函数
    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    private static extern bool RegisterRawInputDevices(
        RAWINPUTDEVICE[] pRawInputDevices,
        uint uiNumDevices,
        uint cbSize);

    [DllImport("user32.dll")]
    private static extern uint GetRawInputData(
        IntPtr hRawInput,
        uint uiCommand,
        IntPtr pData,
        ref uint pcbSize,
        uint cbSizeHeader);

    // 消息常量定义
    private const int WM_INPUT = 0x00FF;
    private const int RIDEV_INPUTSINK = 0x00000100;

    // HID使用页和使用值定义
    private const ushort HID_USAGE_PAGE_GENERIC = 0x01;
    private const ushort HID_USAGE_GENERIC_MOUSE = 0x02;

    // 原始输入设备结构体
    [StructLayout(LayoutKind.Sequential)]
    public struct RAWINPUTDEVICE
    {
        public ushort usUsagePage;
        public ushort usUsage;
        public uint dwFlags;
        public IntPtr hwndTarget;
    }

    private IntPtr _windowHandle;

    public RawInputTouchpad(IntPtr windowHandle)
    {
        _windowHandle = windowHandle;
        RegisterForRawInput();
    }

    private void RegisterForRawInput()
    {
        RAWINPUTDEVICE[] devices = new RAWINPUTDEVICE[1];

        // 配置设备监听参数:注册通用HID鼠标类设备(含触控板)
        devices[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
        devices[0].usUsage = HID_USAGE_GENERIC_MOUSE;
        devices[0].dwFlags = RIDEV_INPUTSINK;
        devices[0].hwndTarget = _windowHandle;

        // 执行注册操作
        if (!RegisterRawInputDevices(devices, (uint)devices.Length,
            (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICE))))
        {
            throw new ApplicationException("注册原始输入设备失败");
        }
    }

    // 窗口消息处理入口,用于捕获WM_INPUT消息
    public void ProcessInputMessage(Message m)
    {
        if (m.Msg == WM_INPUT)
        {
            uint dataSize = 0;

            // 第一次调用获取所需缓冲区大小
            GetRawInputData(m.LParam, 0x10000003, IntPtr.Zero,
                ref dataSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER)));

            if (dataSize > 0)
            {
                IntPtr buffer = Marshal.AllocHGlobal((int)dataSize);
                try
                {
                    // 第二次调用获取实际的原始输入数据
                    if (GetRawInputData(m.LParam, 0x10000003, buffer,
                        ref dataSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dataSize)
                    {
                        // 此处可进一步解析触控板输入数据
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(buffer);
                }
            }
        }
    }
}

上述代码展示了如何通过注册原始输入设备来监听来自触控板的底层输入事件。核心在于使用RegisterRawInputDevices函数注册目标设备,并在窗口消息循环中处理WM_INPUT消息以提取原始数据流。

private void ProcessTouchpadData(IntPtr rawData)
{
    // 解析来自触控板的原始输入数据
    // 具体解析逻辑需依据设备的数据格式进行适配
    Console.WriteLine("收到触控板输入数据");
}

private void RegisterTouchpadDevice(IntPtr windowHandle)
{
    try
    {
        var buffer = Marshal.AllocHGlobal(1024);
        
if (RegisterRawInputDevices(devices, (uint)devices.Length, (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICE))) == false) { throw new InvalidOperationException("注册触控板设备失败"); } } finally { Marshal.FreeHGlobal(buffer); } }

在Windows窗体中集成使用

以下是一个典型的窗体类实现,用于接收并处理来自触控板的原始输入:

public partial class MainForm : Form
{
    private RawInputTouchpad _touchpad;

    public MainForm()
    {
        InitializeComponent();
        _touchpad = new RawInputTouchpad(this.Handle);
    }

    protected override void WndProc(ref Message m)
    {
        _touchpad?.ProcessInputMessage(m);
        base.WndProc(ref m);
    }
}

重要实现说明

  • 平台依赖性:所使用的P/Invoke调用仅适用于Windows系统,无法在其他操作系统上运行。
  • 数据类型映射:必须确保原生API中的数据类型与.NET托管类型正确对应,避免内存访问错误。
  • 错误处理机制:应始终检查系统API的返回状态,并对异常情况做出适当响应。
  • 安全性考虑:直接调用底层API可能绕过部分安全策略,使用时应严格控制权限和调用范围。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Windows Window wind core Dow

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-6 07:56