Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return unsafe pointer to type parameter

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...

like image 558
oliver Avatar asked Mar 18 '18 07:03

oliver


People also ask

What is an unsafe pointer?

The type of data that a pointer can access is the pointer's Pointee type. UnsafePointer provides no automated memory management or alignment guarantees. You are responsible for handling the life cycle of any memory you work with through unsafe pointers to avoid leaks or undefined behavior.

What is unsafe code in C#?

Unsafe code in C# isn't necessarily dangerous; it's just code whose safety cannot be verified. Unsafe code has the following properties: Methods, types, and code blocks can be defined as unsafe. In some cases, unsafe code may increase an application's performance by removing array bounds checks.

Should you use pointers in C#?

The use of pointers is rarely required in C#, but there are some situations that require them. As examples, using an unsafe context to allow pointers is warranted by the following cases: Dealing with existing structures on disk. Advanced COM or Platform Invoke scenarios that involve structures with pointers in them.


Video Answer


1 Answers

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.

like image 73
Marc Gravell Avatar answered Oct 06 '22 00:10

Marc Gravell