| Customize Help

Delegates and MIL hook-type functions



Delegates are objects that contain a reference to functions, and as such, they are similar to function pointers in C++. The MIL.NET wrapper declares a several delegate types, and each one for use with a specific MIL hook-type function; MIL hook-type functions are those MIL functions that take a pointer to a user-defined (hook-handler or event-handler) function. When using a MIL hook-type function in .NET, use a delegate instead of a pointer to a user-defined function. The following table lists the MIL hook-type functions and their associated delegate type.

MIL hook-type function

Associated delegate type

MappHookFunction()

MIL_APP_HOOK_FUNCTION_PTR

MbufHookFunction()

MIL_BUF_HOOK_FUNCTION_PTR

MdigHookFunction(), MdigProcess()

MIL_DIG_HOOK_FUNCTION_PTR

MdispHookFunction()

MIL_DISP_HOOK_FUNCTION_PTR

MfuncAlloc()

MFUNCFCTPTR

MocrHookFunction()

MIL_OCR_HOOK_FUNCTION_PTR

MsysHookFunction()

MIL_SYS_HOOK_FUNCTION_PTR

MthrAlloc()

MIL_THREAD_FUNCTION_PTR

MfpgaHookFunction()

MIL_FPGA_HOOK_FUNCTION_PTR

MdigFocus()

MIL_FOCUS_HOOK_FUNCTION_PTR

To create a new instance of a delegate, you must pass a user-defined function, with the proper signature and return type, to the constructor of the delegate object. The following table outlines the proper signature and return type for each type of delegate.

Delegate type

Return type

Parameter types

MIL_APP_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, MIL_ID, IntPtr

MIL_BUF_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, MIL_ID, IntPtr

MIL_DIG_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, MIL_ID, IntPtr

MIL_DISP_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, MIL_ID, IntPtr

MFUNCFCTPTR

void

MIL_ID

MIL_OCR_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, string, IntPtr

MIL_SYS_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, MIL_ID, IntPtr

MIL_THREAD_FUNCTION_PTR

MIL_INT

IntPtr

MIL_FPGA_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, MIL_ID, IntPtr

MIL_FOCUS_HOOK_FUNCTION_PTR

MIL_INT

MIL_INT, MIL_INT, IntPtr

For more information on the requirements of the user-defined function, please refer to the description of the corresponding hook-type function.

Lifetime of delegates passed to MIL hook-type functions

Delegate objects passed to a MIL hook-type function must stay alive as long as the user-defined function is hooked in MIL. This is because MIL needs to keep a pointer to the user-defined function to call it when the specified event occurs. Since out of scope delegates are subject to be collected by the common language runtime's (CLR's) garbage collector, you must ensure that the delegate will be in scope long enough; otherwise, it could result in an Null reference exception when the specified event occurs. Typically, delegates should be declared as members of the class that calls the MIL hook-type function, as in the following example:

using System;
using Matrox.MatroxImagingLibrary;

namespace Delegates.Correct
{
    class MyDigitizer
    {
        private MIL_ID _digitizerId = MIL.M_NULL;
        private MIL_DIG_HOOK_FUNCTION_PTR _myDelegate;

        public MyDigitizer(MIL_ID sysId)
        {
            MIL.MdigAlloc(sysId, MIL.M_DEFAULT, "M_DEFAULT", MIL.M_DEFAULT, ref _digitizerId);
            _myDelegate = new MIL_DIG_HOOK_FUNCTION_PTR(MyHookHandler);
            MIL.MdigHookFunction(_digitizerId, MIL.M_GRAB_START, _myDelegate, MIL.M_NULL);
        }

        public MIL_INT MyHookHandler(MIL_INT hookType, MIL_ID eventId, IntPtr userDataPtr)
        {
            return MIL.M_NULL;
        }
    }
}

The following example demonstrates an easy-to-make error; the delegate object myDel is declared in the scope of the class constructor, as opposed to being declared as a member of the class that calls the MIL hook-type function. When the constructor exits, myDel goes out of scope and will eventually be reclaimed by the garbage collector. When this happens, any further attempts from MIL to call the user-defined function will result in an exception.

using System;
using Matrox.MatroxImagingLibrary;

namespace Delegates.Incorrect
{
    class MyDigitizer
    {
        private MIL_ID _digitizerId = MIL.M_NULL;
        public MyDigitizer(MIL_ID sysId)
        {
            MIL.MdigAlloc(sysId, MIL.M_DEFAULT, "M_DEFAULT", MIL.M_DEFAULT, ref _digitizerId);
            MIL_DIG_HOOK_FUNCTION_PTR myDel = new MIL_DIG_HOOK_FUNCTION_PTR(MyHookHandler);
            MIL.MdigHookFunction(_digitizerId, MIL.M_GRAB_START, myDel, MIL.M_NULL);
        }

        public MIL_INT MyHookHandler(MIL_INT hookType, MIL_ID eventId, IntPtr userDataPtr)
        {
            return MIL.M_NULL;
        }
    }
}

Passing user data to a user-defined function

Some applications might require user data to be available to the user-defined (hook-handler) function. To pass this user data to the user-defined function, and to protect this user data from the CLR's garbage collector, you should encapsulate the user data in an object, and encapsulate this new user data object in a GCHandle object. Note that a pinned GCHandle object is not needed because MIL does not directly reference the memory address of the user data object. The following example demonstrates how to pass user data to a user-defined function through the creation of a user data object encapsulated by a GCHandle object.

using System;
using System.Runtime.InteropServices;
using Matrox.MatroxImagingLibrary;

namespace Delegates.UserData
{
    class MyUserData
    {
        public MIL_INT MyValue;
    }

    class MyDigitizer
    {
        private MIL_ID _digitizerId = MIL.M_NULL;
        private MIL_DIG_HOOK_FUNCTION_PTR _myDelegate;
        private MyUserData _myUserData;
        private GCHandle _userDataHandle;

        public MyDigitizer(MIL_ID sysId)
        {
            MIL.MdigAlloc(sysId, MIL.M_DEFAULT, "M_DEFAULT", MIL.M_DEFAULT, ref _digitizerId);
        }

        public void Hook()
        {
            // Prepare user data object
            _myUserData = new MyUserData();
            _myUserData.MyValue = 10;

            // Allocate a regular GCHandle to inform the garbage collector
            // not to move the object.
            _userDataHandle = GCHandle.Alloc(_myUserData);

            _myDelegate = new MIL_DIG_HOOK_FUNCTION_PTR(MyHookHandler);

            MIL.MdigHookFunction(_digitizerId,
                                 MIL.M_GRAB_START,
                                 _myDelegate,
                                 GCHandle.ToIntPtr(_userDataHandle));
        }

        public MIL_INT MyHookHandler(MIL_INT hookType, MIL_ID eventId, IntPtr userDataPtr)
        {
            if (userDataPtr != IntPtr.Zero)
            {
                // Use this to get the user data object reference from the GCHandle
                GCHandle userDataHandle = GCHandle.FromIntPtr(userDataPtr);
                MyUserData userData = userDataHandle.Target as MyUserData;

                if (userData.MyValue == 10)
                {
                    // do something
                }
            }

            return MIL.M_NULL;
        }

        public void Unhook()
        {
            // Unhook the function in MIL.
            MIL.MdigHookFunction(_digitizerId,
                                 MIL.M_GRAB_START + MIL.M_UNHOOK,
                                 _myDelegate,
                                 GCHandle.ToIntPtr(_userDataHandle));

            // Free the GCHandle when no longer needed
            _userDataHandle.Free();
        }
    }
}

It is not only important to inform the garbage collector not to move an object, it is also important to let the garbage collector know when it can move an object. This is done by releasing the GCHandle object with its Free function. This is shown in the Unhook function of the above example.