dragonfly
dragonfly

Reputation: 17773

Passing managed callback to DllImport (ed) function

I had a piece of code that throwed exception with respect garbage collected delegate being called by unmanaged function. This is the code:

// Setup Callback functions
errorCode = gsapi_set_stdio(ghostScriptPtr, new StdioMessageEventHandler(RaiseStdInCallbackMessageEvent), new StdioMessageEventHandler(RaiseStdOutCallbackMessageEvent), new StdioMessageEventHandler(RaiseStdErrCallbackMessageEvent));

if (errorCode >= 0)
{
    try
    {
        //GC.SuppressFinalize(this);
        // Init the GhostScript interpreter
        errorCode = gsapi_init_with_args(ghostScriptPtr, commandParameters.Length, commandParameters);

        // Stop the Ghostscript interpreter
        gsapi_exit(ghostScriptPtr);
    }
    finally
    {
        // Release the Ghostscript instance handle
        gsapi_delete_instance(ghostScriptPtr);
    }
}

_Raise... variables passed to function are being disposed before being called by the function.

I don't know what occurred to me, but I changed the callbacks into variables:

var _RaiseStdInCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdInCallbackMessageEvent);
var _RaiseStdOutCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdOutCallbackMessageEvent);
var _RaiseStdErrCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdErrCallbackMessageEvent);
// Setup Callback functions
errorCode = gsapi_set_stdio(ghostScriptPtr, _RaiseStdInCallbackMessageEventHandler, _RaiseStdOutCallbackMessageEventHandler, _RaiseStdErrCallbackMessageEventHandler);

and finally block to:

    finally
    {
        // Release the Ghostscript instance handle
        gsapi_delete_instance(ghostScriptPtr);
        _RaiseStdInCallbackMessageEventHandler = _RaiseStdOutCallbackMessageEventHandler = _RaiseStdErrCallbackMessageEventHandler = null;
    }

and it fixed the issue. Why? I don't know. Perhaps it's just a coincidence. I have a gut feeling that using variables in finally block resulted in not disposing variable's object to early (because it's used in finally block). Is that true? Anyway, is it a correct approach to provide dllimported function with managed callbacks?

Thanks,Pawel

Upvotes: 2

Views: 993

Answers (2)

Hans Passant
Hans Passant

Reputation: 941585

Yes, you're on the right track with this. Not quite. The garbage collector cannot know that the native code has a reference to the delegate. It is buried in a thunk generated by Marshal.GetFunctionPointerForDelegate(), out of reach from the GC. It is therefore necessary that you have another reference to the delegate, one that the GC can see.

You did this partially by using the local variable, the GC also walks the stack and CPU registers and can see the delegate reference. However, this goes wrong when you run your code in Release mode without a debugger. With a debugger attached, the jitter reports the life time of local variables until the end of the method. Which makes debugging a lot easier. It no longer does so without a debugger. Not even when setting the variable to null in the finally block, the jitter optimizer removes that assignment. The optimizer is enabled in the Release build.

Best thing to do is to store the delegate references in a field of your class. And make sure your class object lives long enough to outlast a callback. Next best thing is to use GC.KeepAlive() on the local variables.

Upvotes: 4

Alex F
Alex F

Reputation: 43311

Your post doesn't contain all required information to understand what happens: unmanaged function prototype, PInvoke declarations. Anyway, looking at the question title, I see that you need to use Marshal.GetFunctionPointerForDelegate Method - this is the way to pass managed callback to unmanaged code.

Upvotes: 0

Related Questions