Преобразование массива указателей в массив IntPtr
Я застрял в кажущейся тривиальной задаче и нуждаюсь в вашей помощи.
Мне нужно написать метод со следующей сигнатурой:
System.Array ToIntPtrArray(System.Array a)
где фактический аргумент может быть массивом любого типа указателя (например, int*[]
, long**[]
, void*[,]
) и возврата массив такой же формы с элементами типа System.IntPtr
с теми же численными значениями, что и элементы входного массива. Проблема в том, что я не понимаю, как извлечь числовые значения указателей, если я не знаю их типов заранее.
Например, если я заранее знал, что мой аргумент всегда имеет тип void*[]
, я мог бы написать метод следующим образом:
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;
}
Но проблема в том, что это может быть не void*[]
, а void**[]
или что-то еще, и метод должен иметь возможность обрабатывать все случаи.
Ответы
Ответ 1
Короткий ответ: это невозможно сделать напрямую. Причиной является то, что если вы передадите свою функцию преобразования, любой из обычных контейнеров, поддерживающих индекс (System.Array
, Collections.IList
, ArrayList
и т.д.), Выполняющие операции индекса будут пытаться передать результат в System.Object
. Указатели в С# не выводятся из Object
, поэтому это приведет к SystemNotSupported
или аналогичному исключению.
Существует два разумных решения:
- Преобразуйте массивы указателей, чтобы освободить указательные массивы до вызова
Метод.
- Преобразуйте указательные массивы в указатели указателей указателей перед
вызов метода.
Первый довольно громоздкий, поскольку он требует дублирования всего содержимого массива с циклом for. Второй вариант требует передачи в длину массива, поскольку он больше не обернут управляемым объектом System.Array
.
Пример кода
Метод:
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;
}
Пример использования (целочисленный указательный массив):
int*[] intPtrArray;
// Code that initializes the values of intPtrArray
fixed(int** ptr = &intPtrArray[0])
{
Array result = ToIntPtrArray((void**)ptr, intPtrArray.Length);
}
Пример использования (указательный указатель указателя void):
void**[] voidPtrPtrArray;
// Code that initializes the values of voidPtrPtrArray
fixed(void*** ptr = &voidPtrPtrArray[0])
{
Array result = ToIntPtrArray((void**)ptr, voidPtrPtrArray.Length);
}
Пример использования (многомерный массив указателей int):
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);
}
Где TotalSize
и ReshapeArray
- вспомогательные функции, которые записываются для работы с многомерными массивами. Для получения советов о том, как это сделать, см. Программно объявить массив произвольного ранга.
Ответ 2
Это довольно сложная проблема. Создание массива правильной формы не так уж плохо.
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;
}
Это создает массив правильного типа и формы (размеры и длину), но у него есть проблема. Метод GetValue
, который вы используете для получения элемента из массива, возвращает object
. И вы не можете использовать тип указателя для object
. Ни в коем случае, ни как. Таким образом, вы не можете получить значение из массива! Если вы вызываете GetValue
в массиве long*
, например, вы получите "тип не поддерживается".
Мне кажется, вам нужно каким-то образом скопировать этот нечетно-образный массив в одномерный массив int*
(или любой другой тип указателя). Затем вы можете напрямую проиндексировать временный массив и получить значения, чтобы заполнить ваш массив IntPtr
.
Это интересная проблема с курицей и яйцом. Если вы передадите его как System.Array
, вы не сможете получить элементы из него, потому что нет пути преобразования от object
до int*
(или любого другого типа указателя). Но если вы передадите его как тип указателя (т.е. int**
), то вы не сможете получить форму вещи.
Я полагаю, вы могли бы написать его как:
unsafe System.Array ToIntPtrArray(System.Array a, void** aAsPtr)
Затем у вас есть метаданные System.Array
и фактические данные в форме, которую вы можете использовать.
Ответ 3
Несмотря на то, что на вопрос ответили хорошо, я чувствую необходимость оставить записку/предупреждение будущим читателям.
CLR предназначен для обеспечения безопасности или, по крайней мере, максимально безопасного. Он выполняет это с помощью (помимо всего прочего) безопасности и абстрагирования памяти. Хотя вы можете отключить некоторые из этих защит с помощью небезопасного блока, некоторые защиты жестко закодированы в компилятор/время выполнения. Чтобы обойти это дополнительное препятствие, нужно прибегнуть к хакке и, возможно, медленному коду. Несмотря на то, что эти методы работают, опыт научил меня, что такое поведение приводит к проблемам в будущем, будь то изменение среды выполнения, будущему программисту, нуждающемуся в изменении этого сегмента кода, или вам самому нужно что-то делать с этими указатели позже в коде.
В этот момент я бы серьезно рассмотрел вспомогательную dll, написанную в Managed С++, чтобы обрабатывать сторону "raw pointer". Мое рассуждение заключается в том, что, используя Unsafe, вы уже выбрасываете множество защит, которые предлагает CLR. Вам может быть легче работать без каких-либо дополнительных средств, испеченных в защите. Не говоря уже о том, что вы можете использовать указатели в полной мере, а затем отбросить их обратно до intptr, когда закончите. Если ничего другого, вы можете захотеть взглянуть на реализацию ToIntPtrArray в С++. Все еще передавайте указатели на стороне С#, но избегайте контроля CLR.
Обратите внимание, что я не говорю, что каждый раз, когда вы используете "небезопасные", вы должны выкинуть С++. Совсем наоборот - небезопасно позволит вам сделать совсем немного, но в этом случае помощник С++, вероятно, должен что-то рассмотреть.
Отказ от ответственности. Я не много сделал с управляемым С++. Может быть, я совершенно не прав, и CLR все равно будет следить за указателями. Если бы какая-то более опытная душа могла прокомментировать и сказать мне в любом случае, было бы очень благодарно.