Экскурс к тому, для чего нужно:
Я работаю с Unity, и вынужден пользоваться его API.
Среди API есть метод AssetBundle.LoadFromMemory(byte[] bytes)
, причем перегрузок с параметрами offset
, length
там нет.
Этот метод используется для чтения файлов из памяти, однако мои файлы упакованы в самописный архив, который я хотел читать через кешируемый буфер, чтобы не порождать аллокации при создании массива.
То есть я буквально заперт тем, что мне диктует API. Я не могу например сохранить размер массива отдельно и вынужден копировать данные в новый отдельный массив всякий раз, когда мне нужно воспользоваться данным методом.
Я бы хотел найти какой-нибудь способ обмануть метод API, протолкнув ему мой кешируемый буфер заведомо большего размера. Информация в этом кешируемом буфере просто перезаписывается поверх старой информации, а переразмер происходит только когда данных больше.
Для этих целей я нафантазировал "подменить" размер массива на меньший, а после вызова метода вернуть размер обратно. Буквально хотелось бы просто изменить число, которое возвращает Array.Length
, при том количество данных в нем оставить прежним.
Возможно ли как-то сделать это через unsafe
?
Любые другие идеи приветствуются.
Изменить размер можно, он хранится сразу перед первым элементом массива (проверял на CLR 2, 4, CoreCLR).
static unsafe void Main(string[] args)
{
// для примера
var array = new byte[] {
0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa
};
Print(array);
int oldLength = array.Length;
int newLength = 4;
// адрес начала массива
fixed (byte* ptr = array) {
// адрес, по которому хранится размер массива
// для x86 он равен ptr - 4, для x64 ptr - 8;
int* pSize = (int*)(ptr - sizeof(void*));
// устанавливаем новый размер (только в сторону уменьшения)
*pSize = newLength;
Print(array);
// здесь используйте массив в Unity...
// возвращаем старый размер
*pSize = oldLength;
Print(array);
}
}
static void Print(byte[] array)
{
Console.WriteLine("array.Length == " + array.Length);
foreach (byte a in array)
Console.Write(a.ToString("X2") + " ");
Console.WriteLine();
Console.WriteLine();
}
В результате функция Print выведет:
// исходный массив
array.Length == 10
A1 A2 A3 A4 A5 A6 A7 A8 A9 AA
// измененный массив
array.Length == 4
A1 A2 A3 A4
// восстановленный массив
array.Length == 10
A1 A2 A3 A4 A5 A6 A7 A8 A9 AA
Чтобы добавить смещение, нужно циклически сдвинуть массив влево на величину смещения, затем отрезать длину до нужной. Восстановление - в обратном порядке, сначала изменяем длину до оригинальной, затем двигаем массив циклически вправо.
Исходя из задачи всётаки двигать byte[]
это можно сделать так...
В памяти структура такая: СсылкаНаКласс - Размер - Данные. Ссылку на структуру, как и саму структуру можно менять. Но в процессе работы выяснилось, что обнамуть "указатель" не удаётся, и для смещения позиции таки нужно кастить gc.Target что б изменения вошли в силу. Вот пример реализации:
static byte[] ReArray(GCHandle garray, int offs, int size) {
IntPtr ppArray = GCHandle.ToIntPtr(garray);
IntPtr pArray = Marshal.ReadIntPtr(ppArray);
IntPtr ArrayClass = Marshal.ReadIntPtr(pArray);
int sz = Marshal.ReadInt32(pArray, IntPtr.Size); Console.WriteLine("sz="+sz);
/*Запись ссылки на класс*/
Marshal.WriteIntPtr(pArray, offs, ArrayClass);
/*Запись размера*/
Marshal.WriteInt32(pArray, offs + IntPtr.Size, size);
/*Запишем новую ссылку на сам массив*/
Marshal.WriteIntPtr(ppArray, (IntPtr.Size==4)?new IntPtr(pArray.ToInt32()+offs)
: new IntPtr( pArray.ToInt64() + offs ));
return (byte[])gc.Target;/*вернём сам массив (новый)*/
}
//-------------------------------------------------
GCHandle gc = GCHandle.Alloc(bytes); /*Берём массив*/
Your_Handler( ReArray(gc, 0, 4) ); /*Укоротим*/
Your_Handler( ReArray(gc, 4, 4) ); /*А теперь сложнее - сместим*/
Your_Handler( ReArray(gc, 8, 2) );
ReArray(gc, -12, 2); /*Вернуть позицию на "место" обязательно, сумма смещений 4+8 */
gc.Free();
Тут идея что offs - смещает массив "вперёд-назад", но 12 байт по адресу ppArray нужно потом вернуть назад (я так думаю что если не вернуть то возможен глюк). При смещении массива, если работать с "bytes" то в JIT-коде остаются прямые ссылки на pArray - и bytes начинает глючить (хотя фактически он обновился), но если сделать сast Target-у - "возвращается" bytes на заданное место (обновляет). Для х64 не тестил. Ещё под вопросом можно ли offs брать не кратным 4 или 8 (x64). Вроде работает, но будет ли исключение "unaligned read" не могу сказать. Как достоинство - unsafe можно избежать, но решение небезопасное. Понятно нужно расчитывать что для "шапки" при смещении несколько байт "вытираются".
"Легальный" путь создать массив на базе другого:
byte[] data = GetData(); // Получить массив который нужно разшить
your_handler( (new MemoryStream ( data, xfrom , xlen)).ToArray());
Но он более требовательный по-памяти. Если есть перезагрузка использующая Stream - то можно использовать.
Изучая паттерн Singleton столкнулся с тем ,что классическая реализация данного паттерна очень хромает в плане потокобезопасности и что Lazy-реализация...
Господа, встал такой вопрос - имеется SSO SAML token примерно в таком виде -