none
Invoking a function to run in main thread, from another thread. Help Please RRS feed

  • Question

  • Hello Gurus!

    I am writing a simple windows forms media player program, consisting of a ListView which acts as a playlist, and DirectShow which plays the files. It all works fine so far, i select the file from the ListView to play, hit play and it runs. I can stop and play another file so that is all working good.

    Now however i am implementing continuous play, so when one file finishes, the program will continue to play the next file in the ListView list. 

    I have a play(string fName) function which plays the file, given the file name. This function hooks a custom event and creates a new event waiting thread which, when a EventCode.Compelte is recieved, triggers the custom event.

    Event hooking code in player(string fName):

     

    myPlayer.StopPlay += new player.PlayerEvent(finishedPlaying);

     

    So when this event is triggered, the finishedPlaying function is called. This function checks whether to continue playing the next file, or to just stop.

    In the case where i want to continue playing the next file, i would like to call the play() function with the new filename. However as I believe that the finishedPlaying function is running in the event waiting thread, i need to somehow invoke this play() function in the main thread, from the event waiting thread.

    Below is the code that i have attempted to use, however when mainDispatcher.Invoke is called, it just locks up the main thread and i am unable to press any buttons or anything.

     

            Dispatcher mainDispatcher;
            // Delegate to be used by dispatcher to call the play function within the main thread, from another thread
            delegate void playCallBack(string fName);
    	public frmMain()
            {
                InitializeComponent();
                InitializeModes();
                mainDispatcher = Dispatcher.CurrentDispatcher;
            }
            private void finishedPlaying(Object sender)
            {
                // If we are in continuous mode and this is not the last track, play the next one
                if (mode.continuous && (mode.currentIndex < lvdNdPlayList.Items.Count -1) )
                {
                    ListViewItem lvItem = getFileNameSafe(++mode.currentIndex);
                    string fName = lvItem.SubItems[1].Text + "\\" + lvItem.SubItems[0].Text;
                    // Set the new state to stopped, as the file has finished playing
                    state = playerState.Stopped;
                    // Instantiate the delegate to call play() in main thread from this thread
                    playCallBack playSafe = play;
                    // Now use the dispatcher to call the delegate safely form main thread
                    mainDispatcher.Invoke(playSafe, fName);
                    //play(fName);
                }
                else
                {
                    // irrelevant working code
                }
            }


    This is a little above my head as i am just learning to use multithreading. Any help would be greatly appreciated.

     

    Thanks,

    Chris

     

     

     


    • Edited by HeZzA Thursday, January 19, 2012 10:45 AM Neater and corrected Typo
    • Moved by Dummy yoyoModerator Monday, January 23, 2012 7:40 AM (From:Visual C# General)
    Thursday, January 19, 2012 10:42 AM

All replies

  • First of all,  are you sure your plan function is running in a separate thread instead of GUI thread?

    On a winform application , here is what I will do.

    I initialise a new thread to run the Dotask function, inside of it , while the DDD function will be executed on GUI thread. 

           public delegate void doIt();

     

            private void button1_Click(object sender, EventArgs e)

            {

                ThreadPool.QueueUserWorkItem(new WaitCallback(this.Dotask));

            }

     

            private void Dotask(object state)

            {

                if(this.button1.InvokeRequired)

                {

                    this.button1.Invoke(new doIt(this.DDD));

                }

            }

            private void DDD()

            {

                this.button1.Enabled = false;

            }


    Johnny
    Thursday, January 19, 2012 1:00 PM
  • Hi johnny thanks for the reply.

    I think i understand whats going in there.

    I use similar code to access controls form the second thread. (change the text of buttons or obtain information from the listviews). All of the controls have an invokeRequired associated to them, however my function doesn't.

    What your saying is that i can use the control.Invoke method to execute the delegate on whatever thread the control exists in?

    In that case I could just use the InvokeRequired and Invoke method of any arbitrary control that exists in my application to achieve this?

    In responce to the question

    "First of all,  are you sure your plan function is running in a separate thread instead of GUI thread?"

    The answer is i don't know. I just assume so because when I simply put play(fName) in the finishedPlaying function, when the thread returns to the event loop, it reaches the line:

     

    mediaEvent.FreeEventParams(ecode, ptr1, ptr2);

    and crashes with the error "COM object that has been separated from its underlying RCW cannot be used."

     

    Is there some way to see in debugger which threads are running what?

    Adding the following code to my finished playing function results in the same problem, the UI locks up:

     

     if (this.btnPlay.InvokeRequired)
                    {
                        this.btnPlay.Invoke(new playCallBack(this.play), new object[] { fName });
                    }


    EDIT---

    Following the code through, the UI locks up at the start of a lock (this) block, which is required for changing some asyncronous graph state variables. 

    This code block is always called with the play() function when changing filenames, however only locks up when the play() function is called from the finishedPlaying() function (ie using dispatcher method or the invoke method stated in previous posts)

    Perhaps this tells you something?

     

    Thanks

    Chris 

     


    • Edited by HeZzA Thursday, January 19, 2012 3:19 PM
    Thursday, January 19, 2012 2:16 PM
  • bump 0o
    Saturday, January 21, 2012 12:22 AM
  • Hello HeZzA,

    1. >> I have a play(string fName) function which plays the file, given the file name. This function hooks a custom event and creates a new event waiting thread which, when a EventCode.Compelte is recieved, triggers the custom event...

    >> myPlayer.StopPlay += new player.PlayerEvent(finishedPlaying);

    1.1 I gather that the "custom event" that you mentioned in "This function hooks a custom event ..." is actually the StopPlay event of "myPlayer". Is this correct ?

     

    2. >> So when this event is triggered, the finishedPlaying function is called...

    2.1 Is it not possible that when the finishedPlaying function is called by the myPlayer control/object, it is actually invoked inside the main UI thread of your WinForm application ?

    2.2 This seems likely. What is the control/object that you are using ? If it is an ActiveX, this is highly possible.

    2.3 You can use the "Threads" viewer during debugging to confirm this (menu sequence : Debug|Windows|Threads or Ctrl+Alt+H).

    2.4 If so, you can already call the play() function inside the main UI thread.

     

    3. I think alot depends on the nature of the "myPlayer" control/object and its documentation. What specific control/object is it ? How does it fire its StopPlay event. What does the documentation say ? Does it say that the event may be fired from some other thread besides the main UI thread ?

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Saturday, January 21, 2012 1:54 AM
  • Hi Bio

    1.1 - Yes

    As for the rest I am not too sure what you mean by the nature of the "myPlayer" control/object.

    It is just a class that i have written which has the required methods to run my player.

    I have had a look through the thread viewer with the three methods that i have attempted to use. 

    Method 1: Using Dispatcher - This locks up the UI at the lock function. This lock is inside the CloseInterfaces() function which is called by the play() function when it detects that the file trying to be played is of a different filename to the one currently rendered. Hence the current player is killed and a new one is created. But when the lock(this) is called within the CloseInterfaces(), the ui locks up and nothing more happens.

    Method 2: Using Invoke as suggested by Johnny - This results in the same problem as error 1

    Method 3: Calling play(filename) directly from the finishedPlaying function - This method causes an error: "COM object that has been separated from its underlying RCW cannot be used." In the EventWait function at the line

     

    HR = mediaEvent.FreeEventParams(ecode, ptr1, ptr2);

     

    I believe this is when the StopPlay(this); returns. According to the thread manager, there are two "Event Waiting" threads running at this point.

     

    The first two methods do not generate duplicate threads, but this is likely because they stop at the lock(this) and a new player class is never created.

    I will include a copy of the relevent part of my code, it seems method 3 is the one that is correct and needs refining.. The code for method one and two have also been included, commented.

     

     

    public partial class frmMain : Form
    {
        delegate void playCallBack(string fName);
        // Dispatcher mainDispatcher = Dispatcher.CurrentDispatcher; --- This locks up the main ui when run ---
    
    	public void play(string fName)
    	{   // Irrelevent code
    
            /* This code also resulst in UI locking up
            if (this.btnPlay.InvokeRequired)
            {
                playCallBack playSafe = new playCallBack(play);
                this.btnPlay.Invoke(playSafe, new object[] { fName });
            } else */
    
            // If a file is already loaded, perhaps it is necessary to close the old file and load a new one
            if (state == playerState.Stopped)
            {
                // Check if the filename has been changed
                if (fName != myPlayer.Filename || mode.channelSwitch != myPlayer.playMode)
                {
                    // If the filename has changed, kill
                    myPlayer.Dispose();
                    myPlayer = null;
                    state = playerState.Uninitialised;
                }
            }
    
            // If no file is open yet
            if (myPlayer == null)
            {
                // Try to open a file
                try
                {
                    // Create a new player class and get the handle to it
                    myPlayer = new player(fName, mode.channelSwitch);
                    // Hook to the custom event to be notified when file is finished playing
                    myPlayer.StopPlay += new player.PlayerEvent(finishedPlaying);
                    state = playerState.Stopped;
                }
                catch (COMException comE)
                {
                    MessageBox.Show("Unable to open file: " + comE.Message, "Open Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
    
            // Other irrelevent code
    	}
    
        private void finishedPlaying(Object sender)
        {
            // If we are in continuous mode and this is not the last track, play the next one
            if (mode.continuous && (mode.currentIndex < lvdNdPlayList.Items.Count - 1))
            {
                ListViewItem lvItem = getFileNameSafe(++mode.currentIndex);
                string fName = lvItem.SubItems[1].Text + "\\" + lvItem.SubItems[0].Text;
                // Set the new state to stopped, as the file has finished playing
                state = playerState.Stopped;
    
                // playCallBack playSafe = play;--- This locks up the main ui when run ---
                // mainDispatcher.Invoke(playSafe, fName);--- This locks up the main ui when run ---
                play(fName);
    
            }
            else
            {
                // Irrelevent code
            }
        }// </finishedPlaying>
    
        // Safe crossthreaded callback to retrieve filename from main thread controlls
        private ListViewItem getFileNameSafe(int lvIndex)
        {
            if (this.lvdNdPlayList.InvokeRequired)
            {
                SetListViewCallback getlv = new SetListViewCallback(getFileNameSafe);
                return (ListViewItem)this.Invoke(getlv, new object[] { lvIndex });
            }
            else
            {
                return lvdNdPlayList.Items[lvIndex];
            }
        }
    }
    
    class player : IDisposable
    {
        // Define the event to be called when the file finishes playing
        public event PlayerEvent StopPlay;
        public delegate void PlayerEvent(Object sender);
    
        public player(string Filename, bool channelSwitch)
        {
            // Irrelevent code
    
            // Assign a manual reset event to the graph event
            mre = new ManualResetEvent(false);
    #if USING_NET11
            m_mre.Handle = hEvent;
    #else
            mre.SafeWaitHandle = new Microsoft.Win32.SafeHandles.SafeWaitHandle(hEvent, true);
    #endif
            // Create the event waiting thread
            Thread thrd = new Thread(new ThreadStart(this.EventWait));
            thrd.Name = "MediaEvent thread";
            thrd.Start();
        }
    
        // Event waiting
        private void EventWait()
        {
            // Infinite loop and other irrelevent code
    
            // If file has finished playing
            if (ecode == EventCode.Complete)
            {
                // Stop the filter graph
                Stop();
                // Then throw the custom event that i defined earlier
                if (StopPlay != null)
                {
                    StopPlay(this);
                }
             }
             HR = mediaEvent.FreeEventParams(ecode, ptr1, ptr2);
             DsError.ThrowExceptionForHR(HR);
        }
    
        // The dispose function, to clean up when required
        public void Dispose()
        {
            CloseInterfaces();
        }
    
        // Shut Down!
        private void CloseInterfaces()
        {
            // Initialise the HRESULT variable
            int HR;
            // Lock to avoid contention
            lock (this) // This is where method 1 and 2 lock up
            {
                // If the graph is not currently exiting, set the state to exiting
                // And release any running thread
                if (state != GraphState.Exiting)
                {
                    state = GraphState.Exiting;
    
                    if (mre != null)
                    {
                        mre.Set();
                    }
                }
                // Kill interfaces and other irrelevent code
            }
            // Aparently garbage collector is badddd to use
            //GC.Collect();
    
        }
    
    }
    

     


     Thanks

    Chris

     



    • Edited by HeZzA Saturday, January 21, 2012 5:47 AM
    Saturday, January 21, 2012 5:43 AM
  • Hello HeZzA,

    1. After reading through your code and the other posts in this discussion thread, I figured that the "player" class is a wrapper for the Windows Media Player ActiveX control. Is this correct ?

    2. Assuming that you are using the Windows Media Player ActiveX control, I am currently trying to re-create the problem that you are facing albeit I have not used this particular ActiveX before.

    3. To get me up to speed, please advise me :

    • What is the ActiveX method that you use to play a file ? I think this is openPlayer().
    • What is the event that the ActiveX control fires when it has finished playing the current file ?

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Saturday, January 21, 2012 7:39 AM
  • Oh no, my appologies.

    I am using DirectShowNet. 

    http://directshownet.sourceforge.net/

    I can post the full code, if that would help.

    Basically my program requires to set up a playlist of stereo tracks, and if a checkbox is checked, switch the L and R channels.

    For that reason im using DirectShow to set up a filter graph to use a channel switching DMO which i have written

    Saturday, January 21, 2012 7:51 AM
  • Hello HeZzA,

    1. I downloaded DirectShow.Net from the sourceforge site.

    2. I assume that the relevant assembly here is Assembly DirectShowLib-2005.

    3. Yes, full source codes of the "player" class would be helpful. At this time, I would like to know :

    • The classes from this assembly which are wrapped by "player".
    • The relevant properties, methods and events of these classes.

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Saturday, January 21, 2012 8:08 AM
  • Here is player.cs

    EDIT: I uploaded the files, link in the next post. Should be a bit easier on the eyes

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    
    using DirectShowLib;
    using DirectShowLib.DMO;
    
    namespace SpeachInNoise
    {   // Define the player class that derives from IDisposable
        class player : IDisposable
        {
            // Create the custom variable defining the state of the graph
            enum GraphState
            {
                Stopped,
                Paused,
                Running,
                Exiting
            }
    
            #region Variables
    
            // The name of the file
            private string fname;
            // The current mode that the player is in (channel switching or not)
            private bool cSwitch;
            // Interfaces
            private IFilterGraph2 filterGraph;
            private IMediaControl mediaControl;
            private IMediaEvent mediaEvent;
    
            //Manual Reset Event to be used by the MediaEvent thread
            private ManualResetEvent mre;
    
            // Current state of the graph
            volatile private GraphState state = GraphState.Stopped;
    
            #endregion
    
            // The dispose function, to clean up when required
            public void Dispose()
            {
                CloseInterfaces();
            }
            // Destructor
            ~player()
            {
                CloseInterfaces();
            }
    
            // Define the event to be called when the file finishes playing
            public event PlayerEvent StopPlay;
            public delegate void PlayerEvent(Object sender);
    
            // Constructor
            // ------------ TODO implement SetUpGraph(Filename);
            public player(string Filename, bool channelSwitch)
            {
                try
                {
                    int HR;
                    IntPtr hEvent;
    
                    // Copy the filename into the private fname variable
                    fname = Filename;
                    // copy the play mode into play mode variable
                    cSwitch = channelSwitch;
    
                    if (cSwitch)
                    {
                        // Set up the graph With the channel switch filter
                        SetUpGraphSwitch(Filename);
                    }
                    else
                    {
                        // Otherwise set up a graph with no filters
                        SetUpGraph(Filename);
                    }
    
                    // Grab a handle to the graph's event signaler
                    HR = mediaEvent.GetEventHandle(out hEvent);
                    DsError.ThrowExceptionForHR(HR);
    
                    // Assign a manual reset event to the graph event
                    mre = new ManualResetEvent(false);
    #if USING_NET11
                    m_mre.Handle = hEvent;
    #else
                    mre.SafeWaitHandle = new Microsoft.Win32.SafeHandles.SafeWaitHandle(hEvent, true);
    #endif
                    // Create the event waiting thread
                    //------------------ThreadPool.QueueUserWorkItem(new WaitCallback(this.EventWait));
                    Thread thrd = new Thread(new ThreadStart(this.EventWait));
                    thrd.Name = "MediaEvent thread";
                    thrd.Start();
                }
                catch
                {
                    Dispose();
                    throw;
                }
    
            } // </public player(string Filename)>
    
            // Event waiting
            private void EventWait()
            {
                // Define E_ABORT, to be returned if GetEvent is called when no event is in queue
                // This is a standard HRESULT value
                const int E_ABORT = unchecked((int)0x80004004);
    
                // HRESULT return vlaue
                int HR;
                // Initialize the variables for GetEvent call
                IntPtr ptr1, ptr2;
                EventCode ecode;
    
                do // Begin infinite loop
                {
                    // Wait for an event, -1 waits indefinately
                    mre.WaitOne(-1, true);
    
                    // Lock up to avoid contention for the asyncronous state variable
                    lock (this)
                    {
                        // If graph is not shutting down
                        if (state != GraphState.Exiting)
                        {
                            // Get the event
                            for (HR = mediaEvent.GetEvent(out ecode, out ptr1, out ptr2, 0);
                                HR >= 0;
                                HR = mediaEvent.GetEvent(out ecode, out ptr1, out ptr2, 0))
                            {
                                // If file has finished playing
                                if (ecode == EventCode.Complete)
                                {
                                    // Stop the filter graph
                                    Stop();
    
                                    // Then throw the custom event that i defined earlier
                                    if (StopPlay != null)
                                    {
                                        StopPlay(this);
                                    }
                                }
    
                                // Release the resources that were just used to retireve event info
                                HR = mediaEvent.FreeEventParams(ecode, ptr1, ptr2);
                                DsError.ThrowExceptionForHR(HR);
                            }
    
                            // If some error caused the loop to exit that wasnt due to loop running out of events
                            if (HR != E_ABORT)
                            {
                                DsError.ThrowExceptionForHR(HR);
                            }
                        }
                        // Otherwise, the graph must be shutting down, break from the infinite loop
                        else
                        {
                            break;
                        }
                    }
                } while (true);
                
            } // </private void EventWait()>
    
            // A helper funtion to retrieve the private fname variable
            public string Filename
            {
                get
                {
                    return fname;
                }
            }// </public string Filename>
    
            public bool playMode
            {
                get
                {
                    return cSwitch;
                }
            }
    
            // Start Playing the file
            public void Start()
            {
                // Can only start if not playing already and not shutting down
                if (state == GraphState.Stopped || state == GraphState.Paused)
                {
                    // Call the Run function of the IMediaControl Interface
                    int HR = mediaControl.Run();
                    DsError.ThrowExceptionForHR(HR); // Throw exception if failed
    
                    // Change the Graph state to Running
                    state = GraphState.Running;
                }
            }// </ public void Start()>
    
            // Pause the graph
            public void Pause()
            {
                // Can only pause if already playing
                if (state == GraphState.Running)
                {
                    // Call the Pause function of the IMediaControl Interface
                    int HR = mediaControl.Pause();
                    DsError.ThrowExceptionForHR(HR);// Throw exception if failed
    
                    // Update the state of the graph to Paused
                    state = GraphState.Paused;
                }
            } // </public void Pause()>
    
            // Stop The graph
            public void Stop()
            {
                // Can only stop if filter graph is playing or paused
                if (state == GraphState.Running || state == GraphState.Paused)
                {
                    // Call the Stop method of the IMediaControl Interface
                    int HR = mediaControl.Stop();
                    DsError.ThrowExceptionForHR(HR);// Throw exception if failed
    
                    // Update the state of the graph to Stopped
                    state = GraphState.Stopped;
                }
            } // </public void Stop()>
    
            // Set the surrent position of the clip back to 0, by exposing IMediaPosition Interface
            public void Rewind()
            {
                // Expose the IMediaPosition Interface
                IMediaPosition mediaPosition = filterGraph as IMediaPosition;
                // Set the current position to 0
                int HR = mediaPosition.put_CurrentPosition(0);
            } // </public void Rewind()>
    
            
            // Build the graph
            private void SetUpGraph(string Filename)
            {
                // Initialise the HRESULT variable
                int HR;
    
                // Get the graphbuilder Object, IFilterGraph2 inherits from IGraphBuilder
                filterGraph = new FilterGraph() as IFilterGraph2;
    
                try
                {
                    // Render the file
                    HR = filterGraph.RenderFile(Filename, null);
                    System.Diagnostics.Debug.Write(HR);
                    DsError.ThrowExceptionForHR(HR);
    
                    // Expose the ImediaEvent and IMeciaControl interfaces
                    mediaEvent = filterGraph as IMediaEvent;
                    mediaControl = filterGraph as IMediaControl;
                }
                finally { }
    
    
            } // </private void SetupGraph(string Filename)>
    
            // Build the graph with the channel switch filter
            private void SetUpGraphSwitch(string Filename)
            {
                // Initialise the HRESULT variable
                int HR;
                // Initialise the base filters
                IBaseFilter ibfFile = null;
                IBaseFilter ibfFilter = null;
                IBaseFilter ibfRender = null;
                IDMOWrapperFilter dmoWrapperFilter = null;
    
                // Initialise the graph builder to put all the filters together
                ICaptureGraphBuilder2 icgb = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
    
                // Get the graphbuilder Object, IFilterGraph2 inherits from IGraphBuilder
                filterGraph = new FilterGraph() as IFilterGraph2;
    
                try
                {
                    // Specify the filter graph to be used by the graph builder
                    HR = icgb.SetFiltergraph(filterGraph);
                    DsError.ThrowExceptionForHR(HR);
    
                    // Add in a DMO wrapper filter
                    ibfFilter = (IBaseFilter)new DMOWrapperFilter();
                    dmoWrapperFilter = (IDMOWrapperFilter)ibfFilter;
    
                    // Add the filter to the wrapper (i know the guid of the filter i want to use) 3BE8E8B1-4E29-45e1-A088-9E0D6AE74646
                    // ECHO = EF3E932C-D40B-4F51-8CCF-3F98F1B29D5D
                    HR = dmoWrapperFilter.Init(new Guid("{EF3E932C-D40B-4F51-8CCF-3F98F1B29D5D}"), DMOCategory.AudioEffect);
                    DsError.ThrowExceptionForHR(HR);
    
                    // Add it to the graph
                    HR = filterGraph.AddFilter(ibfFilter, "Filter");
                    DsError.ThrowExceptionForHR(HR);
    
                    // Add a renderer to the graph
                    ibfRender = (IBaseFilter)new AudioRender();
                    HR = filterGraph.AddFilter(ibfRender, "Renderer");
                    DsError.ThrowExceptionForHR(HR);
    
                    // Add the asyncronous reader
                    ibfFile = (IBaseFilter)new AsyncReader();
                    HR = filterGraph.AddFilter(ibfFile, "Reader");
                    DsError.ThrowExceptionForHR(HR);
    
                    // Add the source filter to the reader
                    IFileSourceFilter iFileSink = (IFileSourceFilter)ibfFile;
                    HR = iFileSink.Load(Filename, null);
                    DsError.ThrowExceptionForHR(HR);
    
                    // Now render the graph
                    HR = icgb.RenderStream(null, null, ibfFile, null, ibfFilter);
                    DsError.ThrowExceptionForHR(HR);
    
                    // Select output pin 0
                    IPin iPin = DsFindPin.ByDirection(ibfFilter, PinDirection.Output, 0);
                    
                    // Now render the graph
                    HR = icgb.RenderStream(null, null, iPin, null, ibfRender);
                    DsError.ThrowExceptionForHR(HR);
    
                     // Expose the ImediaEvent and IMeciaControl interfaces
                    mediaEvent = filterGraph as IMediaEvent;
                    mediaControl = filterGraph as IMediaControl;
    
                    // Release unneeded objects
                    Marshal.ReleaseComObject(ibfRender);
                    Marshal.ReleaseComObject(dmoWrapperFilter);
                    Marshal.ReleaseComObject(iPin);
                }
                finally { }
    
    
            } // </private void SetupGraphSwitch(string Filename)>
            
    
            // Shut Down!
            private void CloseInterfaces()
            {
                // Initialise the HRESULT variable
                int HR;
    
                // Lock to avoid contention
                lock (this)
                {
                    // If the graph is not currently exiting, set the state to exiting
                    // And release any running thread
                    if (state != GraphState.Exiting)
                    {
                        state = GraphState.Exiting;
    
                        if (mre != null)
                        {
                            mre.Set();
                        }
                    }
                    // If a IMediaControl interface is still exposed, then stop the graph and kill interface
                    if (mediaControl != null)
                    {
                        HR = mediaControl.Stop();
                        mediaControl = null;
                    }
                    // If a filter grapg exists, release it and set the filterGraph variable to NULL
                    if (filterGraph != null)
                    {
                        Marshal.ReleaseComObject(filterGraph);
                        filterGraph = null;
                    }
                }
                // Use Garbage COllector to reclaim unused memory
                GC.Collect();
    
            } // </private void CloseInterfaces()>
    
    
        } // </player class>
    } // </namespace>

     


    And Form1.cs

     

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Windows.Threading;
    
    namespace SpeachInNoise
    {
        public partial class frmMain : Form
        {
            public frmMain()
            {
                InitializeComponent();
                InitializeModes();
                scanWatchFolders();
                //==================================mainDispatcher = Dispatcher.CurrentDispatcher;
            }
    
            // Define a custom variable describing the state of the player
            enum playerState
            {
                Uninitialised,
                Stopped,
                Paused,
                Playing
            };
            struct playerMode
            {
                public bool continuous;
                public int currentIndex;
                public bool channelSwitch;
            };
    
            // This delegate enables asynchronous calls for setting
    		// the text property on a control.
    		delegate void SetTextCallback(string text);
            // Delegate enabling asyncronous access to a listview item
            delegate ListViewItem SetListViewCallback(int lvIndex);
            // Initialise the dispatcher for the current thread
            //Dispatcher mainDispatcher = Dispatcher.CurrentDispatcher;
            // Delegate to be used by dispatcher to call the play function within the main thread, from another thread
            delegate void playCallBack(string fName);
    
            // Initialise the mode variable
            playerMode mode;
            // Initialise the state variable
            playerState state = playerState.Uninitialised;
            // Also initialise the class variable
            player myPlayer = null;
    
            // Called when the Play button is clicked
            private void btnPlay_Click(object sender, EventArgs e)
            {
                // If we are already playing, pause
                if (state == playerState.Playing)
                {
                    // Pause the player
                    myPlayer.Pause();
                    // Change the play button back to Play
                    btnPlay.Text = "Resume";
                    // Change state to paused
                    state = playerState.Paused;
                }
                // If we are paused, then continue playing
                else if (state == playerState.Paused)
                {
                    // Start the player
                    myPlayer.Start();
                    // Change the play button to pause
                    btnPlay.Text = "Pause";
                    // Change the state to playing
                    state = playerState.Playing;
                }
                // Otherwise, call play to play an uninitialised player or stopped player
                else
                {
                    InitializeModes();
                    if (mode.currentIndex >= 0)
                    {
                        string fName = lvdNdPlayList.Items[mode.currentIndex].SubItems[1].Text + "\\" + lvdNdPlayList.Items[mode.currentIndex].SubItems[0].Text;
                        play(fName);
                    }
    
                }
    
            } // </btnPlay_Click>
    
            private void btnStop_Click(object sender, EventArgs e)
            {
                // If the player is playing or paused, then stop it
                if (state == playerState.Paused || state == playerState.Playing)
                {
                    // Stop the player
                    myPlayer.Stop();
                    // Rewind the file back to 0
                    myPlayer.Rewind();
                    // change the play button to Play, if not already
                    btnPlay.Text = "Play";
                    // Change state to stopeed
                    state = playerState.Stopped;
                } // Else do nothing
    
            } // </btnStop_Click>
    
            // The callback function for custom event called when playing is finished
            private void finishedPlaying(Object sender)
            {
                // If we are in continuous mode and this is not the last track, play the next one
                if (mode.continuous && (mode.currentIndex < lvdNdPlayList.Items.Count -1) )
                {
                    ListViewItem lvItem = getFileNameSafe(++mode.currentIndex);
                    string fName = lvItem.SubItems[1].Text + "\\" + lvItem.SubItems[0].Text;
                    // Set the new state to stopped, as the file has finished playing
                    state = playerState.Stopped;
                    
                    // Instantiate the delegate to call play() in main thread from this thread
                    //playCallBack playSafe = play;
                    // Now use the dispatcher to call the delegate safely form main thread
                    //mainDispatcher.Invoke(playSafe, fName);
                    play(fName);
    
                }
                else
                {
                    // Change the Play button text back to play, must safely crossthread this action
                    btnPlaySetTextSafe("Play");
                    // change the state to Stopped
                    state = playerState.Stopped;
                    // Rewind the file
                    myPlayer.Rewind();
                }
            }// </finishedPlaying>
    
            // Safe crossthreaded callback to retrieve filename from main thread controlls
            private ListViewItem getFileNameSafe(int lvIndex)
            {
                if (this.lvdNdPlayList.InvokeRequired)
                {
                    SetListViewCallback getlv = new SetListViewCallback(getFileNameSafe);
                    return (ListViewItem)this.Invoke(getlv, new object[] { lvIndex });
                }
                else
                {
                    return lvdNdPlayList.Items[lvIndex];
                }
            }
    
            // safe cross threaded callback for setting the text of btnPlay
            private void btnPlaySetTextSafe(string text)
            {
                if (this.btnPlay.InvokeRequired)
                {
                    SetTextCallback set = new SetTextCallback(btnPlaySetTextSafe);
                    this.Invoke(set, new object[] { text });
                }
                else
                {
                    this.btnPlay.Text = text;
                }
            } // </btnPlaySetTextSafe>
    
            private void watchFoldersToolStripMenuItem_Click(object sender, EventArgs e)
            {
                WatchFolders watchFolderForm = new WatchFolders(this);
                watchFolderForm.Show();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                state = playerState.Stopped;
                InitializeModes();
                if (mode.currentIndex >= 0)
                {
                    string fName = lvdNdPlayList.Items[mode.currentIndex].SubItems[1].Text + "\\" + lvdNdPlayList.Items[mode.currentIndex].SubItems[0].Text;
                    play(fName);
                }
            }
    
            public void scanWatchFolders()
            {
                // Clear the current watch folder
                lvdNdWatchFolders.Items.Clear();
    
                String[] wFolders = new String[Properties.Settings.Default.WatchFolders.Count];
                Properties.Settings.Default.WatchFolders.CopyTo(wFolders, 0);
                //Loop through all the watch folders
                for (int i = 0; i < Properties.Settings.Default.WatchFolders.Count; i++)
                {
                    System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(wFolders[i]);
    
                    foreach (System.IO.FileInfo file in dir.GetFiles())
                    {
                        ListViewItem lvItem = new ListViewItem(file.Name);
                        lvItem.SubItems.Add(file.DirectoryName);
                        lvdNdWatchFolders.Items.Add(lvItem);
                    }
    
                }
            }// </scanWatchFodlers>
    
            private void InitializeModes()
            {
                // Initialise the play mode
                mode.continuous = cbContinuous.Checked;
                mode.channelSwitch = cbSwitch.Checked;
                if (lvdNdPlayList.SelectedIndices.Count == 0)
                {
                    if (lvdNdPlayList.Items.Count > 0)
                        mode.currentIndex = 0;
                    else
                        mode.currentIndex = -1;
    
                }
                else if (lvdNdPlayList.SelectedIndices.Count > 0)
                {
                    mode.currentIndex = lvdNdPlayList.SelectedIndices[0];
                }
    
            }// </InitializeModes>
    
            private void play(string fName)
            {/*
                if (this.btnPlay.InvokeRequired)
                {
                    playCallBack playSafe = new playCallBack(play);
                    this.btnPlay.Invoke(playSafe, new object[] { fName });
                }
                else
                {*/
    
                // If a file is already loaded, perhaps it is necessary to close the old file and load a new one
                if (state == playerState.Stopped)
                {
                    // Check if the filename has been changed
                    if (fName != myPlayer.Filename || mode.channelSwitch != myPlayer.playMode)
                    {
                        // If the filename has changed, kill this shit!!!
                        myPlayer.Dispose();
                        myPlayer = null;
                        state = playerState.Uninitialised;
                    }
                }
                // If no file is open yet
                if (myPlayer == null)
                {
                    // Try to open a file
                    try
                    {
                        // Create a new player class and get the handle to it
                        myPlayer = new player(fName, mode.channelSwitch);
    
                        // Hook to the custom event to be notified when file is finished playing
                        myPlayer.StopPlay += new player.PlayerEvent(finishedPlaying);
                        state = playerState.Stopped;
                    }
                    catch (COMException comE)
                    {
                        MessageBox.Show("Unable to open file: " + comE.Message, "Open Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
                // If we were previously stopped, Start
                if (state == playerState.Stopped)
                {
                    // Change the play button to pause
                    btnPlay.Text = "Pause";
                    // start playing file
                    myPlayer.Start();
                    // Change the state to playing
                    state = playerState.Playing;
                }
                
                //}
                
            }// </play>
    
            private void btnRewind_Click(object sender, EventArgs e)
            {
                myPlayer.Rewind();
            }
    
            private void btnSkip_Click(object sender, EventArgs e)
            {
                // If the player is initialised
                if (state != playerState.Uninitialised)
                {
                    // If there are more tracks in the playlist
                    if (mode.currentIndex < lvdNdPlayList.Items.Count - 1)
                    {
                        // Get the filename of the nex track
                        ListViewItem lvItem = getFileNameSafe(++mode.currentIndex);
                        string fName = lvItem.SubItems[1].Text + "\\" + lvItem.SubItems[0].Text;
                        // Stop the palyer
                        myPlayer.Stop();
                        // Set the state to stopped
                        state = playerState.Stopped;
                        play(fName);
                    }
                }
            } 
           
        }
    }
    


     

     

     



    • Edited by HeZzA Saturday, January 21, 2012 8:35 AM
    Saturday, January 21, 2012 8:24 AM
  • This may be easier however..

    Form1.cs => http://up.ht/xyZJHB

    player.cs => http://up.ht/xGdHHo
    Saturday, January 21, 2012 8:33 AM
  • Hello HeZzA,

    1. I managed to trace to the part of your code that reported the "COM object that has been separated from its underlying RCW cannot be used" error.

    2. The trouble spot appears to be the part of the play() function that calls the player.Dispose() method.

    3. Inside Dispose(), CloseInterfaces() is called and here we see the following being executed :

    // If a IMediaControl interface is still exposed, then stop the graph and kill interface
    if (mediaControl != null)
    {
        HR = mediaControl.Stop();
        mediaControl = null;
    }
    // If a filter grapg exists, release it and set the filterGraph variable to NULL
    if (filterGraph != null)
    {
        Marshal.ReleaseComObject(filterGraph);
        filterGraph = null;
    }
    
    

    Pay attention to the fact that Marshal.ReleaseComObject() is called on "filterGraph". This has profound effects on references to its other interfaces, the IMediaEvent interface in particular.

    4. Now the IMediaEvent interface of "filterGraph" was acquired in SetUpGraph() and referenced by "mediaEvent" :

    // Expose the ImediaEvent and IMeciaControl interfaces
    mediaEvent = filterGraph as IMediaEvent;
    mediaControl = filterGraph as IMediaControl;
    
    

    Hence after Marshal.ReleaseComObject() is called on "filterGraph", this likely caused "mediaEvent" to become detached from the underlying COM object which originates from "filterGraph".

    5. The crucial point is that after "mediaEvent" has become detached from its underlying COM object, it is used again in the EventWait() function :

    // Release the resources that were just used to retireve event info
    HR = mediaEvent.FreeEventParams(ecode, ptr1, ptr2);
    DsError.ThrowExceptionForHR(HR);
    
    

    6. Hence there is possibly a code sequence problem.

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Saturday, January 21, 2012 10:34 AM
  • Hi Bio

    I see, i will rethink that part of code tomorrow morning.

    However I am still a little confused, as pressing the skip button actually places the call to play() in exactly the same way that finishedPlaying() does. It grabs a new filename from the next item in the listview and sends it to play(). The Dispose(), CloseInterfaces() functions are called. However the problem does not arise from that, the next file plays as should.

    Hence my initial assumption that there must be some strange cross thread stuff happening here :/

    Thanks

    Chris

    Saturday, January 21, 2012 12:30 PM
  • Hello Chris,

    1. I think the problem is that when EventWait() (an event handler of the player class) is called, the frmMain.finishedPlaying() function is invoked as a result of a call to the StopPlay delegate.

    2. Inside frmMain.finishedPlaying(), frmMain.play() is called which will perform the following sequence of code calls :

    • myPlayer.Dispose();
    • myPlayer = null;
    • myPlayer = new player(fName, mode.channelSwitch);

    The current instance of "myPlayer" is disposed of and a new one is created right in the middle of the execution of the current "myPlayer" instance (i.e. inside its EventWait() function).

    3. But the "mediaEvent" object of the current "myPlayer" (not the newly instantiated one) is still being referenced inside EventWait() and it has detached from its underlying COM object. This causes the exception.

    4. Now, inside btnSkip_Click(), when play() is called, after a new myPlayer is instantiated, the old one is never referenced again. Hence you do not get the exception.

    - Bio.

     

     

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Saturday, January 21, 2012 2:29 PM
  • Hmm i see, so what i will need to do is before allocating a new mediaEvent, check to see if one already exist, and if so, free the parameters before creating new ones.

    Then perhaps set a flag if these parameters have already been released, and check the flag before calling FreeEventParams();

    That may actually be way off the ball, i have just woken up at 3m to get a drink of water and thought i would throw out a reply while im up :p

    Also, Most importantly, Would you agree that method 3 mentioned above is the correct approach. ie i don't have any weird cross threading issues here, just some sequencing problems.

    Thanks

    Chris


    • Edited by HeZzA Saturday, January 21, 2012 7:20 PM
    Saturday, January 21, 2012 7:16 PM
  • Hello Chris,

    1. >> i don't have any weird cross threading issues here...

    I would say that there are no wierd cross-threading issues as far as COM objects are concerned, i.e. the occurrence of the "COM object that has been separated from its underlying RCW cannot be used" error is not caused by threading issues.

    2. >> just some sequencing problems...

    Concerning sequencing : yes, I think there is an issue here. I would be concerned about the fact that an object (i.e. an instance of the player class) is called to Dispose() when it is still performing an operation.

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    • Proposed as answer by Paul Zhou Tuesday, January 24, 2012 6:46 AM
    Sunday, January 22, 2012 1:27 AM
  • Hi HeZzA,
    Welcome to the MSDN forum!

    I'm moving the thread to current forum for better support.

    Thanks for your understanding and have a nice day!

    yoyo

     


    Yoyo Jiang[MSFT]
    MSDN Community Support | Feedback to us
    Monday, January 23, 2012 7:41 AM
    Moderator