Garbage Collector and Delegate

G

Guest

I am registering a managed delegate to an unmanaged DLL for asynchronous
callback. I understand the garbage collector will free the managed resource
if they go out of scope but with the information I have read, it is unclear
to me if the garbage collector will move the delegate (e.g.
cableInterruptCallbackEventHandler) if it is still referenced thereby
invalidating the callback performed by the unmanaged code. I would greatly
appreciate some information regarding this topic. I have included some code
that will illustrate what I am doing. In my actual code I am implementing
the Dispose Pattern. Sorry, it is a bit lengthy.

[btapi.h] (partial)
#ifdef __cplusplus
#define BT_EXTERN extern "C"
#else
#define BT_EXTERN extern
#endif

#if !defined(BT973) && !defined(WINVER) && !defined(BT15901) \
&& !defined(BT15991) && !defined(BT17903) && !defined(BT13908)
#define BT_METHOD
#else
/* Windows requires __stdcall between the return type and the function
name- so we can't just hide it in BT_EXTERN. */
#define BT_METHOD __stdcall
#endif

BT_EXTERN bt_error_t
BT_METHOD bt_icbr_install(
bt_desc_t btd_p,
bt_irq_t irq_type,
bt_icbr_t *icbr_p,
void *param_p,
bt_data32_t vector);

BT_EXTERN bt_error_t
BT_METHOD bt_icbr_remove(
bt_desc_t btd_p,
bt_irq_t irq_type,
bt_icbr_t *icbr_p);


[C#]
using System;
using System.Runtime.InteropServices;

namespace SbsTechnologies.BusAdapter
{
public enum ErrorStatus : int
{
Success,
IOError,
DeviceDoesNotExist,
OutOfMemory,
}

public enum InterruptType : int
{
Overflow,
Error,
Programmed,
Cable,
}

public enum InterruptCallbackType : int
{
Error = InterruptType.Error,
Programmed = InterruptType.Programmed,
Cable = InterruptType.Cable,
}

public class MirrorApiEventArgs : EventArgs
{
private InterruptType interruptType;
private uint vector;

public MirrorApiEventArgs(InterruptType interruptType, uint vector) : base()
{
this.interruptType = interruptType;
this.vector = vector;
}

public InterruptType InterruptType
{
get
{
return this.interruptType;
}
}

public uint Vector
{
get
{
return this.vector;
}
}
}

internal delegate void InterruptCallbackEventHandler(IntPtr parameter,
InterruptType interruptType, uint vector);

public delegate void MirrorApiEventHandler(object sender,
MirrorApiEventArgs e);

internal sealed class MirrorApiInterop
{
private sealed class UnsafeNativeMethods
{

[DllImport(@"bit3_984.dll", EntryPoint = "bt_icbr_install")]
public static extern ErrorStatus bt_icbr_install(
IntPtr btd_p,
InterruptCallbackType irq_type,
InterruptCallbackEventHandler icbr_p,
IntPtr param_p,
uint vector
);

[DllImport(@"bit3_984.dll", EntryPoint = "bt_icbr_remove")]
public static extern ErrorStatus bt_icbr_remove(
IntPtr btd_p,
InterruptCallbackType irq_type,
InterruptCallbackEventHandler icbr_p
);
}

internal static void InstallInterruptCallbackRoutine(IntPtr handle,
InterruptCallbackType interruptCallbackType, InterruptCallbackEventHandler
interruptCallbackEventHandler, IntPtr param, uint vector)
{
UnsafeNativeMethods.bt_icbr_install(handle, interruptCallbackType,
interruptCallbackEventHandler, param, vector);
}

internal static void RemoveInterruptCallbackRoutine(IntPtr handle,
InterruptCallbackType interruptCallbackType, InterruptCallbackEventHandler
interruptCallbackEventHandler)
{
UnsafeNativeMethods.bt_icbr_remove(handle, interruptCallbackType,
interruptCallbackEventHandler);
}
}

public class MirrorApiInterrupt
{
private IntPtr handle;
private uint vector;

private InterruptCallbackEventHandler cableInterruptCallbackEventHandler;
private InterruptCallbackEventHandler errorInterruptCallbackEventHandler;
private InterruptCallbackEventHandler
programmedInterruptCallbackEventHandler;

public event MirrorApiEventHandler Cable;
public event MirrorApiEventHandler Error;
public event MirrorApiEventHandler Programmed;

public MirrorApiInterrupt(IntPtr handle, uint vector)
{
this.handle = handle;
this.vector = vector;

cableInterruptCallbackEventHandler = new
InterruptCallbackEventHandler(this.CableInterruptCallback);
errorInterruptCallbackEventHandler = new
InterruptCallbackEventHandler(this.ErrorInterruptCallback);
programmedInterruptCallbackEventHandler = new
InterruptCallbackEventHandler(this.ProgrammedInterruptCallback);

MirrorApiInterop.InstallInterruptCallbackRoutine(this.handle,
InterruptCallbackType.Cable, cableInterruptCallbackEventHandler, IntPtr.Zero,
this.vector);
MirrorApiInterop.InstallInterruptCallbackRoutine(this.handle,
InterruptCallbackType.Error, errorInterruptCallbackEventHandler, IntPtr.Zero,
this.vector);
MirrorApiInterop.InstallInterruptCallbackRoutine(this.handle,
InterruptCallbackType.Programmed, programmedInterruptCallbackEventHandler,
IntPtr.Zero, this.vector);
}

~MirrorApiInterrupt()
{
MirrorApiInterop.RemoveInterruptCallbackRoutine(this.handle,
InterruptCallbackType.Cable, cableInterruptCallbackEventHandler);
}

private void CableInterruptCallback(IntPtr parameter, InterruptType
interruptType, uint vector)
{
this.OnCable(new MirrorApiEventArgs(interruptType, vector));
}

private void ErrorInterruptCallback(IntPtr parameter, InterruptType
interruptType, uint vector)
{
this.OnError(new MirrorApiEventArgs(interruptType, vector));
}

private void ProgrammedInterruptCallback(IntPtr parameter, InterruptType
interruptType, uint vector)
{
this.OnProgrammed(new MirrorApiEventArgs(interruptType, vector));
}

protected virtual void OnCable(MirrorApiEventArgs e)
{
if (this.Cable != null)
{
this.Cable(this, e);
}
}

protected virtual void OnError(MirrorApiEventArgs e)
{
if (this.Error != null)
{
this.Error(this, e);
}
}

protected virtual void OnProgrammed(MirrorApiEventArgs e)
{
if (this.Programmed != null)
{
this.Programmed(this, e);
}
}
}
}
 
M

Mattias Sjögren

Daniel,
I understand the garbage collector will free the managed resource
if they go out of scope but with the information I have read, it is unclear
to me if the garbage collector will move the delegate (e.g.
cableInterruptCallbackEventHandler) if it is still referenced thereby
invalidating the callback performed by the unmanaged code. I would greatly
appreciate some information regarding this topic.

The delegate may move, but the native function pointer that gets
passed to the function you're calling will remain valid as long as the
delegate is referenced.

In my actual code I am implementing the Dispose Pattern.

Great, because when your ~MirrorApiInterrupt finalizer runs the
delegate may already have been garbage collected, so you can't safely
call RemoveInterruptCallbackRoutine from there.



Mattias
 
G

Guest

Thanks for the help. Your time is appreciated.

Mattias Sjögren said:
Daniel,


The delegate may move, but the native function pointer that gets
passed to the function you're calling will remain valid as long as the
delegate is referenced.



Great, because when your ~MirrorApiInterrupt finalizer runs the
delegate may already have been garbage collected, so you can't safely
call RemoveInterruptCallbackRoutine from there.



Mattias
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top