Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x
?
Solved

Problem with Callbacks and Threads in C#

Posted on 2009-07-10
3
Medium Priority
?
1,849 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
[X]
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
  • 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

Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

Question has a verified solution.

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

Summary: Persistence is the capability of an application to store the state of objects and recover it when necessary. This article compares the two common types of serialization in aspects of data access, readability, and runtime cost. A ready-to…
Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
This course is ideal for IT System Administrators working with VMware vSphere and its associated products in their company infrastructure. This course teaches you how to install and maintain this virtualization technology to store data, prevent vuln…
Have you created a query with information for a calendar? ... and then, abra-cadabra, the calendar is done?! I am going to show you how to make that happen. Visualize your data!  ... really see it To use the code to create a calendar from a q…

604 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