使用Microsoft.Win32.SafeHandles与Marshal.AllocHGlobal和Marshal.FreeHGlobal的区别

4
我有一个.Net类,使用Marshal.AllocHGlobal分配非托管内存给struct,然后使用Marshal.FreeHGlobal释放它。
我知道类Microsoft.Win32.SafeHandles提供了处理这个问题的包装器,但是不清楚如何实例化它们(许多没有构造函数)-应该编写抽象基类的特定实现,还是有一种在extern声明中使用它们的方法?

1
子类化 SafeHandle 的主要问题是,为了使用 Marshal.DestroyStructure,你需要 structType... 这使得一切变得更加复杂。你不能使用泛型(因为它们与 pinvoke 不兼容)... 因此你可以有多个 SafeHandle 子类(每个 Type 一个),或者在 SafeHandle 中有一个属性,手动设置 struct 的类型... 或者你可以使 SafeHandle 的构造函数接受要进行封送的 struct 并将 Type 设置在一个属性中。 - xanatos
我认为SafeHandle是HANDLE的包装器(内核对象的用户模式标识符),而AllocHGlobal和FreeHGlobal是用于操作非托管内存缓冲区的方法。你真的想混淆这些概念吗? - SerG
@SerG 从托管代码?没问题 - 我想在内存中分配一些东西,获取该对象的句柄并对其进行操作,然后释放并确保内存已被释放。通常这意味着需要许多try-finally块,但这并不总是切实可行的。 - Keith
1个回答

7
子类化 SafeHandle 的主要问题在于,为了调用 Marshal.DestroyStructure,你需要结构的Type...这使得一切变得更加复杂。
你无法使用泛型(因为它们与 P/Invoke 不兼容)...所以你可以有多个 SafeHandle 子类(每个都有一个不同的 Type),或者在 SafeHandle 中有一个属性,手动设置结构的类型...或者你可以使 SafeHandle 的构造函数接受要进行封送的结构,并将Type 设置在该属性中。
我正在使用后两种 “选项” (可以手动设置Type 属性或通过构造函数自动设置) 的示例:
public class MySafeHandle : SafeHandle
{
    public Type Type { get; set; }

    public MySafeHandle() : base(IntPtr.Zero, true)
    {
    }

    public MySafeHandle(object obj)
        : base(IntPtr.Zero, true)
    {
        if (obj != null)
        {
            Type = obj.GetType();
            int size = Marshal.SizeOf(obj);

            try
            {
            }
            finally
            {
                // the finally part can't be interrupted by
                // Thread.Abort
                handle = Marshal.AllocHGlobal(size);
                Marshal.StructureToPtr(obj, handle, false);
            }
        }
    }

    public override bool IsInvalid
    {
        get { return handle == IntPtr.Zero; }
    }

    [SecurityCritical]
    protected override bool ReleaseHandle()
    {
        if (handle != IntPtr.Zero)
        {
            if (Type == null)
            {
                throw new InvalidOperationException();
            }

            try
            {
            }
            finally
            {
                Marshal.DestroyStructure(handle, Type);
                Marshal.FreeHGlobal(handle);
                handle = IntPtr.Zero;
            }

            return true;
        }

        return false;
    }
}

你应该使用的构造函数是使用Marshal.StructureToPtr将结构体转换为指针。 它具有一个优点,即它保存了结构体的类型,以便稍后可以使用它来Marshal.DestroyStructure

谢谢,非常详细的回答,解决了我想知道的问题 :-) - Keith
请注意,我已将 Marshal.StructureToPtr(obj, handle, false); 移至 finally {} 块中... 这不会显示在帖子历史记录中,因为我在前5分钟内完成了它。 - xanatos
谢谢。我已经将其实现为一个带有类型约束的通用类 - class SafeStructHandle<T> : SafeHandle where T : struct - Keith
是的,但是泛型没有传递给任何Marshal或pInvoke方法 - 它只是用于句柄包装类。 object.GetType被替换为typeof(T),具有在编译时将其限制为struct对象的优点。为什么这仍然是个问题?我是否忽略了关于如何调用它的某些内容? - Keith
@Keith 不需要直接与PInvoke一起使用,那就没有问题。即使您想将其作为参数传递给PInvoke方法,如果您想将其作为“in”参数传递(因此是在C#端创建的内容),您可能可以将其作为SafeHandle someParameter进行传递(未经测试)。 - xanatos
2
我已经测试过了 - 我可以在 extern 调用的参数中使用 SafeHandle(而不是分配时的 IntPtr),但传入通用的 SafeStructHandle<T> 以进行隐式转换。 - Keith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接