Return unsafe pointer to type parameter
Asked Answered
B

1

10

I am trying to define a property that returns a pointer to a generic type argument like so:

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

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

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

The compiler complains that it is not possible to declare a pointer to the managed type T or get its address or size (CS0208). The curious thing is, if I manually replace the generic type parameter by a concrete struct, that is

public class MyStructMemWrapper
{
    readonly IntPtr pointerToUnmanagedHeapMem;

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

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

everything compiles fine. But then I would have to create a specialized version of the wrapper for every struct I use. So why does the generic even care about what kind of unsafe pointer it is casting?

Background information: I am using a native dll which in turn calls my c# callback function and passes to it my most general user data structure as a pointer (to be more precise: disguised as an IntPtr). In order to be able to pass a GC-stable pointer at all I am allocating my user data structure on the unmanaged heap. Consequently I have to take care that the memory is set free again in the end.

Since this is of course all at the limits of what a devoted c# programmer can suffer, I am creating a wrapper class (around that heap allocation and usage of the pointers to struct) which separates me as much as possible from the ugly stuff. In order to assign values to the structure on the unmanaged heap as easily as possible I want to define the above property.

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;
}

Of course the unsafe property would only be a minor convenience improvement (over returning the unspecific IntPtr directly and casting it to an unsafe pointer from the outside), and so it is probably not worth it at all cost. But now as the problem is on the table I would like to understand it.

Edit: it seems like the problem is, that I assume the struct to be composed of value types only, which allows me to determine its size and so allocate it on the heap. In the specialized version the composition of the struct is indeed known to the compiler.

However in the generic version the struct could also be composed of reference (i.e. managed) types, although I would never do that due to the aforementioned reasons. Unless I am able to write a generic constraint like "where T: struct is composed of value types" I seem to be out of luck...

Bybee answered 18/3, 2018 at 7:5 Comment(12)
You will not be able to do that. Documentation is pretty clear about that "declaring a pointer to a managed type is not allowed". But you could try making a method with restriction void Foo<T>(T bar) where T : struct Buschi
@Buschi but public unsafe MyStruct* Ptr declaration is compiled okAnthropoid
@Alexey But MyStruct is a value type and it doesn't contain any reference typesBuschi
@Buschi right, now seems clearAnthropoid
@FCin: Ah, I understand, the problem is that the struct T might contain managed constituents (like the string in the linked example). But in my case it does not: I only intend to use value types inside MyStruct (otherwise I would not be able to allocate it on the heap). I suppose there is no way to constrain the struct type parameter of the generic even further, is there?Bybee
I've just tried to create a method with restriction of where T : struct, but it doesn't work.Buschi
@Bybee as I know there is declaration ref struct that restricts creation of instance only in stack but not sure about such restriction in genericAnthropoid
@Alexey: I'afraid the stack context from where I pass the MyStruct may not exist anymore when the dll calls the callback with the previously registered MyStruct instance. So this is not an option, let alone that I have no idea how to do it at all.Bybee
What you would have to do is make sure that T is 1. Value type (where T : struct) 2. T does not have any reference type fields ( There is no way to restrict that).Buschi
@FCin: now I have chosen to grant public readonly access to the IntPtr and cast it to the unsafe pointer from the outside. It looks a lot like c++ now, but shit happens.Bybee
@Alexey you were close - this isn't a ref struct scenario, but it is a ref return one :)Ullman
@Buschi you can't do exactly that (first comment) - but you can now do much better things instead - see "ref return" (or my answer)Ullman
U
16

Generics and pointers don't work well together, but this is actually a perfect fit for "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()); }
    }
}

Alternative Ptr syntax:

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

Note that this requires recent versions of Unsafe; here I'm using:

<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0" />

Note that you now don't need unsafe in the consuming code - just the one property that touches pointerToUnmanagedHeapMem.

Consuming code:

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

no unmanaged pointers; the code is now perfectly "safe" outside of .Ptr.

Note: if you need to talk about multiple consecutive items: Span<T>/Memory<T> are your friends.

Ullman answered 18/3, 2018 at 10:9 Comment(3)
Wow, this looks promising! But I can't get it to work. I get CS1031 at the ref keyword in the declaration. I also don't know how to do that package stuff - using Framework 4.5.2 is not enough? Also, I am using SharpDevelop... is this Visual Studio specific?Bybee
@Bybee it requires C# 7, so: does SharpDevelop has up-to-date compiler support? ref return isn't specific to Visual Studio, nor is it specific to any .NET version - but it does require C# 7.Ullman
@Bybee reading #799 and #783, it seems very unlikely that you'll get C# 7 in SharpDevelop; you may wish to consider VSCode (lightweight and free) or VS Community Edition (fuller IDE, and free)Ullman

© 2022 - 2024 — McMap. All rights reserved.