【问题标题】:Converting a D object pointer to void* and passing to a callback将 D 对象指针转换为 void* 并传递给回调
【发布时间】:2018-12-31 14:56:27
【问题描述】:

我想将 D 类指针转换为 void*,将这个 void* 指针连同指向我的回调函数 extern(C) 的指针一起传递给 C 库例程。

C 库例程将调用我的回调extern(C) 函数,该函数会将void* 转换回类指针并使用该类的对象。

问题:我听说 GC 对象可能会被移动到其他位置(可能不是在当前的 D 版本中,而是在将来)。这是否意味着我的void* 指针可能会失效(不再指向我的对象)?

如果问题真的存在,如何解决?

【问题讨论】:

    标签: pointers garbage-collection d void-pointers


    【解决方案1】:

    您可以告诉 GC 将指针作为根保留,此外,您可以使用 import core.memory; GC.addRoot(ptr); 函数告诉它不要将其移动到您身上。这个例子完整地展示了它:

    http://dpldocs.info/experimental-docs/core.memory.GC.addRoot.html#examples

    // Typical C-style callback mechanism; the passed function
    // is invoked with the user-supplied context pointer at a
    // later point.
    extern(C) void addCallback(void function(void*), void*);
    
    // Allocate an object on the GC heap (this would usually be
    // some application-specific context data).
    auto context = new Object;
    
    // Make sure that it is not collected even if it is no
    // longer referenced from D code (stack, GC heap, …).
    GC.addRoot(cast(void*)context);
    
    // Also ensure that a moving collector does not relocate
    // the object.
    GC.setAttr(cast(void*)context, GC.BlkAttr.NO_MOVE);
    
    // Now context can be safely passed to the C library.
    addCallback(&myHandler, cast(void*)context);
    
    extern(C) void myHandler(void* ctx)
    {
       // Assuming that the callback is invoked only once, the
       // added root can be removed again now to allow the GC
       // to collect it later.
       GC.removeRoot(ctx);
       GC.clrAttr(ctx, GC.BlkAttr.NO_MOVE);
    
       auto context = cast(Object)ctx;
       // Use context here…
    }
    

    【讨论】:

    • 我会交换 GC.removeRoot(ctx);GC.clrAttr(ctx, GC.BlkAttr.NO_MOVE);,因为在 OOP 中,资源应该按照它们获取的相反顺序释放。
    • 如果您的代码改为:scope auto context = new Object;(即在堆栈上而不是在堆上分配的对象),您的代码会工作吗?
    • 一般情况下 - 回调的危险在于它会超出调用者的范围,这意味着堆栈分配的对象肯定是死的。如果回调只发生在范围内,那么无论哪种方式都无关紧要 - 对 GC 对象的堆栈引用也将是活动的。 (如果它只是在 C 函数内部运行,那么 D GC 将永远不会运行,除非有另一个线程触发它。)
    • 我想问如果context 指向堆栈,GC.addRoot(cast(void*)context); 是否会起作用。我怀疑它可能会出现段错误、抛出异常或导致未定义的行为。我的恐惧有根据吗?
    • 未定义;文档只说明它指向 GC 块或 null 时会做什么。
    【解决方案2】:

    基于 Adam D. Ruppe 的回答,但已重新组织。

    更好的 OO 代码:

    import core.memory : GC;
    
    class UnmovableObject {
        this() {
            //GC.addRoot(cast(void*)this); // prevents finalization
            GC.setAttr(cast(void*)this, GC.BlkAttr.NO_MOVE);
        }
        ~this() {
           //GC.removeRoot(cast(void*)this);
           GC.clrAttr(cast(void*)this, GC.BlkAttr.NO_MOVE);
       }
    }
    

    【讨论】:

    • 除非手动调用(使用.destroy(obj) 函数),否则析构函数永远不会运行,因为虽然对象是 GC 根,但它永远不会被收集,因此永远不会被销毁。
    • @AdamD.Ruppe 它不能用析构函数来完成,这真的很糟糕。 RAI 成语在这里被打破了。我建议设计一个新的 D 语言特性来解决这个问题
    • 嗯,好吧:为什么要addRootremoveRootsetAttr/clrAttr 似乎适用于任何 GC 指针,而不仅仅是根。所以我建议删除C.addRoot(cast(void*)this);GC.removeRoot(cast(void*)this);。我认为在此之后我的 OO 代码将起作用。对吗?
    • 只有在对象被销毁时才会触发析构函数。 GC 根目录从不 销毁(除非您手动执行此操作)。这就是该功能的全部意义所在!最初的问题是 GC 可能会在 C 持有该块时释放该块,因为 D 的 GC 看不到 C 的内部存储器。通过将它添加为根,它告诉 D 的 GC 不要尝试自动释放它,直到你告诉它它没问题(通过 removeRoot)函数。这可以防止它在 C 仍然持有它时被释放。
    猜你喜欢
    • 1970-01-01
    • 2021-07-22
    • 2012-10-06
    • 2013-06-05
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 2013-06-25
    • 2016-01-12
    相关资源
    最近更新 更多