Figure 1   EnumWnds

FrmMain.frm

VERSION 5.00
Begin VB.Form frmMain 
   BorderStyle     =   1  'Fixed Single
   Caption         =   "AddressOf Example - Enumerate Windows"
   ClientHeight    =   4695
   ClientLeft      =   45
   ClientTop       =   330
   ClientWidth     =   5160
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   ScaleHeight     =   4695
   ScaleWidth      =   5160
   StartUpPosition =   2  'CenterScreen
   Begin VB.CommandButton btnEnd 
      Caption         =   "E&nd"
      Height          =   495
      Left            =   2640
      TabIndex        =   2
      Top             =   3960
      Width           =   2295
   End
   Begin VB.CommandButton btnEnumThem 
      Caption         =   "&Enumerate Windows"
      Height          =   495
      Left            =   120
      TabIndex        =   1
      Top             =   3960
      Width           =   2415
   End
   Begin VB.ListBox lstOutput 
      BeginProperty Font 
         Name            =   "Courier New"
         Size            =   9.75
         Charset         =   0
         Weight          =   400
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      Height          =   3420
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   4815
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' John Robbins, Microsoft Systems Journal - August 1997
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Option Explicit

Private Sub btnEnd_Click()
   End
End Sub

Private Sub btnEnumThem_Click()

   lstOutput.Clear
   
   Dim bRet As Long
   bRet = EnumWindows(AddressOf WndEnumProc, lstOutput)
      
End Sub
EnumWnds.bas
Attribute VB_Name = "mod_EnumWnds"
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' John Robbins, Microsoft Systems Journal - August 1997
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Option Explicit

' Declare the API functions.
Declare Function EnumWindows Lib "User32" _
            (ByVal lpEnumFunc As Any, ByVal lParam As Any) As Long

Declare Function GetWindowText Lib "User32" _
            Alias "GetWindowTextA" _
            (ByVal hwnd As Long, _
             ByVal lpString As String, _
             ByVal cch As Long) As Long


Function WndEnumProc(ByVal hwnd As Long, _
                     ByVal lParam As ListBox) As Long

   Dim szTitle As String
   Dim bRet As Long

   szTitle = String(512, 0)

   bRet = GetWindowText(hwnd, szTitle, 512)

   ' Only show those that have titles.
   If (0 <> bRet) Then
      lParam.AddItem szTitle
   End If

   WndEnumProc = 1

End Function

Figure 2   MT.bas

 Attribute VB_Name = "MultiThreaded_Main"
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE **
 ' NEVER RUN THIS PROGRAM IN THE VB IDE!
 ' ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE **
 
 Option Explicit
 
 ' Note:  This declaration of CreateThread does not allow you to pass
 '  thread attributes.  This is the CreateThread that should be used most
 '  often because the parameter for the thread function is passed by
 '  reference.
 Public Declare Function CreateThread Lib "kernel32" _
          (ByVal lpThreadAttributes As Long, _
           ByVal dwStackSize As Long, _
           ByVal lpStartAddress As Any, _
           ByRef lpParameter As Any, _
           ByVal dwCreationFlags As Long, _
           ByRef lpThreadId As Long) As Long
 
 ' If you only are passing a single variable type like an integer or
 '  string as the thread parameter, use this version of CreateThread.
 ' Note that I set the lpParameter type to long.
 Public Declare Function CreateThread_ByValParam Lib "kernel32" _
          Alias "CreateThread" _
          (ByVal lpThreadAttributes As Long, _
           ByVal dwStackSize As Long, _
           ByVal lpStartAddress As Any, _
           ByVal lpParameter As Long, _
           ByVal dwCreationFlags As Long, _
           ByRef lpThreadId As Long) As Long
 
 ' So you can determine which thread the code is executing in.
 Public Declare Function GetCurrentThreadId Lib "kernel32" () As Long
 
 ' This is the type that is passed to DaThreadFunc so you can do the by
 '  reference demonstration.
 Type PARAM_TYPE
    lValue As Long
 End Type
 
 ' The function used to show a pass by reference.
 Function DaThreadFunc(ByRef lpParam As PARAM_TYPE) As Long
    Dim szStr As String
    szStr = "From DaThreadFunc - Parameter = " & _
            CStr(lpParam.lValue) & vbNewLine & _
            "Thread ID: " & CStr(GetCurrentThreadId)
    MsgBox szStr, , "Function Cool!"
    ' Just to make the return value different, I return -2.
    DaThreadFunc = -2
 End Function
 
 ' The subroutine to show pass by value.
 ' By the way, I noticed that subs always return 0.
 Sub DaThreadSub(ByVal lpVoid As Long)
    Dim szStr As String
    szStr = "From DaThreadSub - Parameter = " & _
            CStr(lpVoid) & vbNewLine & _
            "Thread ID: " & CStr(GetCurrentThreadId)
    MsgBox szStr, , "Sub Cool!"
 End Sub
 
 Sub Main()
 
    Dim lRet As Long
    Dim lThreadID As Long
    Dim stParam As PARAM_TYPE
    Dim szStr As String
 
    lThreadID = 0
 
    ' Do the call to CreateThread with a by reference parameter.
    stParam.lValue = -1
    lRet = CreateThread(0, _
                        0, _
                        AddressOf DaThreadFunc, _
                        stParam, _
                        0, _
                        lThreadID)
 
    ' Do the call to CreateThread with a by value parameter.
    lRet = CreateThread_ByValParam(0, _
                                   0, _
                                   AddressOf DaThreadSub, _
                                   -2, _
                                   0, _
                                   lThreadID)
 
    szStr = "From Da Main Thread" & vbNewLine & _
            "Thread ID: " & CStr(GetCurrentThreadId)
 
    MsgBox szStr
 
 End Sub

Figure 4   Event Solution Sample

frmMain.frm

 VERSION 5.00
 Begin VB.Form fmrMain 
    Caption         =   "Form1"
    ClientHeight    =   1740
    ClientLeft      =   60
    ClientTop       =   345
    ClientWidth     =   4680
    LinkTopic       =   "Form1"
    ScaleHeight     =   1740
    ScaleWidth      =   4680
    StartUpPosition =   3  'Windows Default
    Begin VB.CommandButton btnNoWith 
       Caption         =   "Using the output class to fake events!"
       Height          =   855
       Left            =   600
       TabIndex        =   0
       Top             =   360
       Width           =   3375
    End
 End
 Attribute VB_Name = "fmrMain"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = False
 Attribute VB_PredeclaredId = True
 Attribute VB_Exposed = False
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins
 ' Microsoft Systems Journal - August, 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 Private g_ClassNoWith As TheClass
 
 Private g_Output As OutputClass
 
 Private Sub Form_Load()
 
    Set g_ClassNoWith = New TheClass
    Set g_Output = New OutputClass
    ' Set the output class property of the "NoWith" class.
    Set g_ClassNoWith.clsOutput = g_Output
 End Sub
 
 
 Private Sub btnNoWith_Click()
 
    Dim hThread As Long
    Dim lThreadID As Long
 
    ' Create the thread.
 
    hThread = CreateThread(0, _
                           0, _
                           AddressOf TheThread, _
                           g_ClassNoWith, _
                           0, _
                           lThreadID)
 End Sub

OutputClass.cls

 VERSION 1.0 CLASS
 BEGIN
   MultiUse = -1  'True
 END
 Attribute VB_Name = "OutputClass"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = True
 Attribute VB_PredeclaredId = False
 Attribute VB_Exposed = False
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August, 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 Public Sub DoMe(msg As String)
    Dim iRet As Integer
    iRet = MsgBox(msg, Title:="OutputClass")
    
 End Sub

TheClass.cls

 VERSION 1.0 CLASS
 BEGIN
   MultiUse = -1  'True
 END
 Attribute VB_Name = "TheClass"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = True
 Attribute VB_PredeclaredId = False
 Attribute VB_Exposed = False
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August, 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 Public clsOutput As OutputClass
 
 ' The public method that will be called from the thread.
 Public Sub CallMeFromTheThread()
    Dim iRet As Integer
    iRet = MsgBox("TheClass.CallMeFromTheThread did something", _
                   Title:="TheClass")
 End Sub

Thread.bas

 Attribute VB_Name = "Thread"
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August, 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 Public Declare Function CreateThread Lib "kernel32" _
          (ByVal lpThreadAttributes As Long, _
           ByVal dwStackSize As Long, _
           ByVal lpStartAddress As Any, _
           ByRef lpParameter As Any, _
           ByVal dwCreationFlags As Long, _
           ByRef lpThreadId As Long) As Long
 
 Public Function TheThread(clsTheClass As TheClass) As Long
 
    On Error GoTo TheThread_Error
 
    clsTheClass.CallMeFromTheThread
    
    clsTheClass.clsOutput.DoMe ("I'm from the thread!")
    
    Exit Function
    
 TheThread_Error:
    MsgBox ("TheThread had an error : " + Err.Description)
End Function

Figure 5   Thread Test Sample

frmThreadTest.frm

 VERSION 5.00
 Begin VB.Form frmThreadTest 
    BorderStyle     =   1  'Fixed Single
    Caption         =   "Thread Tester!"
    ClientHeight    =   1725
    ClientLeft      =   45
    ClientTop       =   330
    ClientWidth     =   5805
    LinkTopic       =   "Form1"
    MaxButton       =   0   'False
    ScaleHeight     =   1725
    ScaleWidth      =   5805
    StartUpPosition =   2  'CenterScreen
    Begin VB.CommandButton btnPause 
       Caption         =   "&Pause Thread"
       Height          =   615
       Left            =   1533
       TabIndex        =   3
       Top             =   960
       Width           =   1335
    End
    Begin VB.CommandButton btnStartThread 
       Caption         =   "&Start Thread"
       Height          =   615
       Left            =   131
       TabIndex        =   2
       Top             =   960
       Width           =   1335
    End
    Begin VB.CommandButton btnStopThread 
       Caption         =   "Stop &Thread"
       Height          =   615
       Left            =   2935
       TabIndex        =   4
       Top             =   960
       Width           =   1335
    End
    Begin VB.CommandButton btnEnd 
       Caption         =   "&End"
       Height          =   615
       Left            =   4338
       TabIndex        =   5
       Top             =   960
       Width           =   1335
    End
    Begin VB.Label lblSeconds 
       Caption         =   "0.0"
       BeginProperty Font 
          Name            =   "MS Sans Serif"
          Size            =   24
          Charset         =   0
          Weight          =   700
          Underline       =   0   'False
          Italic          =   0   'False
          Strikethrough   =   0   'False
       EndProperty
       ForeColor       =   &H000000FF&
       Height          =   495
       Left            =   4200
       TabIndex        =   1
       Top             =   240
       Width           =   1335
    End
    Begin VB.Label lblTitle 
       Caption         =   "Look Ma!  No timers, just real threads!"
       BeginProperty Font 
          Name            =   "MS Sans Serif"
          Size            =   9.75
          Charset         =   0
          Weight          =   700
          Underline       =   0   'False
          Italic          =   0   'False
          Strikethrough   =   0   'False
       EndProperty
       Height          =   495
       Left            =   120
       TabIndex        =   0
       Top             =   240
       Width           =   3855
    End
 End
 Attribute VB_Name = "frmThreadTest"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = False
 Attribute VB_PredeclaredId = True
 Attribute VB_Exposed = False
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 ' This is the static thread handle that will be used by this form
 '  to store the executing thread.  If it is 0, then the background
 '  thread is not executing.
 Private g_hThread As Long
 
 ' The array of event handles that are used to coordinate the background
 '  thread.  There are two events here.  The first is the stop event
 '  that when signaled means that the background thread is supposed
 '  to end.  The second event is the event that is signaled when the
 '  background thread is supposed to be updating the label control
 '  with the count.  If it is nonsignaled, then the background thread
 '  is paused.  The second event is the paused/running event.
 Private g_ahSynchEvents(2) As Long
 
 ' The flag that indicates the state of the thread so the UI can show the
 '  state.
 Private g_bThreadIsPaused As Boolean
 
 ' The class that holds the label object that will be passed to the
 '  thread.  This is done because trying to pass the actual label
 '  as the lpParameter to CreateThread always passed zero which obviously
 '  will crash.
 Private g_clsLabel As LabelClass
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' Handles when the form is loaded.
 Private Sub Form_Load()
 
    ' Set the statics to known values, I trust nothing.
    g_hThread = 0
    g_ahSynchEvents(0) = 0
    g_ahSynchEvents(1) = 0
    g_bThreadIsPaused = False
 
    ' Instantiate the class.
    Set g_clsLabel = New LabelClass
    ' Initialize the label class so that you can really pass the label
    '  to the thread as the parameter.
    Set g_clsLabel.m_lblLabel = lblSeconds
 
    ' Disable the stop thread and pause buttons.
    btnStopThread.Enabled = False
    btnPause.Enabled = False
 
 End Sub
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' Called when the form unloads.  If the background thread is running,
 '  then it is killed.
 Private Sub Form_Unload(Cancel As Integer)
 
    ' Shut down the thread if needed.
    KillTheThread
 
    Set g_clsLabel.m_lblLabel = Nothing
 
 End Sub
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' The "End" button is pressed.
 Private Sub btnEnd_Click()
 
    Unload frmThreadTest
 
 End Sub
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' The user wants to start the thread.
 Private Sub btnStartThread_Click()
 
    ' Make sure that you only get in here if no thread is executing.
 #If DEBUGBUILD Then
       If (0 <> g_hThread) Then
          MsgBox ("btnStartThread_Click: g_hThread <> 0")
       End If
 #End If
 
    ' Create the synchronization objects now.
    CreateSynchEvents g_ahSynchEvents
 
    ' Holds the handle id returned by CreateThread.
    Dim lThreadID As Long
 
    ' Create the thread.
    g_hThread = CreateThread(0, _
                             0, _
                             AddressOf TheThread.TheThreadSub, _
                             g_clsLabel, _
                             0, lThreadID)
 
 #If DEBUGBUILD Then
       If (0 = g_hThread) Then
          MsgBox ("btnStartThread_Click: g_hThread <> 0")
       End If
 #End If
 
    If (0 <> g_hThread) Then
       ' Disable the start thread button so you don't create extra
       '  threads.
       btnStartThread.Enabled = False
 
       ' Enable the end thread button so you can kill it.
       btnStopThread.Enabled = True
 
       ' Enable the pause button.
       btnPause.Enabled = True
 
    Else
       ' We didn't get started so kill the synch objects.
       Dim bRet As Long
 
       bRet = CloseHandle(g_ahSynchEvents(0))
 
 #If DEBUGBUILD Then
       If (0 = bRet) Then
          MsgBox ("CloseHandle(g_ahSynchEvents(0)) failed")
       End If
 #End If
 
       bRet = CloseHandle(g_ahSynchEvents(1))
 
 #If DEBUGBUILD Then
       If (0 = bRet) Then
          MsgBox ("CloseHandle(g_ahSynchEvents(1)) failed")
       End If
 #End If
 
    End If
 
 End Sub
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' The user wants to pause the thread.
 Private Sub btnPause_Click()
 
 #If DEBUGBUILD Then
       If (0 = g_hThread) Then
          MsgBox ("btnStartThread_Click: g_hThread = 0")
       End If
 #End If
 
    Dim bRet As Long
 
    ' Is the thread already paused?  If it is, then you need to set the
    '  update event, the second in the array, and put the "Pause Thread"
    '  text back on the button.
    If (True = g_bThreadIsPaused) Then
       ' Reset the flag.
       g_bThreadIsPaused = False
       ' Set the text back.
       btnPause.Caption = "&Pause Thread"
       ' Set the event to signaled.
       bRet = SetEvent(g_ahSynchEvents(1))
 
 #If DEBUGBUILD Then
       If (0 = bRet) Then
          MsgBox ("SetEvent(g_ahSynchEvents(1)) failed")
       End If
 #End If
 
    Else
 
       ' Set the flag.
       g_bThreadIsPaused = True
       ' Set the button caption.
       btnPause.Caption = "&Resume Thread"
       ' Set the paused/running event to nonsignaled.
       bRet = ResetEvent(g_ahSynchEvents(1))
 
 #If DEBUGBUILD Then
       If (0 = bRet) Then
          MsgBox ("ResetEvent(g_ahSynchEvents(1)) failed")
       End If
 #End If
 
    End If
 
 End Sub
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' The user wants to stop the thread.
 Private Sub btnStopThread_Click()
 
 #If DEBUGBUILD Then
       If (0 = g_hThread) Then
          MsgBox ("btnStopThread_Click: g_hThread = 0")
       End If
 #End If
 
    KillTheThread
 
    ' If the thread is paused, then clear the flag and set the button
    '  back to the proper text.
    If (True = g_bThreadIsPaused) Then
       ' Reset the flag.
       g_bThreadIsPaused = False
       ' Set the text back.
       btnPause.Caption = "&Pause Thread"
    End If
 
    ' Reset the state of the buttons.
    btnStartThread.Enabled = True
    btnStopThread.Enabled = False
    btnPause.Enabled = False
 
 End Sub
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' A private routine to end the background thread.  This is here because
 '  it needs to be called from the "Stop" button and during the form
 '  unload.
 Private Sub KillTheThread()
 
    If (0 <> g_hThread) Then
       ' Just kill the thread!
       Dim bRet As Long
       ' It is, so signal the quit event then shut down.
       bRet = SetEvent(g_ahSynchEvents(0))
 
 #If DEBUGBUILD Then
       If (0 = bRet) Then
          MsgBox ("SetEvent(g_ahSynchEvents(0)) failed")
       End If
 #End If
 
       ' Wait for the thread to end.
       bRet = WaitForSingleObject(g_hThread, INFINITE)
 
       ' Close down the synchronization handles.
       bRet = CloseHandle(g_ahSynchEvents(0))
 
 #If DEBUGBUILD Then
       If (0 = bRet) Then
          MsgBox ("CloseHandle(g_ahSynchEvents(0)) failed")
       End If
 #End If
 
       bRet = CloseHandle(g_ahSynchEvents(1))
 
 #If DEBUGBUILD Then
       If (0 = bRet) Then
          MsgBox ("CloseHandle(g_ahSynchEvents(1)) failed")
       End If
 #End If
 
       g_hThread = 0
 
    End If
 
 End Sub
Globals.bas
 Attribute VB_Name = "Globals"
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' The name of the synchronization object that, when signaled, is used to
 '  update the label.
 Public Const SYNCHEVENT_UPDATELABEL As String = "ThreadTest_UpdateLabel"
 ' The name of the synchronization object that, when signaled, tells the
 '  background thread to end.
 Public Const SYNCHEVENT_ENDTHREAD As String = "ThreadTest_EndThread"
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Public Declare Function CreateThread _
         Lib "kernel32" (ByVal lpThreadAttributes As Long, _
                         ByVal dwStackSize As Long, _
                         ByVal lpStartAddress As Any, _
                         ByRef lpParameter As Any, _
                         ByVal dwCreationFlags As Long, _
                         ByRef lpThreadId As Long) As Long
 
 Public Declare Function TerminateThread _
         Lib "kernel32" (ByVal hThread As Long, _
                         ByVal dwExitCode As Long) As Long
 
 Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
 
 Declare Function CreateEvent _
          Lib "kernel32" _
                Alias "CreateEventA" _
          (ByVal lpEventAttributes As Long, _
          ByVal bManualReset As Long, _
          ByVal bInitialState As Long, _
          ByVal lpName As String) As Long
 
 Declare Function CloseHandle _
          Lib "kernel32" _
          (ByVal hObject As Long) As Long
 
 Declare Function WaitForMultipleObjects _
          Lib "kernel32" (ByVal nCount As Long, _
                          ByRef lpHandles As Long, _
                          ByVal bWaitAll As Long, _
                          ByVal dwMilliseconds As Long) As Long
 
 Declare Function WaitForSingleObject _
          Lib "kernel32" (ByVal hHandle As Long, _
                          ByVal dwMilliseconds As Long) As Long
 
 
 Declare Function SetEvent _
          Lib "kernel32" (ByVal hEvent As Long) As Long
 
 
 Declare Function ResetEvent _
          Lib "kernel32" (ByVal hEvent As Long) As Long
 
 Public Const INFINITE = &HFFFF
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' This is the helper function that will create the appropriate events
 '  for both the main thread and the background thread.
 Sub CreateSynchEvents(ahEvents() As Long)
 
    ' The first event is the stop event so it is a manual-reset event
    '  that is created NONSIGNALED.
    ahEvents(0) = CreateEvent(0, 1, 0, SYNCHEVENT_ENDTHREAD)
 
 #If DEBUGBUILD Then
       If (0 = ahEvents(0)) Then
          MsgBox ("CreateSynchEvents: ahEvents(0) = 0")
       End If
 #End If
 
    ' The second event is the paused/running event.  It is created as a
    '  manual-reset event that starts out as signaled.
    ahEvents(1) = CreateEvent(0, 1, 1, SYNCHEVENT_UPDATELABEL)
 
 #If DEBUGBUILD Then
       If (0 = ahEvents(1)) Then
          MsgBox ("CreateSynchEvents: ahEvents(1) = 0")
       End If
 #End If
 
 End Sub
LabelClass.cls
 VERSION 1.0 CLASS
 BEGIN
   MultiUse = -1  'True
 END
 Attribute VB_Name = "LabelClass"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = True
 Attribute VB_PredeclaredId = False
 Attribute VB_Exposed = False
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August, 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 Public m_lblLabel As Label
TheThread.bas
 Attribute VB_Name = "TheThread"
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ' John Robbins, Microsoft Systems Journal - August 1997
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 Option Explicit
 
 ' This is the procedure that will be used as the
 '  background thread.
 
 Public Sub TheThreadSub(clsLabel As LabelClass)
 
    ' The array of event handles that are used to coordinate the
    '  background thread.  There are two events here.  The first is the
    '  stop event that when signaled means that the background thread is
    '  supposed to end.  The second event is the event that is signaled
    '  when the background thread is supposed to be updating the label
    '  control with the count.  If it is nonsignaled, then the
    '  background thread is paused.  The second event is the
    '  paused/running event.
    Dim ahSynchEvents(2) As Long
 
    ' Create the synchronization events.
    CreateSynchEvents ahSynchEvents
 
    ' The number of seconds that we have been running.
    Dim lSecs As Long
    lSecs = 0
 
    ' All that is done in here is just spin around updating the label
    '  text until this thread gets killed.  It's kind of boring,
    '  actually.
    While True
 
       Dim bRet As Long
       Dim lIndex As Long
       lIndex = WaitForMultipleObjects(2, ahSynchEvents(0), 0, INFINITE)
 
       Select Case lIndex
          ' If it is the first one, then stop.
          Case 0
             ' Clean up after ourselves.
             bRet = CloseHandle(ahSynchEvents(0))
 
 #If DEBUGBUILD Then
             If (0 = bRet) Then
                MsgBox ("CloseHandle(ahSynchEvents(0)) failed")
             End If
 #End If
 
             bRet = CloseHandle(ahSynchEvents(1))
 
 #If DEBUGBUILD Then
             If (0 = bRet) Then
                MsgBox ("CloseHandle(ahSynchEvents(1)) failed")
             End If
 #End If
 
             Exit Sub
 
          Case 1
             ' Show how long the thread has been running.  This does not
             '  handle wrap around on the time.
             clsLabel.m_lblLabel.Caption = lSecs / 100
             ' Sleep for a bit.
             Sleep (100)
             lSecs = lSecs + 1
 
          Case Else
 #If DEBUGBUILD Then
             MsgBox ("Case Else hit!!")
 #End If
 
       End Select
    Wend
 
End Sub