Link to home
Start Free TrialLog in
Avatar of raheelasadkhan
raheelasadkhanFlag for Pakistan

asked on

Problem with Callbacks and Threads in C#

I'm having a problem with a callback function that is triggered from within a child thread.

There are two very simple classes. The Monitor class launches a thread which in turn calls the GetFrame method of the Device class. The GetFrame method sends a message to VFW and requires a callback function to receive an image from the webcam.

Everything works fine but sometimes, when the Monitor.Stop() method is called, the application never exits. I suspect this has to do with the callback function because of two reasons:

1. The Monitor class is thoroughly tested and has no errors
2. This problem was not there before I implemented the callback

I'm not sure but one potential problem could be that the callback function passes an image which is being used in managed code on the main thread. But since this problem happens only some of the time, it could be something else.
namespace Monitor
{
    public class Program
    {
        private static Monitor _Monitor = new Monitor();
 
        [System.STAThread]
        public static void Main()
        {
            _Monitor = new Monitor();
 
            _Monitor.Device.DeviceFrameCaptured += new Device.DeviceFrameCapturedEventHandler(Device_DeviceFrameCaptured);
 
            _Monitor.Start();
 
            while (true)
            {
                // Sleep for 10 seconds allowing the Monitor thread to capture images at 2 second intervals
                //  Of course I use a form here in my code and _Monitor.Stop() is called from the FormClosing event
                System.Threading.Thread.Sleep(10000);
 
                break;
            }
 
            _Monitor.Stop();
 
            System.Windows.Forms.MessageBox.Show("This point is sometimes never reached");
        }
 
        private static void Device_DeviceFrameCaptured(object sender, DeviceFrameCapturedEventArgs e)
        {
            // This callback function saves the image to the desktop
            string filename = "";
            System.Drawing.Image image = null;
 
            filename
                = e.DateTime.ToString("yyyyMMddHHmmssfff")
                + "."
                + e.DateTime.Ticks.ToString().PadLeft(long.MaxValue.ToString().Length, '0')
                + ".jpg"
                ;
            filename = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop), filename);
 
            image = new System.Drawing.Bitmap(e.Image);
 
            image.Save(filename, System.Drawing.Imaging.ImageFormat.Jpeg);
        }
    }
 
    public class Monitor
    {
 
        //==================================================
        // Members
        //==================================================
 
        private bool _Running = false;
        private bool _Terminate = true;
        private System.Threading.Thread _ThreadMonitor = null;
 
        private Device _Device = new Device();
 
        ~Monitor()
        {
            this.Stop();
        }
 
        //==================================================
        // Properties
        //==================================================
 
        public bool Running
        {
            get
            {
                return (this._Running);
            }
        }
 
        public Device Device
        {
            get
            {
                return (this._Device);
            }
        }
 
        //==================================================
        // Functions
        //==================================================
 
        public void Start()
        {
            this.Stop();
 
            this._Running = true;
            this._Terminate = false;
 
            this._ThreadMonitor = new System.Threading.Thread(new System.Threading.ThreadStart(this.ThreadMonitor));
 
            this._ThreadMonitor.Start();
        }
 
        public void Stop()
        {
            this._Terminate = true;
 
            if (this._ThreadMonitor != null)
            {
                try
                {
                    this._ThreadMonitor.Join();
 
                    this._ThreadMonitor = null;
                }
                catch
                {
                    try
                    {
                        this._ThreadMonitor.Abort();
                    }
                    catch
                    {
                        // Do nothing
                    }
                }
 
                this._ThreadMonitor = null;
            }
 
            this._Running = false;
        }
 
        /// <summary>
        /// Main monitor thread
        /// </summary>
        private void ThreadMonitor()
        {
            this.Device.Connect();
 
            while (!this._Terminate)
            {
                this.Device.GetFrame();
 
                // Sleep for 2 seconds
                System.Threading.Thread.Sleep(2000);
            }
 
            this.Device.Disconnect();
        }
 
    }
 
    public class Device
    {
 
        //==================================================
        // Win32 API Declarations: Constants, Enumerations & Structures
        //==================================================
 
        public const int WM_USER = 0x0400;
        public const int WM_CAP_START = WM_USER;
        public const int WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
        public const int WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
        public const int WM_CAP_SET_PREVIEW = WM_CAP_START + 50;
        public const int WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
        public const int WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5;
        public const int WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44;
 
        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public struct BITMAPINFOHEADER
        {
            public uint biSize;
            public int biWidth;
            public int biHeight;
            public ushort biPlanes;
            public ushort biBitCount;
            public uint biCompression;
            public uint biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public uint biClrUsed;
            public uint biClrImportant;
        }
 
        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public struct BITMAPINFO
        {
            public BITMAPINFOHEADER bmiHeader;
            public int bmiColors;
        }
 
        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public struct VIDEOHDR
        {
            public System.IntPtr lpData;    // Pointer to locked data buffer
            public uint dwBufferLength;     // Length of data buffer
            public uint dwBytesUsed;        // Bytes actually used
            public uint dwTimeCaptured;     // Milliseconds from start of stream
            public uint dwUser;             // For client's use
            public uint dwFlags;            // Assorted flags (see defines)
            [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.SafeArray)]
            byte[] dwReserved;              // Reserved for driver
        }
 
        //==================================================
        // Win32 API Declarations: Functions
        //==================================================
 
        [System.Runtime.InteropServices.DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA")]
        public static extern int capCreateCaptureWindowA(string lpszWindowName, int dwStyle, int X, int Y, int nWidth, int nHeight, int hwndParent, int nID);
 
        [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessage")]
        public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
 
        // Use with WM_CAP_SET_CALLBACK_FRAME
        [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessage")]
        public static extern int SendMessage(int hWnd, uint wMsg, int wParam, DelegateCallbackGetFrame lParam);
 
        // Use with WM_CAP_GET_VIDEOFORMAT
        [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessage")]
        public static extern int SendMessage(int hWnd, uint wMsg, int wParam, ref BITMAPINFO bitmapInfo);
 
        //==================================================
        // Members
        //==================================================
 
        private int _CaptureWindowHandle = 0;
        private System.Windows.Forms.Form _FormWebcam = null;
        private BITMAPINFO _BitmapInfo = new BITMAPINFO();
 
        private int _ImageWidth = 0;
        private int _ImageHeight = 0;
        private int _ImageStride = 0;
        private System.Drawing.Imaging.PixelFormat _ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Undefined;
 
        public delegate void DeviceFrameCapturedEventHandler(object sender, DeviceFrameCapturedEventArgs e);
        public event DeviceFrameCapturedEventHandler DeviceFrameCaptured = null;
 
        // Callback function for Webcam Frame Capture
        public delegate void DelegateCallbackGetFrame(System.IntPtr hwnd, ref VIDEOHDR videoHeader);
        private DelegateCallbackGetFrame _CallbackGetFrame = null;
 
        //==================================================
        // Functions
        //==================================================
 
        public Device()
        {
            this._CaptureWindowHandle = 0;
            this._FormWebcam = new System.Windows.Forms.Form();
 
            this._CallbackGetFrame = this.CallbackGetFrame;
        }
 
        public void Connect()
        {
            // Connect to the capture device
            this._CaptureWindowHandle = capCreateCaptureWindowA("", 0, 0, 0, 0, 0, this._FormWebcam.Handle.ToInt32(), 0);
 
            // Connect to driver
            SendMessage(this._CaptureWindowHandle, WM_CAP_DRIVER_CONNECT, 0, 0);
            // Set preview to false
            SendMessage(this._CaptureWindowHandle, WM_CAP_SET_PREVIEW, 0, 0);
 
            // Get Video Format so that the image in the callback function
            //  can be properly converted to a System.Drawing.Bitmap object.
            this._BitmapInfo = new BITMAPINFO();
            SendMessage
                (
                this._CaptureWindowHandle,
                WM_CAP_GET_VIDEOFORMAT,
                System.Runtime.InteropServices.Marshal.SizeOf(this._BitmapInfo),
                ref this._BitmapInfo
                );
 
            this._ImageWidth = this._BitmapInfo.bmiHeader.biWidth;
            this._ImageHeight = this._BitmapInfo.bmiHeader.biHeight;
            this._ImageStride = this._BitmapInfo.bmiHeader.biBitCount * this._BitmapInfo.bmiHeader.biWidth / 8;
 
            switch (this._BitmapInfo.bmiHeader.biBitCount)
            {
                case 0:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Undefined;
 
                        break;
                    }
                case 1:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Format1bppIndexed;
 
                        break;
                    }
                case 4:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Format4bppIndexed;
 
                        break;
                    }
                case 8:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Format8bppIndexed;
 
                        break;
                    }
                case 16:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Format16bppRgb555;
 
                        break;
                    }
                case 24:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
 
                        break;
                    }
                case 32:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppRgb;
 
                        break;
                    }
                default:
                    {
                        this._ImagePixelFormat = System.Drawing.Imaging.PixelFormat.Undefined;
 
                        break;
                    }
            }
        }
 
        public void GetFrame()
        {
            // Grab current frame
            SendMessage(this._CaptureWindowHandle, WM_CAP_GRAB_FRAME, 0, 0);
 
            // Set callback
            SendMessage(this._CaptureWindowHandle, WM_CAP_SET_CALLBACK_FRAME, 0, this._CallbackGetFrame);
 
            // Copy data to clipboard
            //  SendMessage(this._CaptureWindowHandle, WM_CAP_EDIT_COPY, 0, 0);
        }
 
        public void Disconnect()
        {
            SendMessage(this._CaptureWindowHandle, WM_CAP_DRIVER_DISCONNECT, 0, 0);
        }
 
        public void CallbackGetFrame(System.IntPtr hwnd, ref VIDEOHDR videoHeader)
        {
            if (this.DeviceFrameCaptured != null)
            {
                System.Drawing.Bitmap image = new System.Drawing.Bitmap
                    (
                    this._ImageWidth,
                    this._ImageHeight,
                    this._ImageStride,
                    this._ImagePixelFormat,
                    videoHeader.lpData
                    );
 
                this.DeviceFrameCaptured(this, new DeviceFrameCapturedEventArgs(System.DateTime.Now, image, ""));
            }
        }
 
    }
 
    public class DeviceFrameCapturedEventArgs
    {
 
        private string _Filename = "";
        private System.DateTime _DateTime = System.DateTime.MinValue;
        private System.Drawing.Image _Image = null;
 
        public string Filename
        {
            get
            {
                return (this._Filename);
            }
        }
 
        public System.DateTime DateTime
        {
            get
            {
                return (this._DateTime);
            }
        }
 
        public System.Drawing.Image Image
        {
            get
            {
                return (this._Image);
            }
        }
 
        public DeviceFrameCapturedEventArgs()
            : this(System.DateTime.MinValue, null, "")
        {
        }
 
        public DeviceFrameCapturedEventArgs(System.DateTime dateTime, System.Drawing.Image image, string filename)
        {
            this._DateTime = dateTime;
            this._Image = image;
            this._Filename = filename;
        }
 
    }
}

Open in new window

Avatar of topdog770
topdog770
Flag of United States of America image

Thank you for attaching a Code Snippet that compiles and runs!!
I suspect that you are correct regarding the bitmap.  Bitmap's hang around until nothing references them, and THEN, they persist until the Garbage collection occurs.   The bitmap being created in the callback maybe the problem.. just looking at the code for  a couple of minutes, I don't ever see the callback function firing.
Is there something else that's required?
 
ASKER CERTIFIED SOLUTION
Avatar of raheelasadkhan
raheelasadkhan
Flag of Pakistan image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
That would do it too.
using your closing flag and only doing the Form.Invoke when closing hasn't been set to true, should solve that issue.