笔记(一)

IntPtr
IntPtr(也叫 “整数指针”)是 C# 中定义在 System 命名空间下的值类型,专门用于表示指针的内存地址
-
它的大小会适配当前操作系统的位数(32 位系统占 4 字节,64 位系统占 8 字节),无需手动区分;
-
是安全的 “托管指针类型”,可以在托管代码中安全地操作非托管内存 / 资源(比如调用 C++ 写的 DLL、操作硬件地址等);
-
不能直接像 C++ 指针那样解引用,但可以通过
Marshal类间接操作指向的内存。
用法:
-
调用非托管 API(如 Windows API、C/C++ 编写的 DLL):很多非托管函数的参数 / 返回值是内存地址,需要用
IntPtr传递; -
操作非托管内存:比如通过
Marshal.AllocHGlobal分配非托管内存,返回的就是IntPtr类型的地址; -
跨平台适配:自动适配 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);
}
}
}
关键注意事项
IntPtr是值类型,不是引用类型,赋值时是值拷贝(而非引用拷贝);- 操作非托管内存时,必须手动释放(如
Marshal.FreeHGlobal),否则会导致内存泄漏; IntPtr.ToInt32()在 64 位系统可能抛出溢出异常,优先使用IntPtr.ToInt64()保证兼容性;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()(此时线程直接通过,信号灯再重置为红灯)。
❌ 常见坑点
- 忘记释放资源:
AutoResetEvent实现了IDisposable,使用完必须调用Dispose(),否则会造成资源泄漏。 - 无限制等待:避免直接用
WaitOne()(无参数),建议传入超时时间,防止线程永久阻塞。 - 多线程竞争:如果多个线程等待,
Set()只会唤醒一个,若需唤醒所有线程,应使用ManualResetEvent(手动重置事件)。
5. AutoResetEvent vs ManualResetEvent(对比)
| 特性 | AutoResetEvent | ManualResetEvent |
|---|---|---|
| 重置方式 | 自动重置(Set 后立即变回红灯) | 手动重置(需调用 Reset () 才变红灯) |
| 唤醒线程数 | 每次唤醒 1 个 | 每次唤醒所有等待的线程 |
| 适用场景 | 一对一线程同步(生产 - 消费) | 一对多线程同步(通知所有线程) |
总结
AutoResetEvent是自动重置的线程同步事件,核心是Set()唤醒一个线程后自动回到阻塞状态。- 核心用法:
WaitOne()阻塞等待,Set()唤醒并自动重置,适用于 “生产者 - 消费者” 等一对一线程同步场景。 - 关键注意:使用时必须加超时、用完释放资源,多线程唤醒场景需改用
ManualResetEvent。


浙公网安备 33010602011771号