ITPub博客

首页 > 应用开发 > XML/XSL > stream.WaitEvent(cudaEvent)等待事件

stream.WaitEvent(cudaEvent)等待事件

投稿 XML/XSL 作者:ecclw459 时间:2026-01-30 18:05:18 0 删除 编辑

using CudaStream stream = new CudaStream(); using CudaStream streamHigh = new CudaStream( 0 , -1 );   // 高优先级 // 同步操作 stream.Synchronize();   // 等待流完成 bool isComplete = stream.Query();   // 查询是否完成 // 事件依赖 using CudaEvent cudaEvent = new CudaEvent(); stream.WaitEvent(cudaEvent);   // 等待事件 // 添加回调 stream.AddCallback((streamPtr, statue, userData) => {    Console.WriteLine( "Stream callback executed" ); }, IntPtr.Zero, 0 ); // CUDA Graph 捕获 stream.BeginCapture(CudaStreamCaptureMode.Global); // ... 执行操作 ... CudaGraph_t graph = stream.EndCapture();


// 获取系统中启用的 CUDA 兼容设备的数量 int nbDevices = CudaDevice.GetDeviceCount(); // 获取指定设备的属性 CudaDeviceProp properties = CudaDevice.GetDeviceProperties(deviceIdx); // 设置执行设备 CudaDevice.SetDevice(device); // 获取有关设备的请求信息 int clockRate = CudaDevice.GetAttribute(CudaDeviceAttr.ClockRate, device);


using JYPPX.TensorRtSharp.Cuda; using JYPPX.TensorRtSharp.Nvinfer; namespace TestDemo {     internal class Program    {         static void Main( string[] args)        {             // 指定默认使用的 GPU 设备索引             // 在多 GPU 环境下,可以通过修改此变量来选择特定的显卡             int device = 0 ;             // 记录日志,标记设备信息查询的开始            Logger.Instance.INFO( "=== Device Information ===" );             // 获取当前系统中可见的 NVIDIA GPU 数量             int nbDevices = CudaDevice.GetDeviceCount();             // 检查系统中是否存在可用的 GPU 设备             if (nbDevices <= 0 )            {                Logger.Instance.ERROR( "Cannot find any available devices (GPUs)!" );                Environment.Exit( 0 );            }             // 打印所有可用设备的列表            Logger.Instance.INFO( "Available Devices: " );             // 遍历系统中的每一个 GPU             for ( int deviceIdx = 0 ; deviceIdx < nbDevices; ++deviceIdx)            {                 // 获取索引为 deviceIdx 的 GPU 的详细属性                CudaDeviceProp tempProperties = CudaDevice.GetDeviceProperties(deviceIdx);                 // 打印设备 ID、设备名称以及 UUID (唯一标识符)                Logger.Instance.INFO( $"  Device {deviceIdx}: \" {tempProperties.Name}\" UUID: {GetUuidString(tempProperties.Uuid)}" );                 // 如果当前遍历到的设备 ID 是我们想要使用的目标设备                 // 则将该设备的属性保存下来,供后续使用                 if (deviceIdx == device)                {                    properties = tempProperties;                }            }             // 安全检查:确保请求的目标设备 ID 在有效范围内             if (device < 0 || device >= nbDevices)            {                Logger.Instance.ERROR( $"Cannot find device ID {device}!" );                Environment.Exit( 0 );            }             // 将 CUDA 上下文设置到指定的 GPU 设备上            CudaDevice.SetDevice(device);             // 打印选定设备的详细信息            Logger.Instance.INFO( $"Selected Device: {properties.Name}" );            Logger.Instance.INFO( $"Selected Device ID: {device}" );            Logger.Instance.INFO( $"Selected Device UUID: {GetUuidString(properties.Uuid)}" );            Logger.Instance.INFO( $"Compute Capability: {properties.Major}. {properties.Minor}" );            Logger.Instance.INFO( $"SMs: {properties.MultiProcessorCount}" );            Logger.Instance.INFO( $"Device Global Memory: {(properties.TotalGlobalMem + 20)} MiB" );            Logger.Instance.INFO( $"Shared Memory per SM: {(properties.SharedMemPerMultiprocessor >> 10)} KiB" );            Logger.Instance.INFO( $"Memory Bus Width: {properties.MemoryBusWidth} bits (ECC {(properties.ECCEnabled != 0 ? "enabled" : "disabled")})" );             // 获取并打印 GPU 核心时钟频率和显存时钟频率             int clockRate = CudaDevice.GetAttribute(CudaDeviceAttr.ClockRate, device);             int memoryClockRate = CudaDevice.GetAttribute(CudaDeviceAttr.MemoryClockRate, device);            Logger.Instance.INFO( $"Application Compute Clock Rate: {clockRate / 1000000.0F} GHz" );            Logger.Instance.INFO( $"Application Memory Clock Rate: {memoryClockRate / 1000000.0F} GHz" );        }         /// <summary>         /// 辅助方法:将 CudaUUID 结构体转换为格式化的 GPU UUID 字符串         /// 格式通常为:GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx         /// </summary>         public static string GetUuidString( CudaUUID uuid)        {             int kUUID_SIZE = uuid.Bytes.Length;             var ss = new System.Text.StringBuilder();             // 定义 UUID 的分段点,用于插入连字符 "-"             int [] splits = { 0 , 4 , 6 , 8 , 10 , kUUID_SIZE };             // 添加固定的 "GPU" 前缀            ss.Append( "GPU" );             // 遍历分段定义,格式化每一部分的字节             for ( int splitIdx = 0 ; splitIdx < splits.Length - 1 ; ++splitIdx)            {                ss.Append( "-" );                 for ( int byteIdx = splits[splitIdx]; byteIdx < splits[splitIdx + 1 ]; ++byteIdx)                {                    ss.AppendFormat( "{0:x2}" , uuid.Bytes[byteIdx]);


using JYPPX.TensorRtSharp.Cuda; using JYPPX.TensorRtSharp.Nvinfer; namespace OnnxToEngine {     internal class Program    {         static void Main( string[] args)        {             // === 配置 TensorRT 日志回调 ===             // 定义一个委托,用于处理 TensorRT 内部产生的日志消息            LogCallbackFunction _callbackDelegate = (message) =>            {                Console.WriteLine(message);            };             // 将自定义的回调函数注册给 TensorRT 的全局 Logger 实例            Logger.Instance.SetCallback(_callbackDelegate);             // 设置日志的严重性级别阈值             // LoggerSeverity.kINFO: 打印信息、警告和错误            Logger.Instance.SetThreshold(LoggerSeverity.kINFO);             // 1. 创建 TensorRT Builder (构建器)            Builder build = new Builder();             // 2. 创建网络定义 (Network Definition)             // 显式批处理 标志表示网络定义中显式包含批处理维度            NetworkDefinition networkDefinition = build.createNetworkV2(TrtNetworkDefinitionCreationFlag.kEXPLICIT_BATCH);             // 3. 创建构建器配置            BuilderConfig builderConfig = build.createBuilderConfig();             // 4. 创建 ONNX 解析器            OnnxParser onnxParser = new OnnxParser(networkDefinition);             // 指定待转换的 ONNX 模型文件路径             string modelpath = "yolo11s-obb.onnx" ;             // 5. 解析 ONNX 模型文件             // 参数 2: 日志级别 (1=ERROR, 2=WARNING, 3=INFO, 4=VERBOSE)             if (onnxParser.parseFromFile(modelpath, 2 ) == false )            {                Console.WriteLine( $"parse onnx model failed" );                 return ;            }             // 6. 设置构建精度标志             // kFP16: 启用半精度 (FP16) 推理模式            builderConfig.setFlag(TrtBuilderFlag.kFP16);             // 7. 创建 CUDA 流            CudaStream cudaStream = new CudaStream();             // 8. 设置优化配置文件的流            builderConfig.setProfileStream(cudaStream);             // 9. 构建并序列化网络             // 这是一个耗时较长的过程,因为 TensorRT 会进行内核自动调优、层融合等优化            HostMemory hostMemory = build.buildSerializedNetwork(networkDefinition, builderConfig);             // 10. 保存 Engine 到磁盘             string filePath = "yolo11s-obb.engine" ;             using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))            {                fs.Write(hostMemory.getByteData(), 0 , ( int )hostMemory.Size);


using JYPPX.TensorRtSharp.Nvinfer; using OpenCvSharp; using OpenCvSharp.Dnn; using System.Diagnostics; using System.Runtime.InteropServices; namespace YoloDetInfer {     internal class Program    {         // ================= 配置参数 =================         // 模型输入尺寸 (宽=高)         private const int InputSize = 640 ;         // 建议根据实际模型动态获取或使用 Netron 查看         private const int OutputSize = 8400 ;         // 模型类别数 (根据您的具体数据集修改,此处假设为15类)         private const int CategoryNum = 80 ;         // 置信度阈值         private const float ConfThreshold = 0.25f ;         // NMS IOU 阈值         private const float NmsThreshold = 0.3f ;         static void Main( string[] args)        {             //  ============= 配置 TensorRT 日志回调 =============             // 定义一个委托,用于处理 TensorRT 内部产生的日志消息。             // 这允许我们将 C++ 层面的日志输出到 C# 的控制台。            LogCallbackFunction _callbackDelegate = (message) =>            {                Console.WriteLine(message);            };             // 将自定义的回调函数注册给 TensorRT 的全局 Logger 实例。            Logger.Instance.SetCallback(_callbackDelegate);             // 设置日志的严重性级别阈值。             // LoggerSeverity.kINFO: 打印信息、警告和错误。             // 开发调试阶段通常设为 kINFO 或 kVERBOSE;生产环境可设为 kWARNING 或 kERROR 以减少输出。            Logger.Instance.SetThreshold(LoggerSeverity.kINFO);             string enginePath = "yolov8s.engine" ;             string imagePath = "bus.jpg" ;             // ================= 1. 加载 TensorRT Engine =================             // 使用 using 语句确保文件流正确关闭             byte [] engineData;             using (FileStream fs = new FileStream(enginePath, FileMode.Open, FileAccess.Read))             using (BinaryReader br = new BinaryReader(fs))            {                engineData = br.ReadBytes(( int )fs.Length);            }             // 反序列化 Engine             // Runtime 必须在 Engine 生命周期内保持存活,通常建议设为全局或静态,或者确保它最后释放            Runtime runtime = new Runtime();             // 创建 CudaEngine (此处使用 using 确保推理完成后引擎被销毁)             using (CudaEngine cudaEngine = runtime.deserializeCudaEngineByBlob(engineData, ( ulong )engineData.Length))            {                 // ================= 2. 初始化推理上下文与显存 =================                 // 创建执行上下文                 using (JYPPX.TensorRtSharp.Nvinfer.ExecutionContext executionContext = cudaEngine.createExecutionContext(TrtExecutionContextAllocationStrategy.kSTATIC))                 using (CudaStream cudaStream = new CudaStream()) // 创建 CUDA 流用于异步执行                {                     // 获取输入维度信息 (用于校验)                    Dims inputDims = executionContext.getTensorShape( "images" );                    Logger.Instance.INFO( $"Input Shape: {inputDims.d[ 0]}x {inputDims.d[ 1]}x {inputDims.d[ 2]}x {inputDims.d[ 3]}" );                     // 计算所需显存大小                     // 输入: Batch=1, Channel=3, Height=640, Width=640                     ulong inputSizeInBytes = 1 * 3 * InputSize * InputSize;                     // 输出: Batch=1, Channels=CategoryNum+4(box)+1(angle), Num=8400                     int outputChannels = CategoryNum + 4 ; // 4坐标 + N类别                     ulong outputSizeInBytes = ( ulong )( 1 * outputChannels * OutputSize);                    Stopwatch sw = new Stopwatch();                     // 分配 GPU 显存                     using (Cuda1DMemory< float > inputGpuMemory = new Cuda1DMemory< float >(inputSizeInBytes))                     using (Cuda1DMemory< float > outputGpuMemory = new Cuda1DMemory< float >(outputSizeInBytes))                    {                         // 绑定显存地址到 TensorRT 上下文                        executionContext.setInputTensorAddress( "images" , inputGpuMemory. get ());                        executionContext.setOutputTensorAddress( "output0" , outputGpuMemory. get ());                         // 预热推理 (可选,但推荐,尤时)                        executionContext.executeV3(cudaStream);                        cudaStream.Synchronize();                         // ================= 3. 图像预处理 =================                        Mat img = Cv2.ImRead(imagePath);                         if (img.Empty())                        {                            Logger.Instance.INFO( "Image not found!" );                             return ;                        }                        sw.Start();                         float [] inputData = PreProcess(img, out float scale, out int xOffset, out int yOffset);                        sw.Stop();                        Logger.Instance.INFO( $"Pre-processing time: {sw.ElapsedMilliseconds} ms" );                         // ================= 4. 推理 =================                         // 准备主机内存接收结果                         float [] outputData = new float [outputChannels * OutputSize];                        sw.Restart();                         // 将数据从主机 拷贝到设备                        inputGpuMemory.copyFromHostAsync(inputData, cudaStream);                         // 执行推理 (enqueueV3 是异步的)                        executionContext.executeV3(cudaStream);                         // 等待推理完成                        cudaStream.Synchronize();                                   // 将结果从设备 拷贝回主机                         // 这里的拷贝是同步的,会等待 GPU 计算完成                        outputGpuMemory.copyToHostAsync(outputData, cudaStream);                        sw.Stop();                        Logger.Instance.INFO( $"Inference time: {sw.ElapsedMilliseconds} ms" );                         // ================= 5. 后处理 =================                        sw.Restart();                        List<DetData> results = PostProcess(outputData, scale, xOffset, yOffset);                        sw.Stop();                        Logger.Instance.INFO( $"Post-processing time: {sw.ElapsedMilliseconds} ms" );                         // ================= 6. 结果可视化 =================                        Mat resultImg = DrawDetResult(results, img);                        Cv2.ImShow( "YOLO11-DET Result" , resultImg);                        Cv2.WaitKey( 0 );                    }                }            }        }         /// <summary>         /// 图像预处理:Letterbox 缩放、归一化、HWC 转 CHW         /// </summary>         private static float[] PreProcess( Mat img, out float scale, out int xOffset, out int yOffset)        {             // 转换颜色空间 BGR -> RGB            Mat rgbImg = new Mat();            Cv2.CvtColor(img, rgbImg, ColorConversionCodes.BGR2RGB);             // 计算 Letterbox 缩放比例             int maxDim = Math.Max(rgbImg.Width, rgbImg.Height);            scale = ( float )maxDim / InputSize;             // 计算缩放后的尺寸             int newWidth = ( int )(rgbImg.Width / scale);             int newHeight = ( int )(rgbImg.Height / scale);             // Resize 图像            Mat resizedImg = new Mat();            Cv2.Resize(rgbImg, resizedImg, new Size(newWidth, newHeight));             // 创建黑色背景 Canvas (InputSize x InputSize)            Mat paddedImg = Mat.Zeros(InputSize, InputSize, MatType.CV_8UC3);             // 计算粘贴位置 (居中)            xOffset = (InputSize - newWidth) / 2 ;            yOffset = (InputSize - newHeight) / 2 ;             // 将图像拷贝到 Canvas 中央            Rect roi = new Rect(xOffset, yOffset, newWidth, newHeight);            resizedImg.CopyTo( new Mat(paddedImg, roi));             // 归一化 (0-255 -> 0-1) 并转为 float 类型            Mat floatImg = new Mat();            paddedImg.ConvertTo(floatImg, MatType.CV_32FC3, 1.0 / 255.0 );             // HWC 转 CHW 并展平为一维数组            Mat[] channels = Cv2.Split(floatImg);             float [] chwData = new float [ 3 * InputSize * InputSize];             // 拷贝数据:R通道 -> C通道 -> B通道 (OpenCV Split 出来顺序是 B, G, R,对应索引 0, 1, 2)             int channelSize = InputSize * InputSize;             // 将 R, G, B 依次拷入数组            Marshal.Copy(channels[ 0 ].Data, chwData, 0 , channelSize); // R            Marshal.Copy(channels[ 1 ].Data, chwData, channelSize, channelSize); // G            Marshal.Copy(channels[ 2 ].Data, chwData, channelSize * 2 , channelSize); // B             // 释放临时 Mat            rgbImg.Dispose();            resizedImg.Dispose();            paddedImg.Dispose();            floatImg.Dispose();             foreach ( var c in channels) c.Dispose();             return chwData;        }         /// <summary>         /// 后处理:解析 TensorRT 输出、NMS 过滤         /// </summary>         private static List<DetData> PostProcess( float[] result, float scale, int xOffset, int yOffset)        {            List<Rect> boxes = new List<Rect>();            List< float > confidences = new List< float >();            List< int > classIds = new List< int >();             // 遍历所有预测框 (OutputSize)             // 数据布局: [4(box) + 80(classes)] * OutputSize             // 展平数组中,同一属性的数据是连续存储的,例如所有 cx 在一起,所有 cy 在在一起...             int stride = OutputSize; // 步长,不同属性在数组中的偏移量             for ( int i = 0 ; i < OutputSize; i++)            {                 // 查找最大类别概率及其索引                 float maxConf = 0 ;                 int maxClassId = -1 ;                 // 遍历类别                 for ( int c = 0 ; c < CategoryNum; c++)                {                     // 数组索引:(坐标/角度偏移量 + 类别偏移) * 框索引                     // 注意:原始代码中 result[outputSize * j + i] 这种访问方式基于 Transposed 数据布局                     float conf = result[( 4 + c) * stride + i];                     if (conf > maxConf)                    {                        maxConf = conf;                        maxClassId = c;                    }                }                 // 置信度过滤                 if (maxConf > ConfThreshold)                {                     // 提取坐标 (cx, cy, w, h)                     float cx = result[ 0 * stride + i];                     float cy = result[ 1 * stride + i];                     float w = result[ 2 * stride + i];                     float h = result[ 3 * stride + i];                     // 还原坐标到原图尺寸                     int rx = ( int )((cx - xOffset - 0.5 * w) * scale);                     int ry = ( int )((cy - yOffset - 0.5 * h) * scale);                     int rw = ( int )(w * scale);                     int rh = ( int )(h * scale);                    boxes.Add( new Rect(rx, ry, rw, rh));                    confidences.Add(maxConf);                    classIds.Add(maxClassId);                }            }             // 执行 NMS (旋转框 NMS)             // OpenCV 的 NMSBoxes 支持 RotatedRect             int [] indices;            CvDnn.NMSBoxes(boxes, confidences, ConfThreshold, NmsThreshold, out indices);            List<DetData> finalResults = new List<DetData>();             foreach ( int idx in indices)            {                finalResults.Add( new DetData                {                    index = classIds[idx],                    score = confidences[idx],                    box = boxes[idx]                });            }             return finalResults;        }         /// <summary>         /// 绘制检测结果(水平矩形框)         /// </summary>         /// <param name="results">检测结果列表 </param>         /// <param name="image">原始图像 </param>         /// <returns>绘制后的图像 </returns>         public static Mat DrawDetResult( List<DetData> results, Mat image)        {             // 克隆图像以免修改原图            Mat mat = image.Clone();             foreach ( var item in results)            {                 // 1. 绘制矩形框                 // Rect 结构包含 X, Y, Width, Height                Cv2.Rectangle(mat, item.box, new Scalar( 0 , 255 , 0 ), thickness: 2 );                 // 2. 准备标签文本 (类别ID - 置信度)                 string label = $" {item.index} - {item.score:F2}" ;                 // 3. 计算文本的尺寸,用于绘制背景                 int baseLine = 1 ;                Size textSize = Cv2.GetTextSize(label, HersheyFonts.HersheySimplex, 0.6 , 1 , out baseLine);                 // 4. 绘制标签背景(半透明黑色矩形),防止文字与背景混淆                 // 位置:矩形左上角略微上移,或者直接贴着左上角                Point labelPosition = new Point(item.box.X, item.box.Y - ( int )textSize.Height - 5 );                 // 确保标签不画出图像边界                 if (labelPosition.Y < 0 ) labelPosition.Y = item.box.Y + ( int )textSize.Height + 5 ;                Rect labelBgRect = new Rect(labelPosition.X,                                            labelPosition.Y - ( int )textSize.Height, // OpenCV GetTextSize 返回的高度是基线到底部的距离,需调整                                            ( int )textSize.Width,                                            ( int )textSize.Height + ( int )baseLine);                 // 如果背景框也在图像范围内,则绘制                 // 注意:这里简化处理,直接画在框上方                Cv2.Rectangle(mat,                               new Point(item.box.X, item.box.Y - textSize.Height - 5 ),                               new Point(item.box.X + textSize.Width, item.box.Y),                               new Scalar( 0 , 255 , 0 ),                               thickness: -1 ); // -1 表示填充                 // 5. 绘制文本(白色文字)                Cv2.PutText(mat,                            label,                             new Point(item.box.X, item.box.Y - 5 ),                            HersheyFonts.HersheySimplex,


        // ================= 配置参数 =================         // 模型输入尺寸 (宽=高)         private const int InputSize = 1024 ;    arc.dlddz.com     // 建议根据实际模型动态获取或使用 Netron 查看         private const int OutputSize = 21504 ;   ihd.dlddz.com      // 模型类别数 (根据您的具体数据集修改,此处假设为15类)         private const int CategoryNum = 15 ;   jrslive.gzuof.cn      // 置信度阈值         private const float ConfThreshold = 0.25f ;         // NMS IOU 阈值         private const float NmsThreshold = 0.3f ;         private const int MaxBatchSize = 24 ;         static void Main( string[] args)        {             //  ============= 配置 TensorRT 日志回调 =============             // 定义一个委托,用于处理 TensorRT 内部产生的日志消息。             // 这允许我们将 C++ 层面的日志输出到 C# 的控制台。            LogCallbackFunction _callbackDelegate = (message) =>            {                Console.WriteLine(message); bw.dlddz.com            };             // 将自定义的回调函数注册给 TensorRT 的全局 Logger 实例。            Logger.Instance.SetCallback(_callbackDelegate);             // 设置日志的严重性级别阈值。             // LoggerSeverity.kINFO: 打印信息、警告和错误。             // 开发调试阶段通常设为 kINFO 或 kVERBOSE;生产环境可设为 kWARNING 或 kERROR 以减少输出。            Logger.Instance.SetThreshold(LoggerSeverity.kINFO);             string enginePath = "yolov8s-obb_b.engine" ;             string [] imagePaths = {                 "P0006.png" , "P0016.png" , "P0456.png" , "P0813.png" };  wei147.hntcsc.com           // ================= 1. 加载 TensorRT Engine =================             // 使用 using 语句确保文件流正确关闭             byte [] engineData;             using (FileStream fs = new FileStream(enginePath, FileMode.Open, FileAccess.Read))             using (BinaryReader br = new BinaryReader(fs))            {                engineData = br.ReadBytes(( int )fs.Length);            }             // 反序列化 Engine             // Runtime 必须在 Engine 生命周期内保持存活,通常建议设为全局或静态,或者确保它最后释放            Runtime runtime = new Runtime();            runtime.setMaxThreads( 10 );     hntcsc.com        // 创建 CudaEngine (此处使用 using 确保推理完成后引擎被销毁)             using (CudaEngine cudaEngine = runtime.deserializeCudaEngineByBlob(engineData, ( ulong )engineData.Length))            {                 // ================= 2. 初始化推理上下文与显存 =================                 // 创建执行上下文                 using (JYPPX.TensorRtSharp.Nvinfer.ExecutionContext executionContext = cudaEngine.createExecutionContext(TrtExecutionContextAllocationStrategy.kSTATIC))                 using (CudaStream cudaStream = new CudaStream()) // 创建 CUDA 流用于异步执行  uni.dlddz.com                {                     // 获取输入维度信息 (用于校验)m.cLbaojie.com                    Dims inputDims = executionContext.getTensorShape( "images" );                    Logger.Instance.INFO( $"Input Shape: guo122.hntcsc.com     {inputDims.d[ 0]}x {inputDims.d[ 1]}x {inputDims.d[ 2]}x {inputDims.d[ 3]}" );                     // 计算所需显存大小                     // 输入: Batch=1, Channel=3, Height=1024, Width=1024                     ulong inputSizeInBytes = MaxBatchSize * 3 * InputSize * InputSize;                     // 输出: Batch=1, Channels=CategoryNum+4(box)+1(angle), Num=8400   web.dlddz.com                     int outputChannels = CategoryNum + 5 ; // 4坐标 + 1角度 + N类别                     ulong outputSizeInBytes = ( ulong )(MaxBatchSize * outputChannels * OutputSize);                    Stopwatch sw = new Stopwatch();       8mowvzl.ttlyf.cn              // 分配 GPU 显存                     using (Cuda1DMemory< float > inputGpuMemory

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3109572/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2026-01-07

  • 博文量
    108
  • 访问量
    102104