Solved

Problem with Callbacks and Threads in C#

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

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

Suggested Solutions

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 introduced a TextBox that supports transparent background.   Introduction TextBox is the most widely used control component in GUI design. Most GUI controls do not support transparent background and more or less do not have the…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…

757 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

21 Experts available now in Live!

Get 1:1 Help Now