Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Wanet Telecoms Ltd on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Raising events from .bas modules in an ActiveX DLL

Status
Not open for further replies.

MuadDubby

Programmer
Sep 23, 1999
236
CA
Here's a complex one ...

I've created an ActiveX DLL (in VB6) that has one method and one event it can raise. The method executes a function in a .bas module that can take up to about 20 seconds to complete it's work, and I'm looking for a way let the main program (which is executing the COM method) know what's going on.

The event is basically there to throw a new status message back to the parent app whenever a new message is available, so that's the route I've picked.

The problem is that since all the logic is in the .bas module, I do not have access to the event and therefore cannot raise it. For all intents and purposes, it looks like only methods in the Class (.cls file) are allowed to raise it.

I've thought of having a method in that class be solely responsible for raising this event, and then whenever my module functions want to send a status message back to the app, they'll call this new method and he will raise the exception. Problem is - I don't seem to have access to these methods. I tried passing them as callbacks, but that is not allowed either.

My code is turning into a bit of sphagetti at this point (more commented code than anything else), so I'm not sure how to proceed.

Any ideas/suggestions would be greatly appreciated.

I know some code samples (on my part) would be helpful. I'll try to clean this mess up a bit and post a snipet.

Thx in advance.
 
Events are declared and raised in a class module. Couldn't you just raise the event in the method that calls the function in the BAS module? I.E.
Code:
'Class module code
Public Event StuffIsDone()

Public Sub DoStuff()
    'This takes up to 20 seconds
    Call MyFunction()

    'We're back!
    RaiseEvent StuffIsDone()
End Sub

An alternative is to move the BAS module function over to the class and turn it into a method. Then you could raise the event from there.
 
Hi Joe

The first method is a bit impractical since I'm seeking to inform the user of the different statuses that arise during the 20 seconds it takes to do the whole thing. Otherwise, the purpose of keeping the user informed (and showing some kind of movemnt on the screen) is not met.

The second idea isn't too bad. I'll have quite a few changes to do, but this might just do it.

Thx for the tips :)

 
I would NOT use a .bas module in a class. As a general rule, it's not a good idea to include .bas modules in a COM object. Not that they don't work, but the architecture can become confusing, as you seem to be learning.

On the other hand, you don't need to create your function as a method either. Just move the function into the class module and make it private.

So, here are some additions to Joe's code:
Code:
'Let's say this class is called MyClass
Public Event StuffIsDone()

Public Sub DoStuff()
    'This takes up to 20 seconds
    Call MyFunction()

    'We're back!
    RaiseEvent StuffIsDone()
End Sub

Private Sub MyFunction()
'Do whatever
End Sub
And then, in your client:
Code:
dim [COLOR=red]WithEvents [/color] myObject as MyClass 'You might have left out the WithEvents keyword?

Private Sub Form_Load() 'For example
set myObject = new MyClass
myObject.DoStuff
End Sub

Private Sub myObject_StuffIsDone
'Do whatever
End Sub

Points are:
1. Declare the event in your class module.
2. Use RaiseEvent to raise it somewhere in the class module.
3. When you declare the class variable in your client, make sure that you use the WithEvents keyword. VB requires this to avoid the code overhead associated with event support if you don't want it.
4. Write an event handler for your class instance.

HTH

Bob
 
Hey bob I thought functions used for a callback were supposed to be in a .bas module?
 
Yeah, but the function you're going to call back is in the client, not in the class module, right?
 
Worth pointing out that that "Raising events from .bas modules in an ActiveX DLL" is perfectly feasible with only a teeny bit of extra code (and almost vital if you want o utilise callbacks, such as when trying to create your own timers)...
 
I've been looking for an example of asynchronous callbacks for quite a while, strongm. Microsoft has buried the "coffee pot" application somewhere, and I can't find it. Do you have an example you can point me to of what you're explaining?

 
Here's my Timer class that illustrates what I was talkng about. It needs to be a singleuse ActiveX Exe. I callit clsTimer. Here's the interface from the class module:
Code:
[blue]Option Explicit

Private Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long

Private mvarEnabled As Boolean
Private mvarInterval As Long
Private mvarTimerID As Long

Public Event Timer()

Public Property Let Interval(ByVal vdata As Long)
    mvarInterval = vdata
    ToggleTimer
End Property


Public Property Get Interval() As Long
    Interval = mvarInterval
End Property

Public Property Let Enabled(ByVal vdata As Boolean)
    mvarEnabled = vdata
    ToggleTimer
End Property

Public Property Get Enabled() As Boolean
    Enabled = mvarEnabled
End Property

Friend Sub RaiseIt(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long)
    If idEvent = mvarTimerID Then
    RaiseEvent Timer
    End If
End Sub

Private Sub Class_Terminate()
    If mvarTimerID Then
        KillTimer 0, mvarTimerID
    End If
End Sub

' Are we raising events?
Private Sub ToggleTimer()
    If mvarEnabled = True And mvarInterval > 0 Then
        mvarTimerID = SetTimer(0, 0, mvarInterval, TimerProcAddress(Me))
    ElseIf mvarTimerID Then
        KillTimer 0, mvarTimerID
        mvarTimerID = 0
    End If
End Sub[/blue]
And here's the BAS module that also goes in the class
Code:
[blue]' This whole support module exists merely to get around the fact that we cannot use AddressOf
' in a class module. But we *can* use it in a normal module in an ActiveX exe or DLL
' Additionally we need to make our class SingleUse - which means an ActiveX exe - to ensure that each vbTimer get's it's own
' timerproc
Option Explicit
Public CurrentTimer As vbTimer

' Use this to get around one of the limitations of AddressOf
Public Function ReturnAddress(lpAddress As Long) As Long
    ReturnAddress = lpAddress
End Function

' This is the timer callback function
Public Sub TimerProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long)
    Call CurrentTimer.RaiseIt(hwnd, uMsg, idEvent, dwTime)
End Sub

Public Function TimerProcAddress(myTimer As vbTimer) As Long
    Set CurrentTimer = myTimer
    TimerProcAddress = ReturnAddress(AddressOf TimerProc)
End Function[/blue]

And you might use it from a form as follows (note that this is basically a non-control version of VB's Timer):
Code:
[blue]Option Explicit
Public WithEvents myTimer As vbTimer

Private Sub Form_Load()
    Set myTimer = New vbTimer
    myTimer.Enabled = True
    myTimer.Interval = 1000
End Sub

Private Sub myTimer_Timer()
    Debug.Print "Hello"
End Sub
[/blue]
 
Thank you strongm. I've put this on my favorites list. I can't get to it for a few days, but I will have a good look at it.
 
Bob,

Have you noticed the enw archive functionality?

Near the top left corner, there is a 'my archives'.
Along the right side, just above the MVPs, is... 'add to my archive'.

Works better than favorites in my opinion.

-George

Strong and bitter words indicate a weak cause. - Fortune cookie wisdom
 
Thanks George,

I never knew about that. I'm sure I'll use it often now.

Bob
 
It gets trickier for multiuse classes.

The stuff you call and pass a callback routine to must accept some sort of "ID" that is (or can be mapped to) an instance index for your class. Of course they then need to pass the ID to the callback routine when it is called.

The nIDEvent parameter of SetTimer() is one such. Overlapped I/O routines typically take an OVERLAPPED structure with an hEvent member you can use for the same purpose.

The trick then is to have some "array" of object references to each instance of your class, so the callback in the std module can call back into the right object instance (usually a Friend method that raises the desired event).

The slickest way to do this is to have your class be "non-creatable" and actually create it using a "parent" class. The parent class needs a method used to request new instances of the child class with the embedded callback.

That method creates an instance of this non-creatable child class, sets the instance's ID by setting a Friend property on the child, caches a reference in a global array or Collection over in the .bas module, and passes a reference back to the requestor.

The child object needs to delete itself from the list when "destroyed" which needs to be done via an explicit method call - or else the reference in the list will keep the instance "alive beyond its years." Of course program termination should clean up any nasties... but the "program" here is the library not the user program. This is one of those "memory leak" things we worry about.

This has less overhead when using multiple instances of the "callback enabled" class than when designing it as a single-use class. Of course it is slightly more complex.


At least I think that's how it works, I may have to dig up an old program and take a look.
 
>actually create it using a "parent" class

What we might, in fact, typically call a 'factory' class
 
Yes, I believe that's the standard term. In class hierarchies of this type one also often assigns this responsibility to a root object in the object model, though clearly you might have such a factory class anywhere in the hierarchy.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top