我想假设这个问题的目的是检查是否至少有一种方法,即使是通过最不安全的黑客攻击,来保持对非 blittable 值类型的引用。我知道这样的设计类型可以与犯罪相提并论;除了学习之外,我不会在任何实际情况下使用它。所以现在请接受阅读异端的不安全代码。
我们知道可以通过这种方式存储和增加对 blittable 类型的引用:
unsafe class Foo
{
void* _ptr;
public void Fix(ref int value)
{
fixed (void* ptr = &value) _ptr = ptr;
}
public void Increment()
{
var pointer = (int*) _ptr;
(*pointer)++;
}
}
就安全性而言,上述课程可与虚空中的跳跃相媲美(没有双关语),但正如这里已经提到的那样,它确实有效。如果将分配在堆栈上的变量传递给它,然后调用方方法的作用域终止,您可能会遇到错误或显式访问冲突错误。但是,如果您执行这样的程序:
static class Program
{
static int _fieldValue = 42;
public static void Main(string[] args)
{
var foo = new Foo();
foo.Fix(ref _fieldValue);
foo.Increment();
}
}
在卸载相关应用程序域之前,不会释放该类,因此适用于该字段。老实说,我不知道高频堆中的字段是否可以重新分配,但我个人认为不会。但是,让我们现在更加抛开安全问题(如果可能的话)。在阅读了这个和这个问题之后,我想知道是否有一种方法可以为非 blittable 静态类型创建类似的方法,所以我制作了这个程序,它确实有效。阅读评论以了解它的作用。
static class Program
{
static Action _event;
public static void Main(string[] args)
{
MakerefTest(ref _event);
//The invocation list is empty again
var isEmpty = _event == null;
}
static void MakerefTest(ref Action multicast)
{
Action handler = () => Console.WriteLine("Hello world.");
//Assigning a handler to the delegate
multicast += handler;
//Executing the delegate's invocation list successfully
if (multicast != null) multicast();
//Encapsulating the reference in a TypedReference
var tr = __makeref(multicast);
//Removing the handler
__refvalue(tr, Action) -= handler;
}
}
实际问题/机会:
我们知道编译器不会让我们存储 ref 传递的值,但是__makeref
关键字,尽管没有记录和建议,但提供了封装和恢复对 blittable 类型的引用的可能性。__makeref
但是,, 的返回值受到了TypedReference
很好的保护。你不能将它存储在一个字段中,你不能装箱,你不能创建一个数组,你不能在匿名方法或 lambdas 中使用它。我所做的只是将上面的代码修改如下:
static void* _ptr;
static void MakerefTest(ref Action multicast)
{
Action handler = () => Console.WriteLine("Hello world.");
multicast += handler;
if (multicast != null) multicast();
var tr = __makeref(multicast);
//Storing the address of the TypedReference (which is on the stack!)
//inside of _ptr;
_ptr = (void*) &tr;
//Getting the TypedReference back from the pointer:
var restoredTr = *(TypedReference*) _ptr;
__refvalue(restoredTr, Action) -= handler;
}
上面的代码同样有效,看起来比以前更糟,但为了知识,我想用它做更多的事情,所以我写了以下内容:
unsafe class Horror
{
void* _ptr;
static void Handler()
{
Console.WriteLine("Hello world.");
}
public void Fix(ref Action action)
{
action += Handler;
var tr = __makeref(action);
_ptr = (void*) &tr;
}
public void Clear()
{
var tr = *(TypedReference*) _ptr;
__refvalue(tr, Action) -= Handler;
}
}
该类Horror
是Foo
该类和上述方法的组合,但您肯定会注意到,它有一个大问题。在方法Fix
中,TypedReference
tr
被声明,它的地址被复制到泛型指针_ptr
中,然后方法结束并且tr
不再存在。当Clear
调用该方法时,“new”tr
被破坏,因为_ptr
指向堆栈的一个区域,该区域不再是TypedReference
. 那么问题来了:
有什么办法可以欺骗编译器使TypedReference
实例在不确定的时间内保持活动状态?
任何达到预期结果的方法都将被认为是好的,即使它涉及丑陋、不安全、缓慢的代码。实现以下接口的类将是理想的:
interface IRefStorage<T> : IDisposable
{
void Store(ref T value);
//IDisposable.Dispose should release the reference
}
请不要将这个问题判断为一般性讨论,因为它的目的是看看到底是否有一种方法可以存储对 blittable 类型的引用,尽管它可能很邪恶。
最后一点,我知道通过 绑定字段的可能性FieldInfo
,但在我看来,后一种方法不支持Delegate
非常多的派生类型。
一个可能的解决方案(赏金结果)
我会在 AbdElRaheim 编辑他的帖子以包含他在评论中提供的解决方案时将他的答案标记为已选择,但我想这不是很清楚。无论哪种方式,在他提供的技术中,在以下课程中总结的技术(我稍微修改了一下)似乎是最“可靠”的(使用该术语具有讽刺意味,因为我们正在谈论利用黑客攻击):
unsafe class Horror : IDisposable
{
void* _ptr;
static void Handler()
{
Console.WriteLine("Hello world.");
}
public void Fix(ref Action action)
{
action += Handler;
TypedReference tr = __makeref(action);
var mem = Marshal.AllocHGlobal(sizeof (TypedReference)); //magic
var refPtr = (TypedReference*) mem.ToPointer();
_ptr = refPtr;
*refPtr = tr;
}
public void Dispose()
{
var tr = *(TypedReference*)_ptr;
__refvalue(tr, Action) -= Handler;
Marshal.FreeHGlobal((IntPtr)_ptr);
}
}
什么Fix
是,从注释中标记为“魔术”的行开始:
- 在进程中分配内存——在它的非托管部分。
- 声明
refPtr
为指向 a 的指针TypedReference
并将其值设置为上面分配的内存区域的指针。这是完成的,而不是_ptr
直接使用,因为具有类型的字段TypedReference*
会引发异常。 - 隐式强制转换
refPtr
并将void*
指针分配给_ptr
. - 设置
tr
为refPtr
和 因此指向的值_ptr
。
他还提供了另一种解决方案,他最初作为答案写的那个,但它似乎不如上面的那个可靠。另一方面,Peter Wishart 还提供了另一种解决方案,但它需要准确的同步,并且每个Horror
实例都会“浪费”一个线程。我将借此机会重申,上述方法绝不适用于现实世界,这只是一个学术问题。我希望它对阅读这个问题的人有所帮助。