In VB6 (due to client requirements), I need to be able to execute multiple instances of an ActiveX EXE that I wrote to download files to multiple units via RS232.
I have developed a test application that, I think mirrors what I need to do. First, an ActiveX EXE that simulates the download process called TClass. This ActiveX EXE raises events to report back its current progress as thus:
TClass.exe (ActiveX EXE, Instancing = SingleUse, Threading Model = Thread per Object)
Option Explicit
Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)
Public Event Progress(Value As Long)
Public SeedVal As Long
Public Sub MultByTwo()
Dim i As Integer
Dim lVal As Long
lVal = SeedVal
For i = 0 To 10
Sleep (2000)
lVal = lVal * 2
RaiseEvent Progress(lVal)
Next i
Exit Sub
End Sub
Next a wrapper class to instantiate TClass and handle the call-back events (Progress), call it WClass (AxtiveX DLL, Instancing = MultiUse, Apartment Threaded):
Option Explicit
Public WSeedVal As Long
Public WResultVal As Long
Private WithEvents MYF87 As TClass.TargetClass
Private Sub Class_Initialize()
' Set MYF87 = CreateObject("TClass.TargetClass")
Set MYF87 = New TClass.TargetClass
End Sub
Public Function Go() As Integer
MYF87.SeedVal = WSeedVal
MYF87.MultByTwo
End Function
Public Sub MYF87_Progress(Value As Long)
WResultVal = Value
DoEvents
End Sub
Public Function CloseUpShop() As Integer
Set MYF87 = Nothing
End Function
And finally the UI to instantiate WClass. This is a simple forms app:
Option Explicit
Private lc1 As WClass.WrapperClass
Private lc2 As WClass.WrapperClass
Private lc3 As WClass.WrapperClass
Private lc4 As WClass.WrapperClass
Private lc5 As WClass.WrapperClass
Private Sub cmd1_Click()
Set lc1 = CreateObject("WClass.WrapperClass")
lc1.WSeedVal = CInt(txt1.Text)
lc1.Go
End Sub
Private Sub cmd2_Click()
Set lc2 = CreateObject("WClass.WrapperClass")
lc2.WSeedVal = CInt(txt2.Text)
lc2.Go
End Sub
Private Sub cmd3_Click()
Set lc3 = CreateObject("WClass.WrapperClass")
lc3.WSeedVal = CInt(txt3.Text)
lc3.Go
End Sub
Private Sub cmd4_Click()
Set lc4 = CreateObject("WClass.WrapperClass")
lc4.WSeedVal = CInt(txt4.Text)
lc4.Go
End Sub
Private Sub cmd5_Click()
Set lc5 = CreateObject("WClass.WrapperClass")
lc5.WSeedVal = CInt(txt5.Text)
lc5.Go
End Sub
Private Sub Form_Load()
Timer1.Interval = 2000
Timer1.Enabled = True
End Sub
Private Sub Form_Unload(Cancel As Integer)
If Not lc1 Is Nothing Then
lc1.CloseUpShop
Set lc1 = Nothing
End If
If Not lc2 Is Nothing Then
lc2.CloseUpShop
Set lc2 = Nothing
End If
If Not lc3 Is Nothing Then
lc3.CloseUpShop
Set lc3 = Nothing
End If
If Not lc4 Is Nothing Then
lc4.CloseUpShop
Set lc4 = Nothing
End If
If Not lc5 Is Nothing Then
lc5.CloseUpShop
Set lc5 = Nothing
End If
End Sub
Private Sub Timer1_Timer()
If Timer1.Enabled Then
Timer1.Enabled = False
If Not lc1 Is Nothing Then
txtRes1.Text = CStr(lc1.WResultVal)
txtRes1.Refresh
End If
If Not lc2 Is Nothing Then
txtRes2.Text = CStr(lc2.WResultVal)
txtRes2.Refresh
End If
If Not lc3 Is Nothing Then
txtRes3.Text = CStr(lc3.WResultVal)
txtRes3.Refresh
End If
If Not lc4 Is Nothing Then
txtRes4.Text = CStr(lc4.WResultVal)
txtRes4.Refresh
End If
If Not lc5 Is Nothing Then
txtRes5.Text = CStr(lc5.WResultVal)
txtRes5.Refresh
End If
Timer1.Interval = 2000
Timer1.Enabled = True
End If
DoEvents
End Sub
txt1, txt2, txt3, txt4 and txt5 are Text items that provide a seed value that ends up getting passed to TClass as a property. txtRes1, txtRes2, txtRes3, txtRes4 and txtRes5 are Text items to hold the results of TClass.MultByTwo, as reported via the RaiseEvent Progress() call. cmd1, cmd2, cmd3, cmd4 and cmd5 are tied to the corresponding _Click functions above, and instantiate WClass.WrapperClass and get everything going. The form also has a Timer object called Timer1 set to fire every 2 seconds. The only purpose of this is to update the UI from the public properties in WClass.
I have built TClass to TClass.exe and WClass to WClass.dll and referenced WClass.dll from the UI app. When I run the form and click cmd1, the first thing i notice is that the Timer1_Timer no longer fires, so my UI never gets updated. Second, if I click on cmd2, it will fire, but appears to block the execution of the first instance.
I have spent a couple days reading posts and instructions on MSDN... no luck... any help would be greatly appreciated!
Thanks!
Update: I have changed the WClass.dll wrapper class to implement the recommendation of using callback functions. See below:
V2: WClass.dll (ActiveX DLL, Apartment Threading, Instancing = MultiUse)
Option Explicit
Public WSeedVal As Long
Public WResultVal As Long
Public Event WProgress(WResultVal As Long)
Private WithEvents MyTimer As TimerLib.TimerEx
Private WithEvents MYF87 As TClass.TargetClass
Private gInterval As IntervalData
Private Sub Class_Initialize()
Set MyTimer = CreateObject("TimerLib.TimerEx")
' Set MyTimer = New TimerLib.TimerEx
Set MYF87 = CreateObject("TClass.TargetClass")
' Set MYF87 = New TClass.TargetClass
End Sub
Public Function Go() As Integer
gInterval.Second = 1
MyTimer.IntervalInfo = gInterval
MyTimer.Enabled = True
End Function
Private Sub MyTimer_OnTimer()
MyTimer.Enabled = False
MYF87.SeedVal = WSeedVal
MYF87.MultByTwo
End Sub
Public Sub MYF87_Progress(Value As Long)
WResultVal = Value
RaiseEvent WProgress(WResultVal)
DoEvents
End Sub
Public Function CloseUpShop() As Integer
Set MYF87 = Nothing
End Function
Requisite changes in UI Class:
Option Explicit
Private WithEvents lc1 As WClass.WrapperClass
Private WithEvents lc2 As WClass.WrapperClass
Private WithEvents lc3 As WClass.WrapperClass
Private WithEvents lc4 As WClass.WrapperClass
Private WithEvents lc5 As WClass.WrapperClass
Private Sub cmd1_Click()
' MsgBox ("Begin UI1.cmd1_Click")
Set lc1 = CreateObject("WClass.WrapperClass")
lc1.WSeedVal = CInt(txt1.Text)
lc1.Go
' MsgBox ("End UI1.cmd1_Click")
End Sub
Public Sub lc1_WProgress(WResultVal As Long)
txtRes1.Text = CStr(WResultVal)
txtRes1.Refresh
DoEvents
End Sub
Private Sub cmd2_Click()
Set lc2 = CreateObject("WClass.WrapperClass")
lc2.WSeedVal = CInt(txt2.Text)
lc2.Go
End Sub
Public Sub lc2_WProgress(WResultVal As Long)
txtRes2.Text = CStr(WResultVal)
txtRes2.Refresh
DoEvents
End Sub
Private Sub cmd3_Click()
Set lc3 = CreateObject("WClass.WrapperClass")
lc3.WSeedVal = CInt(txt3.Text)
lc3.Go
End Sub
Public Sub lc3_WProgress(WResultVal As Long)
txtRes3.Text = CStr(WResultVal)
txtRes3.Refresh
DoEvents
End Sub
Private Sub cmd4_Click()
Set lc4 = CreateObject("WClass.WrapperClass")
lc4.WSeedVal = CInt(txt4.Text)
lc4.Go
End Sub
Public Sub lc4_WProgress(WResultVal As Long)
txtRes4.Text = CStr(WResultVal)
txtRes4.Refresh
DoEvents
End Sub
Private Sub cmd5_Click()
Set lc5 = CreateObject("WClass.WrapperClass")
lc5.WSeedVal = CInt(txt5.Text)
lc5.Go
End Sub
Public Sub lc5_WProgress(WResultVal As Long)
txtRes5.Text = CStr(WResultVal)
txtRes5.Refresh
DoEvents
End Sub
Private Sub Form_Load()
' Timer1.Interval = 2000
' Timer1.Enabled = True
Timer1.Enabled = False
End Sub
Private Sub Form_Unload(Cancel As Integer)
If Not lc1 Is Nothing Then
lc1.CloseUpShop
Set lc1 = Nothing
End If
If Not lc2 Is Nothing Then
lc2.CloseUpShop
Set lc2 = Nothing
End If
If Not lc3 Is Nothing Then
lc3.CloseUpShop
Set lc3 = Nothing
End If
If Not lc4 Is Nothing Then
lc4.CloseUpShop
Set lc4 = Nothing
End If
If Not lc5 Is Nothing Then
lc5.CloseUpShop
Set lc5 = Nothing
End If
End Sub
I still see the same behavior... Click cmd1, then I see the results start in txtRes1. Click cmd2, results stop updating in txtRes1, and txtRes2 updates until it finishes, then txtRes1 updates.
I would not expect this to work in the VB6 debugger, as it is single-threaded, but creating an executable and running that executable still produces these same results.
I have also tried changing the way my TClass is instantiated (New versus CreateObject) - no difference noticed. I have also tried using New and CreateObject() when instantiating WClass too... still not doing what I would like it to do...