返回类型参数的不安全指针

10
我正在尝试定义一个属性,该属性返回指向泛型类型参数的指针,如下所示:
public class MemWrapper<T> where T: struct
{
    readonly IntPtr pointerToUnmanagedHeapMem;

    // ... do some memory management also ...

    public unsafe T* Ptr
    {
        get {return (T*)(pointerToUnmanagedHeapMem);}
    }
}

编译器报错,指针不能声明为托管类型 T 或获取其地址或大小 (CS0208)。奇怪的是,如果我手动用具体的结构替换泛型类型参数,则不会出现以上问题。
public class MyStructMemWrapper
{
    readonly IntPtr pointerToUnmanagedHeapMem;

    // ... do some memory management also ...

    public unsafe MyStruct* Ptr
    {
        get {return (MyStruct*)(pointerToUnmanagedHeapMem);}
    }
}

一切都编译得很好。但是,我将不得不为我使用的每个结构创建一个专门的包装器版本。那么,通用型为什么还要关心它转换的不安全指针类型呢?
背景信息:我正在使用本地dll,该dll调用我的c#回调函数并传递给它我的最通用用户数据结构作为指针(更精确地说:伪装成IntPtr)。为了能够传递GC稳定的指针,我在非托管堆上分配了我的用户数据结构。因此,最后我必须确保内存被释放。
由于这当然超出了忠实的c#程序员所能承受的范围,所以我正在创建一个包装器类(围绕堆分配和结构指针的使用),尽可能地与丑陋的东西隔离开来。为了尽可能轻松地为非托管堆上的结构分配值,我想定义上述属性。
public struct MyStruct {public double x;}

// ...

MemWrapper<MyStruct> m = new MemWrapper<MyStruct>();

unsafe
{
    // ideally I would like to get rid of the whole 
    // bloody unsafe block and directly write m.x = 1.0
    m.Ptr->x = 1.0;
}

当然,使用unsafe属性只是一个轻微的便利改进(比直接返回不明确的IntPtr并从外部将其转换为不安全指针),因此可能不值得一试所有成本。但既然问题已经摆在桌面上,我想了解一下。
编辑:看起来问题是我假设结构仅由值类型组成,这使我能够确定其大小并在堆上分配它。在专门的版本中,结构的组成确实为编译器所知。
但是,在泛型版本中,结构也可以由引用类型(即托管类型)组成,尽管由于上述原因,我永远不会这样做。除非我能编写一个泛型约束,例如“where T:struct由值类型组成”,否则我似乎很倒霉...

2
你将无法这样做。文档非常明确,"不允许声明指向托管类型的指针"。但是你可以尝试使用限制条件void Foo<T>(T bar) where T : struct来创建一个方法。 - FCin
@FCin 但是 public unsafe MyStruct* Ptr 的声明已经被成功编译。 - Alexey Klipilin
@FCin,现在看起来很清楚。 - Alexey Klipilin
@Alexey: 我担心当dll使用先前注册的MyStruct实例回调时,我传递MyStruct的堆栈上下文可能不再存在。所以这不是一个选择,更不用说我完全不知道如何做了。 - oliver
2
你需要做的是确保 T 是 1. 值类型 (其中 T : struct) 2. T 没有任何引用类型字段 (无法限制)。 - FCin
显示剩余7条评论
1个回答

16

泛型和指针并不搭配,但这正是“ref return”的完美应用:

public class MemWrapper<T> where T : struct
{
    readonly IntPtr pointerToUnmanagedHeapMem;

    // ... do some memory management also ...

    public unsafe ref T Ptr
    {
        get { return ref Unsafe.AsRef<T>(pointerToUnmanagedHeapMem.ToPointer()); }
    }
}

指针的另一种语法:Ptr

public unsafe ref T Ptr => ref Unsafe.AsRef<T>(pointerToUnmanagedHeapMem.ToPointer());

请注意,这需要较新版本的Unsafe支持;此处我使用的是:
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0" />

请注意,现在在消费代码中不需要使用unsafe关键字 - 只需要涉及pointerToUnmanagedHeapMem的一个属性即可。
消费代码:
var wrapper = ... // some MemWrapper<T>
ref Foo foo = ref wrapper.Ptr;
Console.WriteLine(foo.SomeProperty); // not foo->SomeProperty
SomeInnerMethod(ref foo); // pass down to other ref Foo methods

没有非托管指针;现在,代码在.Ptr以外是完全“安全”的。

注意:如果你需要讨论多个连续的项目:Span<T>/Memory<T>是很好的选择。


哇,这看起来很有前途!但我无法让它工作。在声明中ref关键字处我得到了 CS1031 错误。而且我也不知道如何处理那些包 - 使用 Framework 4.5.2 还不够吗?另外,我正在使用 SharpDevelop...这与 Visual Studio 有关吗? - oliver
1
@oliver,这需要C# 7,那么:SharpDevelop是否具有最新的编译器支持?ref return不仅适用于Visual Studio,也不适用于任何.NET版本 - 但它确实需要C# 7。 - Marc Gravell
2
@oliver 阅读 #799#783,看起来 SharpDevelop 很难支持 C# 7;你可以考虑使用 VSCode(轻量级且免费)或者 VS Community Edition(更完整的 IDE,也是免费的)。 - Marc Gravell

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