7

I'm implementing an unmanaged array class in C# which I need for some OpenGL calls.

It's going great, but I've hit a roadblock. The following code doesn't compile, and I understand why, but how can I make it work?

    public T this[int i]
    {
        get { return *((T*)arrayPtr + i); }
        set { *((T*)arrayPtr + i) = value; }
    }

I thought that it might work if I ensured that T is a struct

unsafe class FixedArray<T> where T : struct

Doesn't work either...

How can I get something functionally equivilant to what I'm trying to do above?

EDIT: I'm using an unmanaged array with Marshal.AllocHGlobal() so that my array is fixed and the GC won't move it around. OpenGL doesn't actually process the instructions when you call it, OpenGL will try to access the array long after the function has returned.

Here's the whole class if that helps:

unsafe class FixedArray<T> where T : struct
{
    IntPtr arrayPtr;

    public T this[int i]
    {
        get { return *((T*)arrayPtr + i); }
        set { *((T*)arrayPtr + i) = value; }
    }
    public FixedArray(int length)
    {
        arrayPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(T)) * length);
    }
    ~FixedArray()
    {
        Marshal.FreeHGlobal(arrayPtr);
    }
}

The error message is Cannot take the address of, get the size of, or declare a pointer to a managed type ('T')

5
  • 1
    Please include the error message. Commented Mar 22, 2011 at 8:03
  • 2
    Are you sure you need an unmanaged array? Commented Mar 22, 2011 at 8:06
  • Yes, I'm sure. I first tried it without and got AccessViolationExceptions. Commented Mar 22, 2011 at 8:12
  • @Richard Included error message Commented Mar 22, 2011 at 8:16
  • 2
    Side note - you're missing the initial assignment to arrayPtr from your call to AllocHGlobal Commented Mar 22, 2011 at 8:19

3 Answers 3

7

I'm pretty sure that getting your code to compile isn't possible. For your code (T*)arrayPtr + i, this would compute arrayPtr + i * 4 when T is an int and arrayPtr + i * 8 when T is a long. Since the CLR has no way of substituting a 4, 8, or whatever sizeof(T) happens to be, this code just can't work.

What you need to do is use Marshal to do all the work for you. This compiles, but I haven't tested it:

unsafe class FixedArray<T> where T : struct
{
    IntPtr arrayPtr;

    int sizeofT { get { return Marshal.SizeOf(typeof(T)); } }

    public T this[int i]
    {
        get
        {
            return (T)Marshal.PtrToStructure(arrayPtr + i * sizeofT, typeof(T));
        }
        set
        {
            Marshal.StructureToPtr(value, arrayPtr + i * sizeofT, false);
        }
    }
    public FixedArray(int length)
    {
        arrayPtr = Marshal.AllocHGlobal(sizeofT * length);
    }
    ~FixedArray()
    {
        Marshal.FreeHGlobal(arrayPtr);
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This solution will copy the structures into and out of the memory block each time this[] is used, so reasonable statements like fixedArray[3].Data = 3; will not work as expected. If you want to do it this way, you should also implement Dispose and use GC.AddMemoryPressure.
That looks pretty sound, i'll try it out when I get home. I have experience with C and with C#. But unmanaged C# is new to me.
5

You don't need an unmanaged array.

You can declare a managed array normally:

var myArray = new MyStruct[13];

and then use the GCHandle class to pin it in memory and get its address:

var handle = GCHandle.Alloc(myArray, GCHandleType.Pinned);
IntPtr address = handle.AddrOfPinnedObject();

The array will exist at that memory address until you call handle.Free. If you never call Free, then it will stay there until your program exits.

2 Comments

How does the performance of this compare to Gabe's example. Is it okay to pin lots of large arrays in the garbage collector's managed memory?
Performance is a nuanced property. Pinning arrays will reduce the efficiency of the GC; whether it will have "too much" of an impact can only be determined with testing. Gabe's answer will not impact the GC as much (as long as you use Dispose and AddMemoryPressure as I recommended), but adds overhead when modifying the arrays.
0

It is now possible in c# 7.3. You need to specify the unmanaged generic constraint:

unsafe class FixedArray<T> where T : unmanaged
{
    T* arrayPtr;

    public T this[int i]
    {
        get { return *(arrayPtr + i); }
        set { *(arrayPtr + i) = value; }
    }
    public FixedArray(int length)
    {
        arrayPtr = (T*)Marshal.AllocHGlobal(sizeof(T) * length);
    }
    ~FixedArray()
    {
        Marshal.FreeHGlobal((IntPtr)arrayPtr);
    }
}

Unfortunately there are still limitations. For instance, the type T cannot contain generic types, so it is not possible [at the moment] for FixedArray to contain other FixedArrays.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.