为了账号安全,请及时绑定邮箱和手机立即绑定

如何在保留装箱对象的前提下修改值

标签:
资讯

有人问如何在保留装箱对象的前提下修改值?

 

场景:

?

object obj = 100;Console.WriteLine("original object value: " + obj.ToString()); // when debug, make obj's ID: 1#//TODO: modify obj value here (to 1000, for example), but preserve obj objectConsole.WriteLine("modified object value: " + obj.ToString()); // make sure obj's ID: 1#

 

分析:
显然这里直接obj = 1000是不行的,那样之后得到的是对1000装箱的对象,而不是对100的装箱对象了,那么如何修改呢?  
首先,这里列出本文涉及的一些.NET和CLR的准备知识——装箱的对象的分配和存储、对象的托管内存地址获取、对象唯一性确定、托管内存数据读写。如果你不是很熟悉,没关系,经过本篇的实践,加上MSDN的解释,你很快就可以理解。

1、对象的分配和存储。这里设计的仅仅是部分,细节可以参考CLR via。对象分配在托管堆上,由几个部分组成,第一部分是存储的是对象类型的TypeHandle,其后内容随类型不同而不同;对于装箱对象,其后紧跟的内存存储的是装箱的值(就是我们要找到然后去修改的东东了)。

2、对象的托管内存地址获取。通过System.Runtime.InteropServices.GCHandle类和其上的静态方法获取。

3、对象唯一性确定。这个方法有两种,第一种,需要依赖VS IDE Debug环境,在IDE的debug下,可以对任何对象设置对象标识(object ID),通过对象标识,就可以知道对象的往生来去了。另一种办法则是利用第二条知识,使用GCHandle的IsAllocated来判断。

4、通过上面得到了托管地址,如何修改托管地址处保存的内容呢?使用System.Runtime.InteropServices.Marshal.StructureToPtr或者System.Runtime.InteropServices.Marshal.WriteXXX系列方法即可。

基于以上内容,我们可以可以做到在保留装箱对象的前提下修改值了,显然首先需要的是装箱对象的引用,然后调用System.Runtime.InteropServices.GCHandle.Aloc(object)得到托管地址,该托管地址指向的内容就是装箱的对象;由于装箱对象的第一部分是TypeHandle,所以需要将指针向后偏移IntPtr.Size得到数据存储地址,然后通过Marshal.StructureToPtr写入新的内容即可。代码片段如下

?

if (!_memData.IsAllocated){    _memData = GCHandle.Alloc(_boxedObject);}IntPtr pMemData = GCHandle.ToIntPtr(_memData);IntPtr pBox = new IntPtr((Marshal.ReadIntPtr(pMemData).ToInt64() + IntPtr.Size));Marshal.StructureToPtr(value, pBox, false);

 

结果:

 

 

 

讨论:
显然这里写入数据时候是需要很小心的,因为如果装箱的数据占用内存小,而写入的数据比它大的话,就会触发AccessViolationException,甚至导致溢出,形成安全漏洞。

额外话题:  
如果传入的就是一个引用类型的实例,会是什么结果呢? 
还等什么呢,赶快自己动手试试喽。

附录,完整的测试代码:

 

?

using System;using System.Runtime.InteropServices; namespace BoxedObjectWriter{    class Program    {        static void Main(string[] args)        {            object test = 100;            Console.WriteLine("original value=" + test.ToString() + ", hash=" + test.GetHashCode());            BoxedObject b = new BoxedObject(test);            b.Value = 1000;            Console.WriteLine("after edit value=" + test.ToString() + ", hash=" + test.GetHashCode());             Console.ReadLine();        }    }     public class BoxedObject : IDisposable    {        private object _boxedObject;        private GCHandle _memData;         public BoxedObject(object boxObject)        {            if (boxObject == null)            {                throw new ArgumentNullException();            }            _boxedObject = boxObject;                    }         ~BoxedObject()        {            (this as IDisposable).Dispose();        }         public object Value        {            get { return _boxedObject; }            set            {                if (value == null)                {                    throw new ArgumentNullException();                }                if (value.GetType() != _boxedObject.GetType())                {                    throw new NotSupportedException(string.Format("Can not set [{0}] value to [{1}] object",                        value.GetType().Name,                        _boxedObject.GetType().Name));                }                 if (!_memData.IsAllocated)                {                    _memData = GCHandle.Alloc(_boxedObject);                }                IntPtr pMemData = GCHandle.ToIntPtr(_memData);                IntPtr pBox = new IntPtr((Marshal.ReadIntPtr(pMemData).ToInt64() + IntPtr.Size));                Marshal.StructureToPtr(value, pBox, false);            }        }         IDisposable Members    }}
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消