Mir*_*Mir 11 c# delegates unsafe ref typedreference
我想假设这个问题的目的是检查是否至少有一种方法,即使通过最不安全的黑客,仍然保持对非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)++;
}
}
Run Code Online (Sandbox Code Playgroud)
在安全性方面,上述类别可以与虚空中的跳跃相媲美(没有双关语意),但正如这里已经提到的那样.如果在堆栈上分配的变量传递给它,然后调用方法的作用域终止,则可能会遇到错误或显式访问冲突错误.但是,如果您执行这样的程序:
static class Program
{
static int _fieldValue = 42;
public static void Main(string[] args)
{
var foo = new Foo();
foo.Fix(ref _fieldValue);
foo.Increment();
}
}
Run Code Online (Sandbox Code Playgroud)
在卸载相关应用程序域之前,不会处理该类,因此适用于该字段.老实说,我不知道高频堆中的字段是否可以重新分配,但我个人认为不是.但是现在让我们放弃安全(如果可能的话).在阅读了这个和这些问题后,我想知道是否有办法为非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;
}
}
Run Code Online (Sandbox Code Playgroud)
我们知道编译器不会让我们存储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;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码同样适用,看起来比以前更糟,但为了知识,我想用它做更多,所以我写了以下内容:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
该Horror班是组合Foo类和上面的方法,但你肯定会注意到,它有一个很大的问题.在声明的方法中Fix,TypedReference tr它的地址被复制到通用指针内部_ptr,然后方法结束并且tr不再存在.当Clear调用该方法时,"new" tr被破坏,因为_ptr指向堆栈中不再是a的区域TypedReference.所以这里有一个问题:
有没有办法欺骗编译器使TypedReference实例保持活动一段不确定的时间?
任何达到预期结果的方法都会被认为是好的,即使它涉及丑陋,不安全,慢速的代码.实现以下接口的类是理想的:
interface IRefStorage<T> : IDisposable
{
void Store(ref T value);
//IDisposable.Dispose should release the reference
}
Run Code Online (Sandbox Code Playgroud)
因为它的目的是看看,毕竟还有请不要判断问题作为一般的讨论是存储引用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);
}
}
Run Code Online (Sandbox Code Playgroud)
什么Fix是从评论中标记为"魔术"的行开始:
refPtr为指向a的指针,TypedReference并将其值设置为上面分配的内存区域的指针.这样做,而不是_ptr直接使用,因为具有类型的字段TypedReference*会抛出异常.refPtr到void*并分配指针_ptr.tr为指向的值refPtr,因此_ptr.他还提供了另一个解决方案,他最初写的答案,但它似乎不如上面那个可靠.另一方面,Peter Wishart还提供了另一种解决方案,但它需要准确的同步,每个Horror实例都会"浪费"一个线程.我要抓住这个机会重申,上述方法是没有办法适用于现实世界中使用的,它只是一个学术问题.我希望这对阅读这个问题的人有所帮助.
你到底想做什么?局部变量位于堆栈上,参数也取决于调用约定。存储或返回本地或参数的地址不好,因为它会被覆盖。除了不调用方法之外,没有办法阻止它们被重写。
如果打开非托管调试,您可以使用内存调试器和寄存器窗口来查看发生了什么。
这是更容易理解的 C 示例。为什么打印不显示正确的值。因为当调用打印函数时,它的堆栈帧会覆盖该值。
int* bad(int x, int y)
{
int sum = x + y;
return ∑
};
int* bad2(int x, int y)
{
x += y;
return &x;
}
int _tmain(int argc, _TCHAR* argv[])
{
int* sum1 = bad(10, 10);
int* sum2 = bad(100, 100);
printf("%d bad", *sum1); // prints 200 instead of 20
sum1 = bad2(10, 10);
sum2 = bad2(100, 100);
printf("%d bad", *sum1); // prints 200 instead of 20
return 0;
};
Run Code Online (Sandbox Code Playgroud)
无法让 clr 坚持更长时间。您可以做的一件事是将堆栈上的变量进一步推出。下面是一个例子。但这一切都很糟糕:(
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Xml.Linq;
using System.Runtime.InteropServices;
namespace Bad
{
class Program
{
static void Main(string[] args)
{
Action a = () => Console.WriteLine("test");
Horror h = new Horror();
h.Fix(new Big(), ref a, new Big());
h.Clear();
Console.WriteLine();
}
}
[StructLayout(LayoutKind.Sequential, Size = 4096)]
struct Big
{
}
unsafe class Horror
{
void* _ptr;
static void Handler()
{
Console.WriteLine("Hello world.");
}
public void Fix(Big big, ref Action action, Big big2)
{
action += Handler;
var tr = __makeref(action);
_ptr = (void*)&tr;
}
public void Clear()
{
var tr = *(TypedReference*)_ptr;
__refvalue(tr, Action) -= Handler;
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
994 次 |
| 最近记录: |