Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people, just like you, are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
Solved

Problem with Callbacks and Threads in C#

Posted on 2009-07-10
3
1,824 Views
Last Modified: 2012-08-14
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

0
Comment
Question by:raheelasadkhan
  • 2
3 Comments
 
LVL 12

Expert Comment

by:topdog770
ID: 24844094
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?
 
0
 

Accepted Solution

by:
raheelasadkhan earned 0 total points
ID: 24844315
Thanks for the input. The main thread launches the monitor thread which in turn calls the GetFrame() function. This function has the following line:

SendMessage(this._CaptureWindowHandle, WM_CAP_SET_CALLBACK_FRAME, 0, this._CallbackGetFrame);

this._CallbackGetFrame is bieng assigned in the Device constructor and of course the main thread adds the following event:

_Monitor.Device.DeviceFrameCaptured += new Device.DeviceFrameCapturedEventHandler(Device_DeviceFrameCaptured);

Basically the code runs perfectly well and does what it's supposed to do. The callback function does get called. If you run the code with a standard webcam attached, the app should start taking snaps and save the files to your desktop.

I think I have another solution. In my application, the main thread is a form and when the form is closed by the user, I tried to use the Form.Invoke method to update the log text on the same form. That prevented the event procedure to exit presumably because the Invoke method got stuck.

I only noticed this after converting the code in a simple format to post here. Do let me know if you see any problems with it.
0
 
LVL 12

Expert Comment

by:topdog770
ID: 24855004
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.
0

Featured Post

Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
About delegates in c sharp 3 57
Question! 4 38
.NET 2008 VB and C# 6 39
length of the password hash sha1:64000 to set sql field property. 13 65
Introduction Although it is an old technology, serial ports are still being used by many hardware manufacturers. If you develop applications in C#, Microsoft .NET framework has SerialPort class to communicate with the serial ports.  I needed to…
This article aims to explain the working of CircularLogArchiver. This tool was designed to solve the buildup of log file in cases where systems do not support circular logging or where circular logging is not enabled
Although Jacob Bernoulli (1654-1705) has been credited as the creator of "Binomial Distribution Table", Gottfried Leibniz (1646-1716) did his dissertation on the subject in 1666; Leibniz you may recall is the co-inventor of "Calculus" and beat Isaac…
Nobody understands Phishing better than an anti-spam company. That’s why we are providing Phishing Awareness Training to our customers. According to a report by Verizon, only 3% of targeted users report malicious emails to management. With compan…

791 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question