草庐IT

c# - 使 TypedReference 在方法 block 之外保持事件状态而不返回它

coder 2024-05-30 原文

我想假设这个问题的目的是检查是否至少有一种方法,即使是通过最不安全的 hack,来保持对非 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)++;
    }
}

在安全性方面,上述类(class)可与虚空跳跃(无双关语)相媲美,但它确实有效,正如前面提到的 here .如果将分配在堆栈上的变量传递给它,然后调用方方法的作用域终止,您可能会遇到错误或显式访问冲突错误。但是,如果您执行这样的程序:
static class Program
{
    static int _fieldValue = 42;

    public static void Main(string[] args)
    {
        var foo = new Foo();
        foo.Fix(ref _fieldValue);
        foo.Increment();
    }
}

在卸载相关应用程序域之前,不会处理该类,因此适用于该字段。老实说,我不知道高频堆中的字段是否可以重新分配,但我个人认为不能。但是现在让我们把安全放在一边(如果可能的话)。阅读后thisthis我想知道是否有办法为非 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 class 是 Foo 的组合class 和上述方法,但是您肯定会注意到,它有一个大问题。在方法Fix , TypedReference tr声明后,其地址被复制到泛型指针 _ptr 内,然后方法结束和 tr不复存在。当Clear方法被调用,"new"tr已损坏,因为 _ptr指向堆栈中不再是 TypedReference 的区域.那么问题来了:

有什么办法可以欺骗编译器保留一个 TypedReference实例存活时间不确定?

任何达到预期结果的方法都被认为是好的,即使它涉及丑陋、不安全、缓慢的代码。实现以下接口(interface)的类将是理想的:
interface IRefStorage<T> : IDisposable
{
    void Store(ref T value);
    //IDisposable.Dispose should release the reference
}

请不要将这个问题判断为一般性讨论,因为它的目的毕竟是查看是否存在 一种存储对 blittable 类型的引用的方法,尽管它可能很邪恶。

最后一点,我知道通过 FieldInfo 绑定(bind)字段的可能性。 ,但在我看来,后一种方法不支持派生自 Delegate 的类型。非常。

一个可能的解决方案(赏金结果)

一旦他编辑了他的帖子以包含他在评论中提供的解决方案,我就会将 AbdElRaheim 的答案标记为已选择,但我想这不是很清楚。无论哪种方式,在他提供的技术中,在以下类(class)中总结的技术(我略有修改)似乎最“可靠”(使用该术语具有讽刺意味,因为我们正在谈论利用黑客):
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确实是,从注释中标记为“magic”的行开始:
  • 在进程中分配内存——在它的非托管部分。
  • 声明 refPtr作为指向 TypedReference 的指针并将其值设置为上面分配的内存区域的指针。这样做了,而不是使用 _ptr直接,因为类型为 TypedReference* 的字段会抛出异常。
  • 隐式转换 refPtrvoid*并将指针分配给 _ptr .
  • tr作为 refPtr 指向的值因此 _ptr .

  • 他还提供了另一种解决方案,即他最初作为答案编写的解决方案,但它似乎不如上述方案可靠。另一方面,Peter Wishart 也提供了另一种解决方案,但它需要精确同步且每个 Horror实例会“浪费”一个线程。我会借此机会重复上述方法在用于现实世界的方式,这只是一个学术问题。我希望它对任何阅读这个问题的人都有帮助。

    最佳答案

    你到底想做什么?局部变量在堆栈上,参数也取决于调用约定。存储或返回本地或参数的地址并不好,因为它会被覆盖。除了不调用方法之外,没有办法防止它们被覆盖。

    如果您打开非托管调试,您可以使用内存调试器和注册窗口来查看发生了什么。

    这是更容易理解的 C 示例。为什么打印不显示正确的值。因为当打印函数被调用时,它的堆栈帧会覆盖该值。

    int* bad(int x, int y)
    {
        int sum = x + y;
        return &sum;
    };
    
    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;
    };
    

    无法让 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;
            }
        }
    }
    

    关于c# - 使 TypedReference 在方法 block 之外保持事件状态而不返回它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14168002/

    有关c# - 使 TypedReference 在方法 block 之外保持事件状态而不返回它的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

      我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

    5. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

      我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

    6. Ruby 方法() 方法 - 2

      我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

    7. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

      为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

    8. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    9. ruby - Highline 询问方法不会使用同一行 - 2

      设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

    10. ruby - RSpec - 使用测试替身作为 block 参数 - 2

      我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

    随机推荐