Click here to Skip to main content
15,885,979 members
Please Sign up or sign in to vote.
3.33/5 (3 votes)
See more:
Hey everybody,
I've got what seems to be a simple problem, but I just can't get my head around it.
I have narrowed it down to this:
When I use mciSendString to open, play and pause a wav file, the play/pause commands only work when being sent from the very same thread the 'open' command was called.

So this works:
mciSendString("open ""C:\file.wav"" type waveaudio alias audiofile", Nothing, 0, IntPtr.Zero)
mciSendString("play audiofile from 0", Nothing, 0, IntPtr.Zero)
thread.sleep(300)
mciSendString("pause audiofile", Nothing, 0, IntPtr.Zero)
thread.sleep(300)
mciSendString("resume audiofile", Nothing, 0, IntPtr.Zero)
thread.sleep(300)
mciSendString("close audiofile", Nothing, 0, IntPtr.Zero)

But this does not:
dim t1 as thread, t2 as thread
t1 = new Thread(addressof openFile)
t1.start()
t2 = new Thread(addressof playFile)
t2.start()
'etc.
 
private sub openFile()
mciSendString("open """ & fileName & """ type waveaudio alias audiofile", Nothing, 0, IntPtr.Zero)
end sub
private sub playFile()
mciSendString("play audiofile from 0", Nothing, 0, IntPtr.Zero)
end sub
'etc.


This is, of course, not the actual code, but you get the idea.
When I debug it with console.writeline(Thread.CurrentThread.ManagedThreadId), I can see that in those cases the threads have the same ThreadId, the commands succeed (return value of mciSendString is zero), and in those cases the mciSendString commands get called by threads with different ThreadIds, they fail (Return value is 263 -- INVALID_DEVICE_NAME).
I googled my brain out, but I cannot find an example of how to create a thread that has a constant ThreadId, or how to simply invoke a method in a certain thread.
I might be blinded by the whole lotta code I read and tried out, but right now it seems I can't get to the answer.
If somebody could point me to the right direction, that would be super cool.
Posted
Updated 18-Apr-11 12:00pm
v3

Okay, I worked something out, for those who are looking for a solution to similar problems.

Mine consists of creating a dedicated thread, waiting to issue an mciSendString command. Basically, it follows the producer/consumer pattern, where the producer and consumer commnuicate over locks of a dedicated object.


VB
public class mciTest
  private mciConsumerThread as Thread

  public sub new()
    ' The consumer side is started in its own thread
    ' create one from where the mciSendString method gets called
    mciConsumerThread = New Thread(AddressOf threadedMCIconsumer)
    mciConsumerThread.IsBackground = True
    mciConsumerThread.Start()
  end sub

  Private Sub threadedMCIconsumer()
    Do
        SyncLock MonitorLock
            ' wait for relase of MonitorLock object;
            ' when this statement exits, a new mciCommand
            ' has been issued
            Monitor.Wait(MonitorLock)
            ' the object has been released, so make the mci call
            ' from this thread and put the return value into the
            ' syncMciResult field
            If syncMciCommand <> "" Then
                syncMciResult = MciSendString(syncMciCommand, Nothing, 0, IntPtr.Zero)
            End If
            ' tell the other thread that a result has been obtained.
            Monitor.Pulse(MonitorLock) ' [This exits the Monitor.wait(MonitorLock) 
                                       ' command on the other thread.]
            ' start loop all over again, waiting for the next command.
        End SyncLock
    Loop
    ' As this is a background-thread, the loop exits
    ' when the thread is aborted from outside.
  End Sub


  ' The producer is the command being called instead of issuing 
  ' mciSendString directly:

  Public Function mciReplacement(ByVal Command As String) As Integer

    Dim nonLockedReturn as Integer ' this will be the return value

    SyncLock MonitorLock
        ' The MonitorObject has been freed temporarily by the 
        ' threadedMCIconsumer thread.
        ' Grab the syncMciCommand variable and memorize the mciCommand
        syncMciCommand = Command

        ' Release the MonitorObject, so the other thread 
        ' can process the mciSendString
        Monitor.Pulse(MonitorLock) ' [This exits the Monitor.wait(MonitorLock) command 
                                   ' on the other thread.]
        
        ' Now wait until the mci-thread signals the arrival of the result.
        Monitor.Wait(MonitorLock)
        ' result has been written into syncMciResult
        nonLockedReturn = syncMciResult
    End SyncLock

    ' use nonLockedReturn to get the result safely out of SyncLock
    Return nonLockedReturn
  End Function

  'The synced variables need to be defined
  Private MonitorLock as new Object()
  Private mciReturnValue As Integer
  Private syncMciCommand as String

End Class

To make thread safe calls to the media control interface (mci), use the above class as follows:
VB
Dim cMci as new mciTest()
cMci.mciReplacement("open ""C:\test.wav"" type waveaudio alias audiofile")
cMci.mciReplacement("play audiofile")
cMci.mciReplacement("pause audiofile")
cMci.mciReplacement("resume audiofile")
 
Share this answer
 
Comments
Member 10966381 23-Jul-14 11:08am    
This is the solution. Thank you for posting it - saved me a truck load of work.
Many year after.. but I was reserching on this problem... it is basically because the MCI API needs a Message Loop to be active in the application (So.. it has to be Windows Forms to make the life easy)

http://msdn.microsoft.com/en-us/magazine/cc163417.aspx[^]

This post gave me an idea... so I decided to implement an Invoke and InvokeRequeried over the mciCall always to make sure the calls will be made inside the Main Application Thread. Of course I need to send all over the classes the Form reference to use it, but is not a big deal, at least in my project it isn't.

I hope if someone has the same problem can solve it with this :D

I know today with NUGET Package many extensions and libraries do the job.. but I try to make my project under my control, using the less third party libraries as possible.. so I am pretty sure many people would find it useful.

Thanks

Here is the final code

C#
using System;
using System.Threading;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class mciSafeCall
{

    [DllImport("winmm.dll")]
    private static extern int mciSendString(string strCommand, StringBuilder strReturn, int iReturnLength, IntPtr hwndCallback);


    private int syncMciResult;
    private string syncMciCommand;
    private Form formulario;

    public mciSafeCall(Form form)
    {
        formulario = form;
        syncMciCommand = "";
    }

    public void mciReplacement(string Command)
    {
        syncMciCommand = Command;
        if (!string.IsNullOrEmpty(syncMciCommand))
        {
            if (formulario.InvokeRequired)
                formulario.Invoke(new Action(() =>
                {
                    syncMciResult = mciSendString(syncMciCommand, null, 0, IntPtr.Zero);
                }));
            else
                syncMciResult = mciSendString(syncMciCommand, null, 0, IntPtr.Zero);

        }
    }



}
 
Share this answer
 
v2
From reading this CP article it seems the problem is something to do with the 2 threads competing for the same object. Have a read of the article here Introduction to making multithreaded VB.NET Apps[^]
 
Share this answer
 
Comments
skigh 18-Apr-11 19:09pm    
Thank you very much for your fast answer, but sadly it didn't help at all. I don't think there are any ressources or objects the threads are competing for. I can even let one thread finish before starting the other one (put a console.readkey in between the thread.starts) and still there's an error:(
Simon_Whale 19-Apr-11 2:21am    
they are competing for the mcisendstring in openfile and playfile subroutines
skigh 21-Apr-11 6:44am    
How am I supposed to sync-lock an API command? MSDN says:
"The type of the expression in a SyncLock statement must be a reference type, such as a class, a module, an interface, array or delegate."

mciSendString is a Win32-API:
Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal uReturnLength As Int32, ByVal hwndCallback As Int32) As Int32
Simon_Whale 21-Apr-11 7:24am    
As a test to make sure that its the mcisendstring inside the threads that is causing the issue I would comment them out and put in something like a debug.print or something similar.

How you can sync-lock a API function At this point in time Im not sure without further lookup.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900