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.
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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With