Solved

Problem with Callbacks and Threads in C#

Posted on 2009-07-10
3
1,818 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

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

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

Article by: Najam
Having new technologies does not mean they will completely replace old components.  Recently I had to create WCF that will be called by VB6 component.  Here I will describe what steps one should follow while doing so, please feel free to post any qu…
Introduction This article series is supposed to shed some light on the use of IDisposable and objects that inherit from it. In essence, a more apt title for this article would be: using (IDisposable) {}. I’m just not sure how many people would ge…
This Micro Tutorial will teach you how to censor certain areas of your screen. The example in this video will show a little boy's face being blurred. This will be demonstrated using Adobe Premiere Pro CS6.
Video by: Mark
This lesson goes over how to construct ordered and unordered lists and how to create hyperlinks.

863 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

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now