Предупреждение: Это всего лишь упражнение для тех, кто страстно любит разбирать вещи, чтобы понять их механику.
Я изучал пределы того, что я смог выполнить в С#, и написал функцию ForceCast()
для выполнения трансляции грубой силы без каких-либо проверок типов. Никогда не используйте эту функцию в производственном коде.
Я написал класс под названием Original
и структуру под названием LikeOriginal
, как с двумя целыми переменными. В Main()
я создал новую переменную с именем orig
и установил ее в новый экземпляр Original
с a=7
и b=20
. Когда orig
передается в LikeOriginal
и сохраняется в casted
, значения cG
и dG
становятся undefined, что следует ожидать, поскольку LikeOriginal
- это экземпляры структуры и класса, содержащие больше метаданных чем экземпляры структуры, что приводит к несоответствию макета памяти.
Результат:
Casted Original to LikeOriginal
1300246376, 542
1300246376, 542
added 3
Casted LikeOriginal back to Original
1300246379, 545
Обратите внимание, что когда я вызываю casted.Add(3)
и отбрасываю обратно на Original
и печатаю значения a
и b
, неожиданно они успешно увеличиваются на 3, и это повторяемо.
Что меня смущает, так это то, что приведение класса к структуре приведет к тому, что cG
и dG
будут сопоставляться с метаданными класса, но когда они будут изменены и возвращены в класс, они правильно отображают с помощью a
и b
.
Почему это так?
Используемый код:
using System;
using System.Runtime.InteropServices;
namespace BreakingStuff {
public class Original {
public int a, b;
public Original(int a, int b)
{
this.a = a;
this.b = b;
}
public void Add(int val)
{
}
}
public struct LikeOriginal {
public int cG, dG;
public override string ToString() {
return cG + ", " + dG;
}
public void Add(int val) {
cG += val;
dG += val;
}
}
public static class Program {
public unsafe static void Main() {
Original orig = new Original(7, 20);
LikeOriginal casted = ForceCast<Original, LikeOriginal>(orig);
Console.WriteLine("Casted Original to LikeOriginal");
Console.WriteLine(casted.cG + ", " + casted.dG);
Console.WriteLine(casted.ToString());
casted.Add(3);
Console.WriteLine("added 3");
orig = ForceCast<LikeOriginal, Original>(casted);
Console.WriteLine("Casted LikeOriginal back to Original");
Console.WriteLine(orig.a + ", " + orig.b);
Console.ReadLine();
}
//performs a pointer cast but with the same memory layout.
private static unsafe TOut ForceCast<TIn, TOut>(this TIn input) {
GCHandle handle = GCHandle.Alloc(input);
TOut result = Read<TOut>(GCHandle.ToIntPtr(handle));
handle.Free();
return result;
}
private static unsafe T Read<T>(this IntPtr address) {
T obj = default(T);
if (address == IntPtr.Zero)
return obj;
TypedReference tr = __makeref(obj);
*(IntPtr*) (&tr) = address;
return __refvalue(tr, T);
}
}
}