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

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

 

场景:

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),通过对象标识,就可以知道对象的往生来去了。另一种办法则是利用第二条知识,使用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
    }
}
时间: 2024-10-22 14:45:28

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

《精通QTP——自动化测试技术领航》—第1章1.6节对象库(下)之进阶编程篇

1.6 对象库(下)之进阶编程篇 精通QTP--自动化测试技术领航 阶段要点 掌握手写代码的3种方式. 明确测试对象(TO)与运行时对象(RO)的区别. 4种操作对象封装属性的方法. 你也可以是一名魔术师.黑客. 几种常见的QTP无法识别或识别错误的原因. 1.6.1 引言 在对象库上篇这个章节中,相信读者已经对对象库的使用以及其他各个方面有了一个全面的了解.那么从现在开始,作者将引领读者开始基于对象库编程的学习,从而掌握对象库编程的知识与技巧.读者如果觉得在对象库上篇中还有不能够完全掌握的地方

如何在不破坏Java虚拟机的前提下实现热部署

本文将探索如何在不破坏 Java 虚拟机现有行为的前提下,实现某个单一类的热部署,让系统无需重启就完成某个类的更新. 在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作.对于某些大型的应用来说,每次的重启都需要花费大量的http://www.aliyun.com/zixun/aggregation/7201.html">时间成本.虽然 osgi 架构的出现,让模块

在不格式化硬盘的前提下转成NTFS格式

  故障问题:在不格式化硬盘的前提下转成NTFS格式 故障处理:在"开始"菜单中单击"运行"命令,打开"运行"对话框,输入"cmd"并回车,在打开的命令提示符窗口中输入命令"convert X: /FS:NTFS",其中X代表欲转换文件的分区盘符.比如要将C盘从FAT32格式转换为NTFS格式,就在命令提示符窗口中输入"convert C: /FS:NTFS",然后回车.

在没有物理光驱的前提下,U盘在PE下怎么安装xp

在没有物理光驱的前提下,利用U盘在PE下安装XP原版镜像的步骤方法如下: 需要用到的工具: winxp原版系统iso镜像+启动U盘 电脑店U盘制作工具 winxp原版系统iso镜像下载地址: 中文版迅雷下载(或电驴) | 英文版迅雷下载(或电驴) 1.进入第一个PE后找到我们事先准备好的xp光盘iso镜像,右键点击加载虚拟磁盘(也可以利用虚拟光驱来加载或者直接用右键里的RAR解压到本地硬盘分区) 第一步 2.打开桌面上的windows安装工具,选择刚才加载的虚拟磁盘(如果前面是直接解压的ISO文

python-Python中subprocess模块怎样运行外一个shell命令的前提下再运行另外一个,谢谢

问题描述 Python中subprocess模块怎样运行外一个shell命令的前提下再运行另外一个,谢谢 比如说先运行切换用户权限,然后再进行操作比如以下编码是不行的 child1=subprocess.Popen('su test'shell=True)child1=subprocess.Popen('mkdir test'shell=True)这样子还是会在当前用户进行mkdir,那怎样做才能在test用户下进行创建?谢谢!! 解决方案 把几个命令放到一个bash脚本 然后popen直接执行

java socket 多线程-请问下面这个程序,在不改变功能的前提下,可以改装成多线程运行吗?*请贴上代码,谢谢*

问题描述 请问下面这个程序,在不改变功能的前提下,可以改装成多线程运行吗?*请贴上代码,谢谢* import java.net.*; // for Socket, ServerSocket, and InetAddress import java.io.*; // for IOException and Input/OutputStream public class Server { private static final int BUFSIZE = 32; // Size of receiv

如果文件感染病毒,在保证数据安全的前提下应该:

问题描述 如果文件感染病毒,在保证数据安全的前提下应该: A.隔离病毒 B.删除病毒 C.不做任何操作 D.删除文件 应该选择什么? 求网鞋回答 解决方案 我会选择这个:D.删除文件 解决方案二: B 一定要删除v 病毒 解决方案三: 和文件一起隔离,一般删不掉的

ios- IOS在不使用任何框架的前提下,如何把模型转换成字典呢?

问题描述 IOS在不使用任何框架的前提下,如何把模型转换成字典呢? IOS在不使用任何框架的前提下,如何把模型转换成字典呢?我现在真的不知道怎么弄了,谢谢 解决方案 利用KVC就可以了直接[self setValuesForKeysWithDictionary:Dict]; 解决方案二: 你要的是自动转换吗?那样就需要通过objc的函数遍历模型的属性,然后转换成字典的 key-value ,还要考虑数据类型兼容问题,不然会有bug,具体做法可以参考 JsonModel 的实现如果是手动转换,那就

winform-C# Winform项目,在不关闭在线视频播放的App客户端前提下,如何实现频道切换。

问题描述 C# Winform项目,在不关闭在线视频播放的App客户端前提下,如何实现频道切换. 在C# Winform项目,有个form窗体以及三个button按钮(button1为打开"CCTV-5"体育频道,button2为打开"CCTV-2"财经频道,button3为打开"CCTV-13"新闻频道) 目前已经可以通过以下代码实现在button1按钮中将"CBOX央视影音"在线视频播放的App客户端的"CCTV