Search code examples
d

How do I allocate a delegate on the heap?


How do I heap allocate a delegate and get a pointer to it?

I want to be able to cast it to a void pointer and back, so I can store it in a heterogeneous array.


Solution

  • a delegate in D, similar to a dynamic array, is a simple struct with 2 pointer fields. It has the fields:

    • .ptr which is the context pointer (this reference, closure, stack frame pointer)
    • .funcptr which is the function pointer

    So, if you can absolutely make sure the context pointer won't be destroyed when you call your serialized delegate (best on a class this reference, meaning most commonly a member function of a class), you can just store a struct on the heap with these 2 void* values or make a copy of its bytes and then afterwards deserialize it back to a delegate.

    Example:

    alias MyDelegate = void delegate();
    
    void main()
    {
        int local;
        MyDelegate dlg = () { import std.stdio; writeln("delegate ", local); };
    
        // T.sizeof gives the size of a variable on the stack, so we get the size
        // of the delegate summing together context pointer and function pointer
        // (and potentially additional ABI things depending on the compiler/target)
        // `.dup` copies the bytes from the stack stored for the delegate to the
        // heap, but it doesn't pin the context pointer or anything similar which
        // would however make this a safer operation.
        auto heap = (cast(ubyte*)&dlg)[0 .. MyDelegate.sizeof].dup.ptr;
    
        local = 5;
    
        callHeapDlg(heap);
    }
    
    void callHeapDlg(ubyte* heap)
    {
        // This could be a potentially unsafe cast if casting to some other delegate!
        // You might be casting away safety attributes or other things. Best to
        // restrict the type of the delegate you convert to heap from the start.
        // Simply recreates the delegate by casting a pointer to the bytes you have
        // copied to a pointer to a delegate object and then dereferencing it.
        // This copies the pointers from the heap back to the stack here.
        auto dlg = *(cast(MyDelegate*) heap);
    
        dlg(); // prints "delegate 5"
    }
    

    Try it online

    But never forget that this is an unsafe operation which you could potentially corrupt your application with and even accidentally introduce arbitrary code execution. Especially if your delegate context pointer goes out of scope and gets deallocated you can get major undebugable problems.

    In this case for example you shouldn't call the heap allocated delegate outside the main function because it most likely contains a pointer to the current stack frame of the main function which gets invalid after leaving it.

    See https://dlang.org/spec/abi.html#delegates