在显式结构中将几个CLR引用字段相互叠加?

thr*_*thr 11 .net c# clr struct

编辑:我很清楚这对于值类型非常有效,我的具体问题是将其用于引用类型.

Edit2:我也知道你不能在结构中覆盖引用类型和值类型,这只是为了将几个引用类型字段相互重叠.

我一直在修补.NET/C#中的结构,我发现你可以这样做:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1 {

    class Foo { }
    class Bar { }

    [StructLayout(LayoutKind.Explicit)]
    struct Overlaid {
        [FieldOffset(0)] public object AsObject;
        [FieldOffset(0)] public Foo AsFoo;
        [FieldOffset(0)] public Bar AsBar;
    }

    class Program {
        static void Main(string[] args) {
            var overlaid = new Overlaid();
            overlaid.AsObject = new Bar();
            Console.WriteLine(overlaid.AsBar);

            overlaid.AsObject = new Foo();
            Console.WriteLine(overlaid.AsFoo);
            Console.ReadLine();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上避免在运行时通过使用具有显式字段布局的结构进行动态转换,然后以正确的类型访问内部对象.

现在我的问题是:这是否会以某种方式导致内存泄漏,或CLR内部的任何其他未定义的行为?或者这是一个完全支持的约定,可以使用而没有任何问题?

我知道这是CLR的一个黑暗角落,而且这种技术在极少数特定情况下才是可行的选择.

Han*_*ant 5

好吧,您发现了一个漏洞,CLR 允许它,因为所有重叠的字段都是对象。任何允许你直接处理对象引用的东西都会被 TypeLoadException 拒绝:

  [StructLayout(LayoutKind.Explicit)]
  struct Overlaid {
    [FieldOffset(0)]
    public object AsObject;
    [FieldOffset(0)]
    public IntPtr AsPointer;
  }
Run Code Online (Sandbox Code Playgroud)

但是您可以通过提供类字段来利用它。只要您只是读取字段值,就不会发生真正的坏事,例如,您可以通过这种方式获取跟踪句柄的值。

然而,写入这些字段会导致 ExecutionEngineException。但是,我认为如果您可以正确猜测跟踪句柄的值,那它就是一个漏洞。不过实际使用已经足够接近于零了。


Mar*_*ell 1

我看不出如何在运行时不注入额外检查的情况下验证显式布局版本,因为它允许您查看对不属于声明类型的内容的非空引用。

这会更安全:

struct Overlaid { // could also be a class for reference-type semantics
    private object asObject;
    public object AsObject {get {return asObject;} set {asObject = value;} }
    public Foo AsFoo { get {return asObject as Foo;} set {asObject = value;} }
    public Bar AsBar { get {return asObject as Bar;} set {asObject = value;} }
}
Run Code Online (Sandbox Code Playgroud)

没有参考资料被撕毁等风险,而且仍然只有一个字段。它不涉及任何有风险的代码等。特别是,它不会冒诸如以下愚蠢的事情的风险:

    [FieldOffset(0)]
    public object AsObject;
    [FieldOffset(0)]
    public Foo AsFoo;
    [FieldOffset(1)]
    public Bar AsBar; // kaboom!!!!
Run Code Online (Sandbox Code Playgroud)

另一个问题是,这种方式只能支持单个字段,除非你能保证CPU模式;偏移量 0 很简单,但如果您需要多个字段并且需要支持 x86 和 x64,它会变得更加棘手。

  • 没有真正回答我的问题。我想知道是否可以按照我的要求进行(并且_确切__我的要求)。不是同一问题的另一种解决方案(因为虽然我的示例经过简化,但真正的问题不能使用引用类型)。 (3认同)
  • 我需要将它们保存在完全相同的内存位置。你根本就没有回答我的问题。当然,您提供了另一种解决方案,但这不是我的问题:“我如何以不同的方式解决这个问题?”,现在是吗? (3认同)