最近在做数据的处理,需要记录数据状态,用于用户的撤销和反撤销处理。
为了开发方便记录数据当然优先选择反射,采用指针数据加反射,数据容器使用DataBuffer,测试速度比Newtonsoft.Josn序列化快10几倍,但是平时开发的场景较多,保存一次需要50多MS。大多时间消耗在字段值读取和写入上面,对于强迫症来说,不能忍。
网上查关于C#的反射优化多大是使用属性,Emit,创建委托之类的
好在查询的时候发现的C#的不常用关键字 __makeref 和 __refvalue
然后花了两天时间捣鼓研究,发现 FieldInfo中有个字段叫RuntimeFieldHandle 的,其值是内存地址,好家伙,根据以前开发外挂的经验去做内存分析,看来微软还是比较良心声明也比较简单,32位的偏移位置地址记录在第8个字节那里,64位的偏移位置地址记录在第12字节那里。
而获取类的地址就比较简单了, 使用__makeref获取类实例的引用地址,然后通过引用地址再跳转一次就找到了。
那么动起手来,封装一个操作类,这里我们还要判定程序运行位数,使用Environment.Is64BitProcess;
代码就不详细介绍了,直接贴出来
using System;
using System.Collections.Generic;
using System.Text;
namespace huqiang.Data
{
public class UnsafeOperation
{
static bool is64 = Environment.Is64BitProcess;
/// <summary>
/// 获取值类型的地址
/// </summary>
/// <param name="tf"></param>
/// <returns></returns>
public unsafe static IntPtr GetValueAddr(TypedReference tf)
{
return *(IntPtr*)&tf;
}
/// <summary>
/// 获取引用类型的地址
/// </summary>
/// <param name="tf"></param>
/// <returns></returns>
public unsafe static IntPtr GetObjectAddr(TypedReference tf)
{
return *(IntPtr*)*(IntPtr*)&tf;
}
/// <summary>
/// 获取引用类型的地址
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public unsafe static IntPtr GetObjectAddr(object obj)
{
TypedReference tf = __makeref(obj);
return *(IntPtr*)*(IntPtr*)&tf;
}
/// <summary>
/// 程序的字段声明偏移位置
/// </summary>
/// <param name="ptr"></param>
/// <returns></returns>
public unsafe static int GetFeildOffset(IntPtr ptr)
{
if (is64)
return *(Int16*)(ptr + 12) + 8;
return *(Int16*)(ptr + 8) + 4;
}
/// <summary>
/// 将对象的地址设置到目标地址,不会有类型判定和引用计数,操作堆数据会造成GC判定错误,推荐在栈上操作
/// </summary>
/// <param name="tar"></param>
/// <param name="obj"></param>
public unsafe static void UnsafeSetObject(void* tar, object obj)
{
TypedReference tf = __makeref(obj);
if (is64)
*(long*)tar = *(long*)(*(IntPtr*)&tf);
else
*(int*)tar = *(int*)(*(IntPtr*)&tf);
}
/// <summary>
/// 将对象的地址设置到目标地址,有类型判定和引用计数,推荐在堆上操作
/// </summary>
/// <param name="tar"></param>
/// <param name="obj"></param>
public unsafe static void SetObject(IntPtr tar, object obj)
{
object tmp = "";
TypedReference tr = __makeref(tmp);
if(is64)
{
long* p = (long*)&tr;
*p = (long)tar;
__refvalue(tr, object) = obj;
}
else
{
int* p = (int*)&tr;
*p = (int)tar;
__refvalue(tr, object) = obj;
}
}
/// <summary>
/// 获取目标地址的对象
/// </summary>
/// <param name="tar"></param>
/// <returns></returns>
public unsafe static object GetObject(void* tar)
{
object tmp = "";
TypedReference tr = __makeref(tmp);//new TypedReference();
if(is64)
{
long* p = (long*)&tr;
*p = (long)tar;
return __refvalue(tr, object);
}
else
{
int* p = (int*)&tr;
*p = (int)tar;
return __refvalue(tr, object);
}
}
/// <summary>
/// 获取32位程序的字段声明偏移位置
/// </summary>
/// <param name="ptr"></param>
/// <returns></returns>
public unsafe static int GetFeildOffset32(IntPtr ptr)
{
return *(Int16*)(ptr + 8) + 4;
}
/// <summary>
/// 将对象的地址设置到目标地址,不会有类型判定和引用计数,操作堆数据会造成GC判定错误,推荐在栈上操作
/// </summary>
/// <param name="tar"></param>
/// <param name="obj"></param>
public unsafe static void UnsafeSetObject32(int* tar, object obj)
{
TypedReference tf = __makeref(obj);
*tar = *(int*)(*(IntPtr*)&tf);
}
/// <summary>
/// 将对象的地址设置到目标地址,有类型判定和引用计数,推荐在堆上操作
/// </summary>
/// <param name="tar"></param>
/// <param name="obj"></param>
public unsafe static void SetObject32(int* tar, object obj)
{
object tmp = "";
TypedReference tr = __makeref(tmp);
int* p = (int*)&tr;
*p = (int)tar;
__refvalue(tr, object) = obj;
}
/// <summary>
/// 获取目标地址的对象
/// </summary>
/// <param name="tar"></param>
/// <returns></returns>
public unsafe static object GetObject32(int* tar)
{
object tmp = "";
TypedReference tr = __makeref(tmp);//new TypedReference();
int* p = (int*)&tr;
*p = (int)tar;
return __refvalue(tr, object);
}
/// <summary>
/// 获取64位程序的字段声明偏移位置
/// </summary>
/// <param name="ptr"></param>
/// <returns></returns>
public unsafe static int GetFeildOffset64(IntPtr ptr)
{
return *(Int16*)(ptr + 12) + 8;
}
/// <summary>
/// 将对象的地址设置到目标地址,不会有类型判定和引用计数,操作堆数据会造成GC判定错误,推荐在栈上操作
/// </summary>
/// <param name="tar"></param>
/// <param name="obj"></param>
public unsafe static void UnsafeSetObject64(long* tar, object obj)
{
TypedReference tf = __makeref(obj);
*tar = *(long*)(*(IntPtr*)&tf);
}
/// <summary>
/// 将对象的地址设置到目标地址,有类型判定和引用计数,推荐在堆上操作
/// </summary>
/// <param name="tar"></param>
/// <param name="obj"></param>
public unsafe static void SetObject64(long* tar, object obj)
{
object tmp = "";
TypedReference tr = __makeref(tmp);
long* p = (long*)&tr;
*p = (long)tar;
__refvalue(tr, object) = obj;
}
/// <summary>
/// 获取目标地址的对象
/// </summary>
/// <param name="tar"></param>
/// <returns></returns>
public unsafe static object GetObject64(void* tar)
{
object tmp = "";
TypedReference tr = __makeref(tmp);//new TypedReference();
long* p = (long*)&tr;
*p = (long)tar;
return __refvalue(tr, object);
}
}
}
然后我们写一个控制台测试程序
using System;
using huqiang.Data;
namespace ConsoleTest
{
class Program
{
public class Wepon
{
public string Name;
}
public class People
{
public int Age;
public int Tow { get; set; }
public byte Sex;
public string Name;
public long Time;
public Wepon wepon;
}
unsafe static void Main(string[] args)
{
Type typ = typeof(People);
var nf = typ.GetField("Age");
var handle = nf.FieldHandle;
int os1 = UnsafeOperation.GetFeildOffset(handle.Value);
nf = typ.GetField("Sex");
var handle2 = nf.FieldHandle;
int os2 = UnsafeOperation.GetFeildOffset(handle2.Value);
nf = typ.GetField("wepon");
var handle3 = nf.FieldHandle;
int os3 = UnsafeOperation.GetFeildOffset(handle3.Value);
nf = typ.GetField("Name");
var handle4 = nf.FieldHandle;
int os4 = UnsafeOperation.GetFeildOffset(handle4.Value);
People people = new People();
people.Age = 16;
people.Name = "Hello";
people.Tow = 6;
people.Sex = 4;
people.Time = -1;
Wepon wepon = new Wepon();
wepon.Name = "机枪";
TypedReference tf = __makeref(people);
IntPtr c = UnsafeOperation.GetObjectAddr(tf);
*(int*)(c + os1) = 200;//设置年龄
*(byte*)(c + os2) = 3;//设置性别
UnsafeOperation.SetObject(c + os3, wepon);//设置武器类
UnsafeOperation.SetObject(c + os4, "打的不错哦!呵呵.");//设置名字
Console.WriteLine(people.Age);
Console.WriteLine(people.Sex);
Console.WriteLine(people.wepon.Name);
Console.WriteLine(people.Name);
Console.ReadKey(false);
}
}
}
运行效果如下
将代码转移至Unity中发现内存的偏移地址不一样,使用VS无法直接查看内存,只好祭出Cheat Engine,发现其字段数据偏移地址为24
public unsafe static Int16 GetFeildOffset(IntPtr ptr)
{
return *(Int16*)(ptr + 24);
}
其对象地址需要再偏移一个长整型
public unsafe static object GetObject(IntPtr tar)
{
object tmp = "";
TypedReference tr = __makeref(tmp);//new TypedReference();
long* p = (long*)&tr;
p++;
*p = (long)tar;
return __refvalue(tr, object);
}
最后测试结果运行效率提升1倍,勉强能接受吧
创作不易,转载请声明原创地址