笔记(一)

image-20260110134150966

IntPtr

IntPtr(也叫 “整数指针”)是 C# 中定义在 System 命名空间下的值类型,专门用于表示指针的内存地址

  • 它的大小会适配当前操作系统的位数(32 位系统占 4 字节,64 位系统占 8 字节),无需手动区分;

  • 是安全的 “托管指针类型”,可以在托管代码中安全地操作非托管内存 / 资源(比如调用 C++ 写的 DLL、操作硬件地址等);

  • 不能直接像 C++ 指针那样解引用,但可以通过 Marshal 类间接操作指向的内存。

用法:

  1. 调用非托管 API(如 Windows API、C/C++ 编写的 DLL):很多非托管函数的参数 / 返回值是内存地址,需要用 IntPtr 传递;

  2. 操作非托管内存:比如通过 Marshal.AllocHGlobal 分配非托管内存,返回的就是 IntPtr 类型的地址;

  3. 跨平台适配:自动适配 32/64 位系统,避免手动定义 int(32 位)或 long(64 位)来存储地址。

        IntPtr ptr1 = IntPtr.Zero; // 空指针(地址0)
        IntPtr ptr2 = new IntPtr(123456); // 用数值初始化地址
        IntPtr ptr3 = IntPtr.Add(ptr2, 4); // 地址偏移4字节(相当于 ptr2 + 4)

        // 2. 转换(IntPtr ↔ 数值)
        long addressValue = ptr2.ToInt64(); // 转成long(兼容64位)
        int addressValue32 = ptr2.ToInt32(); // 转成int(仅32位系统安全)
        IntPtr ptr4 = new IntPtr(addressValue); // 数值转回IntPtr

        // 3. 判断是否为空指针
        if (ptr1 == IntPtr.Zero)
        {
            Console.WriteLine("ptr1 是空指针");
        }

实例:调用非托管 API(以 Windows API 为例)

using System;
using System.Runtime.InteropServices;

class Win32ApiDemo
{
    // 导入Windows API(非托管函数)
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    static void Main()
    {
        // 获取当前控制台窗口的句柄(示例)
        IntPtr consoleHandle = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
        
        // 调用API,传递IntPtr类型的句柄
        int textLength = GetWindowTextLength(consoleHandle);
        
        Console.WriteLine($"控制台窗口文本长度: {textLength}");
    }
}

 

实例:操作非托管内存

using System;
using System.Runtime.InteropServices;

class UnmanagedMemoryDemo
{
    static void Main()
    {
        // 1. 分配100字节的非托管内存,返回内存地址(IntPtr)
        IntPtr unmanagedPtr = Marshal.AllocHGlobal(100);
        
        try
        {
            // 2. 向非托管内存写入数据
            string text = "Hello IntPtr";
            Marshal.StringToHGlobalUni(text, unmanagedPtr);
            
            // 3. 从非托管内存读取数据
            string readText = Marshal.PtrToStringUni(unmanagedPtr);
            Console.WriteLine("从非托管内存读取的内容: " + readText);
        }
        finally
        {
            // 4. 释放非托管内存(必须手动释放,否则内存泄漏)
            Marshal.FreeHGlobal(unmanagedPtr);
        }
    }
}

关键注意事项

  1. IntPtr值类型,不是引用类型,赋值时是值拷贝(而非引用拷贝);
  2. 操作非托管内存时,必须手动释放(如 Marshal.FreeHGlobal),否则会导致内存泄漏;
  3. IntPtr.ToInt32() 在 64 位系统可能抛出溢出异常,优先使用 IntPtr.ToInt64() 保证兼容性;
  4. IntPtr.Zero 代表空指针,相当于 C++ 中的 NULL/nullptr

什么是 “反射(Reflection)”?

反射是.NET 提供的一套机制,允许程序在运行时

  • 查看类型的元数据(字段、方法、属性、布局等);
  • 动态创建类型实例、调用方法、读写字段 / 属性;
  • 突破编译时的类型限制,操作编译时未知的类型。

Type t = typeof(MyCamera.MV_CC_DEVICE_INFO)是(反射的 “入口”)编译时确定类型,获取 Type 对象(元数据),无运行时开销

t.GetFields()(获取结构体所有字段)是(完整反射操作)运行时动态查询类型信息,有少量运行时开销

Marshal.PtrToStructure(ptr, t)依赖反射(但本身是互操作 API)利用 Type 对象的元数据解析非托管内存,本质是 “用反射信息做互操作

Activator.CreateInstance(t)(动态创建实例)是(典型反射操作)运行时动态创建对象,无需编译时知道类型

AutoResetEvent

1. 核心概念:AutoResetEvent 是什么?

AutoResetEvent 属于 System.Threading 命名空间,是一个自动重置的同步事件,可以把它想象成:

  • 一个 “信号灯”,初始状态可以是终止(Signaled,绿灯)非终止(Non-Signaled,红灯)

  • 线程调用

    WaitOne()
    

    时,会检查信号灯:

    • 绿灯(Signaled):线程直接通过,且信号灯自动变回红灯(这就是 “Auto” 自动重置的核心)。
    • 红灯(Non-Signaled):线程会阻塞,直到其他线程调用 Set() 将信号灯设为绿灯。

2. 核心方法与构造函数

构造函数

// initialState: 初始状态(true=绿灯/Signaled,false=红灯/Non-Signaled)
AutoResetEvent are = new AutoResetEvent(bool initialState);

核心方法

方法 作用
Set() 将信号灯设为绿灯(Signaled),唤醒一个等待的线程,随后自动重置为红灯
WaitOne() 阻塞当前线程,直到信号灯变为绿灯(可传入超时时间,避免永久阻塞)
WaitOne(int millisecondsTimeout) 阻塞指定毫秒数,超时后返回 false
Reset() 手动将信号灯设为红灯(Non-Signaled)

3. 基础使用示例(经典 “生产者 - 消费者”)

这个示例展示:主线程启动消费者线程(阻塞等待),生产者线程生产数据后通过 Set() 唤醒消费者,且每次唤醒后信号灯自动重置。

using System;
using System.Threading;

class AutoResetEventDemo
{
    // 初始化为红灯(非终止状态),消费者线程启动后会先阻塞
    private static AutoResetEvent _dataReadyEvent = new AutoResetEvent(false);
    private static string _data = null;

    static void Main()
    {
        // 启动消费者线程(先阻塞,等待数据)
        new Thread(Consumer).Start();

        // 主线程作为生产者,模拟生产数据
        Console.WriteLine("生产者:开始生产数据...");
        Thread.Sleep(2000); // 模拟耗时操作
        _data = "这是生产的第1批数据";
        Console.WriteLine("生产者:数据已生产完成,唤醒消费者");
        _dataReadyEvent.Set(); // 设为绿灯,唤醒1个消费者,随后自动重置为红灯

        // 继续生产第二批数据
        Thread.Sleep(2000);
        _data = "这是生产的第2批数据";
        Console.WriteLine("生产者:第二批数据已生产,再次唤醒消费者");
        _dataReadyEvent.Set();

        // 等待消费者处理完所有数据
        Thread.Sleep(3000);
        _dataReadyEvent.Dispose(); // 释放资源(重要)
    }

    static void Consumer()
    {
        Console.WriteLine("消费者:等待数据中...");
        
        // 第一次等待(阻塞,直到Set()调用)
        if (_dataReadyEvent.WaitOne(5000)) // 超时5秒
        {
            Console.WriteLine($"消费者:收到数据 -> {_data}");
        }

        // 信号灯已自动重置为红灯,第二次等待
        Console.WriteLine("消费者:继续等待下一批数据...");
        if (_dataReadyEvent.WaitOne(5000))
        {
            Console.WriteLine($"消费者:收到数据 -> {_data}");
        }

        Console.WriteLine("消费者:所有数据处理完成");
    }
}

输出结果

消费者:等待数据中...
生产者:开始生产数据...
生产者:数据已生产完成,唤醒消费者
消费者:收到数据 -> 这是生产的第1批数据
消费者:继续等待下一批数据...
生产者:第二批数据已生产,再次唤醒消费者
消费者:收到数据 -> 这是生产的第2批数据
消费者:所有数据处理完成

4. 关键特性与注意事项

✅ 自动重置的核心特点

  • 每次 Set() 只能唤醒一个等待的线程(如果有多个线程等待,会按顺序唤醒)。
  • 唤醒后信号灯立即自动变回红灯,无需手动调用 Reset()
  • 如果调用 Set() 时没有线程等待,信号灯会保持绿灯,直到有线程调用 WaitOne()(此时线程直接通过,信号灯再重置为红灯)。

❌ 常见坑点

  1. 忘记释放资源AutoResetEvent 实现了 IDisposable,使用完必须调用 Dispose(),否则会造成资源泄漏。
  2. 无限制等待:避免直接用 WaitOne()(无参数),建议传入超时时间,防止线程永久阻塞。
  3. 多线程竞争:如果多个线程等待,Set() 只会唤醒一个,若需唤醒所有线程,应使用 ManualResetEvent(手动重置事件)。

5. AutoResetEvent vs ManualResetEvent(对比)

特性 AutoResetEvent ManualResetEvent
重置方式 自动重置(Set 后立即变回红灯) 手动重置(需调用 Reset () 才变红灯)
唤醒线程数 每次唤醒 1 个 每次唤醒所有等待的线程
适用场景 一对一线程同步(生产 - 消费) 一对多线程同步(通知所有线程)

总结

  1. AutoResetEvent自动重置的线程同步事件,核心是 Set() 唤醒一个线程后自动回到阻塞状态。
  2. 核心用法:WaitOne() 阻塞等待,Set() 唤醒并自动重置,适用于 “生产者 - 消费者” 等一对一线程同步场景。
  3. 关键注意:使用时必须加超时、用完释放资源,多线程唤醒场景需改用 ManualResetEvent
posted @ 2026-01-31 15:31  huihui不会写代码  阅读(1)  评论(0)    收藏  举报