Я делал несколько крайне небезопасных и немного бесполезных действий с пакетом System.Runtime.CompilerServices.Unsafe
MSIL, который позволяет вам многое делать с указателями, которые вы не можете использовать в C #. Я создал метод расширения, который возвращает ref byte
, причем этот байт является началом указателя таблицы методов в начале объекта, что позволяет использовать любой объект в фиксированной инструкции, переводя байтовый указатель в начало объект:
public static unsafe ref byte GetPinnableReference(this object obj)
{
return ref *(byte*)*(void**)Unsafe.AsPointer(ref obj);
}
Затем я решил проверить это, используя этот код:
[StructLayout(LayoutKind.Explicit, Pack = 0)]
public class Foo
{
[FieldOffset(0)]
public string Name = "THIS IS A STRING";
}
[StructLayout(LayoutKind.Explicit, Pack = 0)]
public struct Bar
{
[FieldOffset(0)]
public string Name;
}
А потом в методе
var foo = new Foo();
//var foo = new Bar { Name = "THIS IS A STRING" };
fixed (byte* objPtr = foo)
{
char* stringPtr = (char*)(objPtr + (foo is Foo ? : 12));
for (var i = 0; i < foo.Name.Length; i++)
{
Console.Write(*(stringPtr + i /* Char offset */));
}
Console.WriteLine();
}
Console.ReadKey();
Действительно странная вещь об этом - то, что это успешно печатает "ЭТО СТРОКА"? Код работает так:
- Получить байтовый указатель,
objPtr
, к самому началу объекта
- Добавьте 16, чтобы получить фактические данные
- Добавьте еще 16, чтобы пройти заголовок строки к фактическим данным строки.
- Добавьте 4, чтобы пропустить первые 4 байта строки, которые являются
int
_stringLength
(выставлено нам как свойство Length
)
- интерпретировать результат как указатель на символ
EDIT: Важный момент - при переключении foo
на тип Bar
я добавляю только 12, а не 36 байтов (36 = 16 + 16 + 4). Почему в структуре только 8 байтов заголовка, а не 32 в классе? Было бы разумно, чтобы структура имела меньший заголовок (я не думаю, что syncblk), но тогда почему строка не имеет 16-байтовой головы? Я ожидаю, что смещение будет 8 + 16 + 4 (28), а не 8 + 4 (12)
Однако это предположение делает большой недостаток. Предполагается, что строка хранится внутри class/struct
. Однако строки являются ссылочными типами, и только моя ссылка на них хранится внутри объекта. В частности, я думал, что ссылочные типы могут быть помещены в кучу только - и поскольку эта структура является локальной переменной, я думал, что она находится в стеке. Если бы это было не так, код наверняка выглядел бы примерно так, чтобы получить stringPtr
byte** stringRefptr = objPtr + 16;
char* stringPtr = (char*)(*stringRefPtr + 20);
где вы берете строковую ссылку как byte**
и затем используете ее, чтобы добраться до символов. И это все равно не имело бы смысла, если бы внутренняя строка была char[]
(я не уверен, что это так)
Так почему же это работает, и вывести строку, даже если она ошибочно предполагает, что строка хранится в строке, когда строка является ссылочным типом?
ПРИМЕЧАНИЕ. Требуется .NET Core 2.0+ с пакетом System.Runtime.CompilerServices.Unsafe
nuGet и C # 7.3+.