10

I'm stuck on a seemingly trivial task and need your help.

I need to write a method with the following signature:

System.Array ToIntPtrArray(System.Array a)

where an actual argument can be an array of any pointer type (e.g. int*[], long**[], void*[,]) and returning an array of the same shape with elements of type System.IntPtr having the same numeric values as elements of an input array. The problem is that I do not understand how to extract numeric values of pointers if I do not know their types beforehand.


For example, if I knew beforehand that my argument is always of type void*[], I could write the method as follows:

unsafe IntPtr[] ToIntPtrArray(void*[] a)
{
    var result = new IntPtr[a.Length];
    for (int i = 0; i < a.Length; i++)
        result[i] = (IntPtr) a[i];

    return result;
}

But the problem is it could be not void*[], but void**[] or anything else, and the method should be able to handle all cases.

13
  • 1
    What is the numerical value of void*? Commented Jul 23, 2013 at 17:30
  • 4
    @CédricBignon I do not quite understand your question. A variable of type void* could have any numeric value within the platform-dependent range of addresses, and in any case it would fit into IntPtr on that platform. I added an example showing how it should work in this case. Commented Jul 23, 2013 at 17:50
  • 2
    @TheJoker Yes, I would use an explicit conversion if I knew an exact type of a pointer at compile-time. But as i do not know it, what conversion do you suggest to use? And what kind of undefined behavior do you expect? Commented Jul 23, 2013 at 17:54
  • 3
    @nicholas The method should be able to handle all types of pointers that could possibly exist in the .NET runtime. I do not know what possible callers may choose to pass as an argument. I could use a set of overloads provided that they would cover all possible inputs. Commented Jul 23, 2013 at 17:57
  • 1
    Something to watch out for (from the MSDN docs(: "Using an Array object of pointers in native code is not supported and will throw a NotSupportedException for several methods." GetValue is one such method. Commented Jul 23, 2013 at 19:39

3 Answers 3

3

The short answer is, this cannot be done directly. The reasons are that if you pass your conversion function any of the conventional index-capable containers (System.Array, Collections.IList, ArrayList, etc.) performing the index operations will attempt to cast the result to System.Object. Pointers in C# do not derive from Object, so this will result in an SystemNotSupported or similar exception.

There are two reasonable workarounds:

  1. Convert the pointer arrays to void pointer arrays before calling the method.
  2. Convert the pointer arrays to void pointer pointers before calling the method.

The first one is rather cumbersome, as it requires duplicating the entire contents of the array with a for loop. The second option requires passing in the length of the array as it is no longer wrapped with a managed System.Array object.

Sample Code

Method:

    unsafe Array ToIntPtrArray(void** a, int count)
    {
        IntPtr[] intPtrArray = new IntPtr[count];

        for (int n = 0; n < count; n++)
            intPtrArray[n] = new IntPtr(a[n]);

        return intPtrArray;
    }

Sample Usage (integer pointer array):

int*[] intPtrArray;

// Code that initializes the values of intPtrArray

fixed(int** ptr = &intPtrArray[0])
{
   Array result = ToIntPtrArray((void**)ptr, intPtrArray.Length);
}

Sample Usage (void pointer pointer array):

void**[] voidPtrPtrArray;

// Code that initializes the values of voidPtrPtrArray

fixed(void*** ptr = &voidPtrPtrArray[0])
{
    Array result = ToIntPtrArray((void**)ptr, voidPtrPtrArray.Length);
}

Sample Usage (multidimensional int pointer array):

int*[,] int2dArray;

// Code that initializes the values of int2dArray

fixed(int** ptr = &int2dArray[0,0])
{
    Array result = ToIntPtrArray((void**)ptr, TotalSize(int2dArray));
    Array reshaped = ReshapeArray(result,int2dArray);
}

Where TotalSize and ReshapeArray are helper functions that are written to deal with multi-dimensional arrays. For tips on how to accomplish this see: Programatically Declare Array of Arbitrary Rank.

Sign up to request clarification or add additional context in comments.

6 Comments

Nice, but it does not handle the OP's case of void*[,] with the same shape correctly. It flattens the array into one dimension but that's unavoidable without the use of reflection, I believe.
@Xcelled194, I would challenge that even without pointer constraints of the problem, creating an arbitrary array with the same rank and dimensions as a an original array is a challenging problem. For jagged arrays this can be accomplished with recursion, for rectangular multidimensional arrays, I am not sure it can be done at all.
It would have to be done with reflection and SetValue(), I'm almost certain. But my gut tells me it could be done.
Yes, I just wrote the code to create a rectangular multidim array. Setting values would need to use SetValue method, but can be done. ` var a = new byte[10, 3]; int[] lens = new int[a.Rank], lbs = new int[a.Rank]; for (int i = 0; i < a.Rank; i++) { lens[i] = a.GetUpperBound(i)+1; lbs[i] = a.GetLowerBound(i); } var b = (byte[,])Array.CreateInstance(typeof(byte), lens, lbs);`
Multi-dimensional arrays are stored linearly, so the main method IntToPtrArray will still work. To keep things simple I left the ReshapeArray function as an external helper; however, it could very well be implemented inside the IntToPtrArray function as well. Jagged arrays would be more difficult.
|
2

This is a rather difficult problem. Creating an array of the proper shape isn't too bad.

unsafe System.Array ToIntPtrArray(System.Array a)
{
    int[] lengths = new int[a.Rank];
    int[] lowerBounds = new int[a.Rank];
    for (int i = 0; i < a.Rank; ++i)
    {
        lengths[i] = a.GetLength(i);
        lowerBounds[i] = a.GetLowerBound(i);
    }
    Array newArray = Array.CreateInstance(typeof (IntPtr), lengths, lowerBounds);

    // The hard part is iterating over the array.
    // Multiplying the lengths will give you the total number of items.
    // Then we go from 0 to n-1, and create the indexes
    // This loop could be combined with the loop above.
    int numItems = 1;
    for (int i = 0; i < a.Rank; ++i)
    {
        numItems *= lengths[i];
    }

    int[] indexes = new int[a.Rank];
    for (int i = 0; i < numItems; ++i)
    {
        int work = i;
        int inc = 1;
        for (int r = a.Rank-1; r >= 0; --r)
        {
            int ix = work%lengths[r];
            indexes[r] = lowerBounds[r] + ix;
            work -= (ix*inc);
            inc *= lengths[r];
        }

        object obj = a.GetValue(indexes);
        // somehow create an IntPtr from a boxed pointer
        var myPtr = new IntPtr((long) obj);
        newArray.SetValue(myPtr, indexes);
    }
    return newArray;
}

That creates an array of the right type and shape (dimensions and length), but it has a problem. The GetValue method, which you use to get an item from the array, returns an object. And you can't cast a pointer type to an object. No way, no how. So you can't get the value from the array! If you call GetValue on an array of long*, for example, you'll get "type not supported."

I think you need some way to copy that oddly-shaped array to a one-dimensional array of int* (or any other pointer type). Then you could directly index the temporary array and get the values to populate your IntPtr array.

It's an interesting chicken-and-egg problem. If you pass it as a System.Array, then you can't get items from it because there's no conversion path from object to int* (or any other pointer type). But if you pass it as a pointer type (i.e. int**), then you can't get the shape of the thing.

I suppose you could write it as:

unsafe System.Array ToIntPtrArray(System.Array a, void** aAsPtr)

You then have the System.Array metadata and the actual data in a form that you can use.

2 Comments

Your next to last suggestion is the one that would really work. The final suggestion of just passing a pointer to the array would not since you are not allowed to take the address of a System.Array object (unless you want to do Marshalling). Pinning the array as a void** with the fixed keyword and also passing the original array for metadata is only way to kill all the OP's requests with one stone.
@nicholas: Thanks. You're right. I did a little more exploration. See my update.
0

Even though the question has been answered well, I feel the need to leave a note/warning to future readers.

The CLR is designed to keep you safe, or at least as safe as possible. It accomplishes this with (among other things) type safety and abstracting memory operations. While you can turn some of these protections off with the unsafe block, some protections are hardcoded into the compiler/runtime. In order to circumvent this additional hurdle, one must resort to some hackey, and possibly slow, code. While these methods work, experience has taught me that doing such things leads to problems down the road, whether it be a change in the runtime, a future programmer needing to modify that segment of code, or you yourself needing to do something else with those pointers later on in the code.

At this point, I would seriously consider a helper dll written in Managed C++ to handle the "raw pointer" side of things. My reasoning is that by using Unsafe, you're already throwing out many protections the CLR offers. You may find it easier to work unencumbered by any additional, baked in protections. Not to mention you can use pointers to their full extent, and then cast them back to intptr when you're done. If nothing else, you may want to look at implementing ToIntPtrArray in C++. Still pass it pointers on the C# side, but escape the CLR's oversight.

Note that I'm not saying that every time you use "unsafe" you should bust out the C++. Quite contrary - unsafe will allow you to do quite a bit - but in this instance, a C++ helper is probably something to consider.

Disclaimer: I have not done a whole lot with managed C++. It could be that I am totally wrong and the CLR would still monitor the pointers. If some more experienced soul could comment and tell me either way, It'd be much appreciated.

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.