Home All Groups Group Topic Archive Search About

I need a Class 101 on threading in VB6

Author
12 Jun 2009 7:37 AM
MM
Some while ago I asked why my form froze execution when I moved it
around the screen and received a lengthy reply from Olaf (Schmidt),
for which I was very grateful, but much of it went over my head. Since
I had lots of other work to do anyway on the body of the app in
question, I forgot about the "execution freezing" potential of a
typical VB6 app, but now I am interested again!

Let's take a simple example. Place an MCI control (MMControl1) on a
form (you don't have to actually do this, since I am sure most VB
programmers can work through the scenario on their head) and get it to
play any MIDI file.

Add the following to the Form_Click event:

   MMControl1.Notify = False
   MMControl1.Shareable = False
   MMControl1.DeviceType = "Sequencer"
   MMControl1.FileName = "C:\MIDI Files\Chopin\chp_waltz op18.mid"
   MMControl1.Wait = True
   MMControl1.Command = "Open"
   MMControl1.Command = "Play"

Now move the form about, load other apps, do whatever comes to mind to
place a burden on the CPU. Whatever you try, the MIDI output continues
uninterrupted.

Fantastic!

Now try setting up a loop of midiOutShortMsg messages being pumped out
of the midi out port. The music plays fine.....

UNTIL you start moving the form around as above. Then the output stops
dead. Any other CPU activity slows the output and makes it sound
erratic.

What I want is MIDI output that is never interrupted, as with the MCI
control example, but where I have complete control over the individual
midiOutShortMsg messages. That is, I can start or end at any
particular message and I can pre-empt any message and do some
additional task just before it is sent, like switching on an LED or
making a virtual on-screen piano key visibly depress.

So, my basic question is: How does the MCI control do its thing
without interruption? My second question is how can I achieve the same
uninterrupted output WITHOUT the MCI control?

Please use simple language and try posting an example or two! I learn
best by example.

Many thanks.

MM

Author
12 Jun 2009 9:51 AM
Alfie [UK]
On Fri, 12 Jun 2009 08:37:33 +0100, MM <kylix***@yahoo.co.uk> wrote:

>Please use simple language and try posting an example or two! I learn
>best by example.
>
What are your midiOutOpen parameters? And how are you creating your
'stream' of midiOutShortMsgs?

You can use the midiOutOpen CallBack parameters and a Timer to
'simulate' Asynchronous behaviour that won't be blocked by most
events, such as other code running, the form moving, etc, although you
will still be at the mercy of system and midi device loads, and you
will have to code for re-entrancy.
--
Alfie [UK]
<http://www.delphia.co.uk/>
There are two theories to arguing with women. Neither one works.
Author
12 Jun 2009 10:53 AM
MM
On Fri, 12 Jun 2009 10:51:58 +0100, "Alfie [UK]" <alfie@mail.invalid>
wrote:

>On Fri, 12 Jun 2009 08:37:33 +0100, MM <kylix***@yahoo.co.uk> wrote:
>
>>Please use simple language and try posting an example or two! I learn
>>best by example.
>>
>What are your midiOutOpen parameters?

ret = midiOutOpen(g_MIDI_Out, dev_id, 0&, 0&, 0&)

> And how are you creating your
>'stream' of midiOutShortMsgs?

I've removed the 'housekeeping' details from the following and show
only the core part:

Private Function CreateNewEvent(DeltaTime As Long, _
                                status As Byte, _
                                Optional data1 As Byte = 0, _
                                Optional data2 As Byte = 0, _
                                Optional data3 As Byte = 0) As Boolean

' MIDIEventPtr is a Public variable
' The MIDIEventQueue is a public UDT array

MIDIEventPtr = MIDIEventPtr + 1

With MIDIEventQueue(MIDIEventPtr)
    .DeltaTime = DeltaTime
    .status = status
    .data1 = data1
    .data2 = data2
    .data3 = data3
End With

End Function

>You can use the midiOutOpen CallBack parameters and a Timer to
>'simulate' Asynchronous behaviour that won't be blocked by most
>events,

Have you any example, or could point to one on the web?

> such as other code running, the form moving, etc, although you
>will still be at the mercy of system and midi device loads, and you
>will have to code for re-entrancy.

Thanks for your feedback.

MM
Author
12 Jun 2009 1:21 PM
Alfie [UK]
On Fri, 12 Jun 2009 11:53:01 +0100, MM <kylix***@yahoo.co.uk> wrote:

>Have you any example, or could point to one on the web?

Have a look at http://www.vbforums.com/showthread.php?t=566767

You'll see the midiOutOpen call uses a CallBack to the form to avoid
blocking. There are other CallBack options,
http://allapi.mentalis.org/apilist/midiOutOpen.shtml

If you start with that code, add a Timer (Interval=500) and this Sub;

Private Sub Timer1_Timer()
   Static t As Integer

      Command1_MouseDown t, 2, 0, 0, 0

      t = t + 1
      If t > 6 Then t = 0

End Sub

....you should see that you can both move the form around, and click
the buttons/combo without blocking the sequence from playing.

Timer Controls are not 100% accurate (due to the way that they work),
you may need to guard against re-entrancy for small Intervals or lots
of code in the Timer Sub, and you should enable/disable the Timer
explicitly which is not shown here. If you're going to run tight time
loops like this you should also be throwing in a DoEvents occasionally
to keep it responsive.

HTH
--
Alfie [UK]
<http://www.delphia.co.uk/>
If rabbits feet are so lucky, what happened to the rabbit?
Author
12 Jun 2009 3:40 PM
MM
On Fri, 12 Jun 2009 14:21:42 +0100, "Alfie [UK]" <alfie@mail.invalid>
wrote:

>On Fri, 12 Jun 2009 11:53:01 +0100, MM <kylix***@yahoo.co.uk> wrote:
>
>>Have you any example, or could point to one on the web?
>
>Have a look at http://www.vbforums.com/showthread.php?t=566767
>
>You'll see the midiOutOpen call uses a CallBack to the form to avoid
>blocking.

I added the callback to my code, but it made no difference. Output
still freezes when I move the form or scroll the grid.

But a question: Why would having a Callback cause blocking to be
avoided?

Show quoteHide quote
> There are other CallBack options,
>http://allapi.mentalis.org/apilist/midiOutOpen.shtml

>If you start with that code, add a Timer (Interval=500) and this Sub;
>
>Private Sub Timer1_Timer()
>   Static t As Integer
>
>      Command1_MouseDown t, 2, 0, 0, 0
>
>      t = t + 1
>      If t > 6 Then t = 0
>
>End Sub

The Timer made no difference either.

>...you should see that you can both move the form around, and click
>the buttons/combo without blocking the sequence from playing.
>
>Timer Controls are not 100% accurate (due to the way that they work),
>you may need to guard against re-entrancy for small Intervals or lots
>of code in the Timer Sub, and you should enable/disable the Timer
>explicitly which is not shown here. If you're going to run tight time
>loops like this you should also be throwing in a DoEvents occasionally
>to keep it responsive.

I'm already using DoEvents in the midiOutShortMsg loop.

I'll try the other link you provided.

Thanks.

MM
Author
12 Jun 2009 3:57 PM
Nobody
Instead of moving the form, try adding a label and in Timer1_Timer, show the
current time using Time() function. This way if you see the time frozen, you
can quickly tell if things are working out as expected.
Author
12 Jun 2009 5:16 PM
Alfie [UK]
On Fri, 12 Jun 2009 16:40:19 +0100, MM <kylix***@yahoo.co.uk> wrote:

>I added the callback to my code, but it made no difference. Output
>still freezes when I move the form or scroll the grid.
>
>But a question: Why would having a Callback cause blocking to be
>avoided?

I haven't done much directly with midi, as I usually use mciSendString
to handle multimedia with pre-built sound files, but I believe that
creating the device with midiOutOpen (by default) blocks until the
device has processed the messages, so setting a path open with a
callback avoids that being the cause of blocking because it returns as
soon as the message is sent (the CallBack then notifies your app that
the message has been handled).
Show quoteHide quote
>
>> There are other CallBack options,
>>http://allapi.mentalis.org/apilist/midiOutOpen.shtml
>
>>If you start with that code, add a Timer (Interval=500) and this Sub;
>>
>>Private Sub Timer1_Timer()
>>   Static t As Integer
>>
>>      Command1_MouseDown t, 2, 0, 0, 0
>>
>>      t = t + 1
>>      If t > 6 Then t = 0
>>
>>End Sub
>
>The Timer made no difference either.

I meant add the Timer to the first sample link I provided, my bad, I
added the AllAPI link after writing the first bit.
>
>I'm already using DoEvents in the midiOutShortMsg loop.

As Nobody mentions something else may be blocking your code. The Timer
is not infallible and can be blocked itself in certain situations, and
can operate differently within the IDE and a compiled app.

I've just got back to my desk so let me see if I can re-work a simpler
example.
--
Alfie [UK]
<http://www.delphia.co.uk/>
This could be seen as a bug or a feature... how Microsoft do you feel today ?
Author
12 Jun 2009 6:14 PM
Alfie [UK]
OK, I've simplied an example out of the code in the first link I
previously provided. This just plays the C chord every half second
using a Timer firing off 3 midiOutShortMsg messages.

Start a new project, add a combo box and a timer to the form (I left
the combo box as for some reason my default midi device doesn't play
within the IDE, so I needed to select one that does).

Paste the following code into the form's code page (I've pre-wrapped
the lines at 76 chars but watch for line wrap);

<-- CODE -->
Option Explicit

Private Const MAXPNAMELEN = 32

Private Type MIDIOUTCAPS
   wMid As Integer
   wPid As Integer
   vDriverVersion As Long
   szPname As String * MAXPNAMELEN
   wTechnology As Integer
   wVoices As Integer
   wNotes As Integer
   wChannelMask As Integer
   dwSupport As Long
End Type

Private Declare Function midiOutShortMsg Lib "winmm.dll" ( _
   ByVal hMidiOut As Long, ByVal dwMsg As Long) As Long
Private Declare Function midiOutClose Lib "winmm.dll" ( _
   ByVal hMidiOut As Long) As Long
Private Declare Function midiOutOpen Lib "winmm.dll" ( _
   lphMidiOut As Long, ByVal uDeviceID As Long, ByVal dwCallback As
Long, _
   ByVal dwInstance As Long, ByVal dwFlags As Long) As Long
Private Declare Function midiOutGetNumDevs Lib "winmm" () As Integer
Private Declare Function midiOutGetDevCaps Lib "winmm.dll" Alias _
   "midiOutGetDevCapsA" (ByVal uDeviceID As Long, lpCaps As
MIDIOUTCAPS, _
   ByVal uSize As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (
_
   pDst As Any, pSrc As Any, ByVal ByteLen As Long)

Private Type MidiMsg
   status As Byte
   Note As Byte
   Velocity As Byte
   Data3 As Byte
End Type

Private Const CALLBACK_WINDOW = &H10000
Private Const KeyOn = &H90

Private hMidi As Long

Private Sub Form_Load()
   Dim i As Long
   Dim caps As MIDIOUTCAPS

   Timer1.Enabled = False
   Timer1.Interval = 500

   Me.ScaleMode = vbPixels
   Combo1.Left = 0
   Combo1.Top = 0
   Combo1.Width = Me.ScaleWidth

   For i = 0 To midiOutGetNumDevs() - 1
      midiOutGetDevCaps i, caps, Len(caps)
      Combo1.AddItem caps.szPname, i
   Next
   Combo1.ListIndex = 0

   Timer1.Enabled = True

End Sub

Private Sub Combo1_Click()
   midiOutClose hMidi
   midiOutOpen hMidi, Combo1.ListIndex, Me.hWnd, 0, CALLBACK_WINDOW
End Sub

Private Sub Timer1_Timer()
   Static bWorking As Boolean
   Dim msg As MidiMsg
   Dim ml As Long

   If Not bWorking Then

      bWorking = True

      msg.status = KeyOn
      msg.Velocity = 64
      msg.Data3 = 0

      msg.Note = 60
      CopyMemory ml, msg, 4
      midiOutShortMsg hMidi, ml

      msg.Note = 64
      CopyMemory ml, msg, 4
      midiOutShortMsg hMidi, ml

      msg.Note = 67
      CopyMemory ml, msg, 4
      midiOutShortMsg hMidi, ml

      bWorking = False

   End If

End Sub

Private Sub Form_Unload(Cancel As Integer)
   midiOutClose hMidi
   Timer1.Enabled = False
End Sub

<-- CODE -->

The CallBack to the form ensures that the device isn't blocking and
the Timer firing attempts to ensure that other code isn't blocking.

If this was production code youmay want to use a multimedia timer
rather than the VB control (for greater resolution), and need more
error-trapping, but this should demonstrate non-blocking asynchronous
operation.

Depending upon how you are producing your 'notes' stream, the
CallBack_Function flag may be more appropriate for you. The CallBack
function would then read your message data and pass it into the
device, you may not actually need a timer method at all.
--
Alfie [UK]
<http://www.delphia.co.uk/>
Heller's Law: The first myth of management is that it exists.
Author
12 Jun 2009 9:07 PM
MM
On Fri, 12 Jun 2009 19:14:42 +0100, "Alfie [UK]" <alfie@mail.invalid>
wrote:

>OK, I've simplied an example out of the code in the first link I
>previously provided. This just plays the C chord every half second
>using a Timer firing off 3 midiOutShortMsg messages.
>
>Start a new project, add a combo box and a timer to the form (I left
>the combo box as for some reason my default midi device doesn't play
>within the IDE, so I needed to select one that does).
>
>Paste the following code into the form's code page (I've pre-wrapped
>the lines at 76 chars but watch for line wrap);

[snip]

No joy, I'm afraid. As soon as I move the form (hold mouse down in
caption bar and drag form) the output stops dead. It resumes as soon
as I release the mouse button.

MM
Author
13 Jun 2009 9:35 AM
MM
On Fri, 12 Jun 2009 22:07:27 +0100, MM <kylix***@yahoo.co.uk> wrote:

Show quoteHide quote
>On Fri, 12 Jun 2009 19:14:42 +0100, "Alfie [UK]" <alfie@mail.invalid>
>wrote:
>
>>OK, I've simplied an example out of the code in the first link I
>>previously provided. This just plays the C chord every half second
>>using a Timer firing off 3 midiOutShortMsg messages.
>>
>>Start a new project, add a combo box and a timer to the form (I left
>>the combo box as for some reason my default midi device doesn't play
>>within the IDE, so I needed to select one that does).
>>
>>Paste the following code into the form's code page (I've pre-wrapped
>>the lines at 76 chars but watch for line wrap);
>
>[snip]
>
>No joy, I'm afraid. As soon as I move the form (hold mouse down in
>caption bar and drag form) the output stops dead. It resumes as soon
>as I release the mouse button.

However, I have now had the opportunity to test it on XP and your
example works!

Must be a failing of Windows 98SE, I reckon.

MM
Author
15 Jun 2009 12:46 PM
Alfie [UK]
On Sat, 13 Jun 2009 10:35:57 +0100, MM <kylix***@yahoo.co.uk> wrote:

>However, I have now had the opportunity to test it on XP and your
>example works!
>
>Must be a failing of Windows 98SE, I reckon.
>
I've done a bit of digging and Win98 doesn't support kernel-mixing, so
only one app can be 'in control' of the kernel level/hardware at any
one time (even though winmm.dll and the audio drivers are in-process
threaded and asynchronous, and we open the device with a callback to
avoid blocking). I don't think that this is the issue, but something
to bear in mind on Win98.

The Timer control is a system timer which will both limit it's
resolution and WM_TIMER messages are 'low priority' so can be 'bumped'
by other messages such as GUI events, or when system load is high.

You could try a multimedia timer, which runs in it's own thread.
You'll have to be a bit more careful with it and it can make debugging
more difficult (as it still fires in the IDE when you use breakpoints,
stop on error, etc so will crash the IDE).
You are also limited in what calls can be made from within a
multimedia TimerProc because it is running in a seperate thread,
midiOutShortMsg is permitted (it is a multimedia timer after all).

http://www.vbaccelerator.com/home/vb/code/Libraries/HiResTimer/article.asp
and http://www.xtremevbtalk.com/showthread.php?t=96917 have some
detail and examples of using the MM Timer.
--
Alfie [UK]
<http://www.delphia.co.uk/>
They say suffering brings wisdom. I say prepare to have enlightenment beaten into your bitch-ass.