Your New Jekyll Site

home

MDA CallbackOnCollectedDelegate

18 Dec 2011

One of the MDAs enabled by default is the CallbackOnCollectedDelegate, this MDA is for interop code. Delegates don´t get garbage collected in purely Managed code. However if you mix managed and unmanaged code, the Garbage collector can prematurely mark an object as unused and collectable, even though there still might exist references to it from unmanaged code.

Code Example

public delegate void ResultCallBackType(int result);

[DllImport("UnmanagedLib.dll",
    SetLastError = true,
    CallingConvention = CallingConvention.StdCall)]
public static extern
void RegisterCallback(ResultCallBackType callback, int delayMs);

public void PrintResultCallback(int result)
{
    string message = string.Format("Result = {0}", result);
    System.Console.WriteLine(message);
}

static void Main(string[] args)
{
    int iterations = int.Parse(args[0]);
    Program prg = new Program();

    // Register a callback to be called in 5000 ms
    RegisterCallback(prg.PrintResultCallback, 5000);

    // At this point object prg is not used any more
    // and is marked as collectable
    //
    // and the prg object run the risk of being collected
    // before the callback arrives.
    System.Console.ReadLine();
}

In the code above managed code registers a function pointer (callback) in unmanaged code, then the execution continues. At some point the object where the callback exists falls out of scope. Most of the time the code might work, and in a few cases it might result in a crash. It depends on if the garbage collector runs or not.

The garbage collector can manually be triggered by inserting code immediately before the call to the System.Console.Readline()

GC.Collect();
GC.WaitForPendingFinalizers();

The GC can also be indirectly triggered by allocations. Running the code below with typically 500 iterations causes a crash in my system due to garbage collection.

var memList = new List();
for (int i = 0; i < iterations; i++)
{
    var memAlloc = new byte[10000];
    memList.Add(memAlloc);
    System.Threading.Thread.Sleep(10);
}

Image of windbg

The correct symbol files, will help the debugger to map the faulting address to a line number in your code. The word “debug” in Debug build is misleading, since you also can debug Release builds. This is useful to know if you ever encounter an error that just appears on the release build. You might encounter small jumps here and there, where the code optimizer has changed the control flow, but that usually doesn’t cause big problems.

Below, we see the visual studio debugger during a debugging session. It displays the correct line, because the underlying instruction is located on a address which can be mapped to a line in the source code.

Image of windbg