zoukankan      html  css  js  c++  java
  • 如何在保留装箱对象的前提下修改值

    前两天给别人解答装箱问题时,有人问如何在保留装箱对象的前提下修改值?回头想了想,趁今天一会闲暇实现了出来,牵出来溜溜。

    场景:
    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 object
    Console.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,具体可以参考我的另一篇,链接在这里:http://www.cnblogs.com/winkingzhang/archive/2008/01/29/1057534.html),通过对象标识,就可以知道对象的往生来去了。另一种办法则是利用第二条知识,使用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
        }

    }
  • 相关阅读:
    《那些年啊,那些事——一个程序员的奋斗史》——95
    更精炼更专注的RTMPClient客户端EasyRTMPClient,满足直播、转发、分析等各种需求libEasyRTMPClient接口调用说明
    RTSP播放器网页web无插件直播流媒体音视频播放器libEasyPlayerRTSP库接口调用说明
    支持Windows/Linux 32&64/ARM各平台的简单高效RTSPClient工具库libEasyRTSPClient库接口调用说明
    支持H.264/G.711/G.726/AAC视频格式RTSP开源推流组件libEasyPusher库接口调用说明
    RTSP播放器网页web无插件直播流媒体音视频播放器libEasyPlayerRTSP库接口调用说明
    简单高效易用Windows/Linux/ARM/Android/iOS全平台实现RTMP推送组件EasyRTMP如何通过海康的SDK获取视频流推送到RTMP流媒体服务器
    支持8K播放且低延时高并发全功能的流媒体播放器EasyPlayerPro如何播放H.265视频?
    支持H.264/G.711/G.726/AAC视频格式RTSP开源推流组件EasyPusherWin推送到EasyDarwin服务器失败问题解决
    NVR录像机RTSP协议实时流转RTMP协议直播流推送方案EasyRTMPLive运行报0xc000007b错误的原因解析
  • 原文地址:https://www.cnblogs.com/winkingzhang/p/1083732.html
Copyright © 2011-2022 走看看