2

I have researched, and read just about everything I could find on this. It's very likely that I have seen the solution to my problem, but fail to recognize it. It seems the forest is too thick for me.

I have written a dll in vb.net - VS2010. It works perfectly when used in a vb.net project. I have enabled the COM, etc. to allow it to work with VB6.

Up until I need the events, it works just as planned in VB6. But now trying to get the WithEvents part to work is where I'm completely lost!

My VB.Net dll raises an event once a time compare function becomes true (or equal). It also returns the actual time of the event.

In my VB6 project, using the WithEvents function requires the declaration be within a class module. I can't get my head around how this would/should work. As I stated earlier, many post I have read point to the fact it must be in a class module, but none address exactly how to accomplish this in a clean straight forward method. Well, at least what seems straight-forward to me, anyway.

The following code is how I make use of the dll prior to adding an event. All properties, and actions, etc. work correctly. This is located in a .bas module.

Public PT As New PrecisionTimer.PrecisionTimer

This is example of how it might be used on a form:

Private Sub Command3_Click()
Dim aa As Integer

    aa = PT.SetTimerEvent(Val(Text6.Text))
    Select Case aa
        Case -1
            OkToInitiate = True
        Case False
            OkToInitiate = False
    End Select

End Sub

But now I need to react to the event within the dll. Here is the line in the VB.Net dll that raises the event:

RaiseEvent OTE(ActualTriggeredTime.ToString())

So, can someone assist me, I'm really having a lot of trouble figuring out how to accomplish this.

Thanks!

More Info:
@TnTinMn, To a point you are right, CM has never been my strong suit. Here is the code in MyClass:

Option Explicit

Public WithEvents PTM As PrecisionTimer.PrecisionTimer

Private Sub PTM_OTE(ByVal ActualTriggeredTime As Variant)
Dim aa As Integer
   aa = 1
End Sub

Now the PTM_OTE sub can be deleted, and a new one will automatically appear, without the aa = 1 code of course.

In my Module1.bas:

Option Explicit
Public PT As New MyClass.PTM
Public OkToInitiate As Boolean

Do you see an error in my code?

In the Object Browser, I do see the OTE Event. At this point, I'm thinking I should be able to see PT in the Code Editor drop down in the upper left hand corner, but I don't?!?!

More info:
I added an additional function to the event routine in the VB.Net dll - a TriggeredFlag.

Public Sub OnTimedEvent(source As Object, e As ElapsedEventArgs)
   'Other code including basic error handling       
   RaiseEvent OTE(ActualTriggeredTime.ToString())
   TriggeredFlag = True
End Sub

Now when I use the dll in my vb6 project, I can monitor the TriggeredFlag. It does change as expected. Yet the Event doesn't seem to be received. I beleive the problem is in the VB6 code - the WithEvents statement (maybe) in the class module. Here is my entire Class Module:

Option Explicit

Public WithEvents PTM As PrecisionTimer.PrecisionTimer

Public Sub Class_Initialize()
    Set PTM = New PrecisionTimer.PrecisionTimer
End Sub

Private Sub PTM_OTE(ByVal ActualTriggeredTime As String)
Dim aa As Integer
    aa = 1
End Sub

Final additional info:

Thanks to all that assisted in resolving this! Here is what my Module1.Bas ended up as:

Option Explicit

Public PT As New PrecisionTimer.PrecisionTimer
Public DT As New DTParams

Public OkToInitiate As Boolean

In the General section of the form:

Option Explicit

Public WithEvents TimerEvents As PrecisionTimer.PrecisionTimer

The Form_load routine includes:

Set TimerEvents = PT

This is the TimerEvents_OTE routine:

Private Sub TimerEvents_OTE(ByVal ActualTriggeredTime As String)
'Fires when the "Timer Done" event fires

    Call SetTimerText(ActualTriggeredTime)

End Sub

And finally, no Class Module was used.

Thanks again all!

Ken
  • 39
  • 1
  • 4
  • You don't explicitly subscribe events in VB6, the convention is to give the event handler procedure the correct name. Ought to resemble Sub PT_OTE(time As String) with these snippets, assuming PT is the variable that was declared WithEvents and OTE is the name of the event. Otherwise the same idea as Command3_Click, handles the Click event for an object named Command3. – Hans Passant Mar 24 '18 at 21:51
  • @Hans, Yes you are correct - PT would be the variable declared with WithEvents, and OTE is the name of the event. 1. I'm not clear about where the event handler resides. 2. Shouldn't the intellesense show the PT Sub? – Ken Mar 24 '18 at 22:23
  • Since you have not shown us the VB.Net code and how you are exposing it to COM, I will direct you to the article: [Walkthrough: Creating COM Objects with Visual Basic](https://msdn.microsoft.com/en-us/library/x66s8zcd(v=vs.110).aspx). This makes it pretty painless. – TnTinMn Mar 24 '18 at 22:45
  • @TnTinMn Yes, this is the method I originally used. I also just re-verified that that is how my code is. – Ken Mar 24 '18 at 23:07
  • @TnTinMn, To a point you are right, CM has never been my strong suit, but...Here is the code in MyClass: Option Explicit Public WithEvents PTM As PrecisionTimer.PrecisionTimer Private Sub PTM_OTE(ByVal ActualTriggeredTime As Variant) Dim aa As Integer aa = 1 End Sub – Ken Mar 25 '18 at 01:13
  • @TnTinMn, To a point you are right, CM has never been my strong suit, but...Here is the code in MyClass: Option Explicit Public WithEvents PTM As PrecisionTimer.PrecisionTimer Private Sub PTM_OTE(ByVal ActualTriggeredTime As Variant) Dim aa As Integer aa = 1 End Sub Now the PTM_OTE sub can be deleted, and a new one will automatically appear, with out the aa = 1 code. In my Module1.bas: Option Explicit Public PT As New MyClass.PTM Public OkToInitiate As Boolean Do you see an error in may code? – Ken Mar 25 '18 at 01:30
  • Do you see an error in may code? In the Object Browser, I do see the OTE Event. At this point, I'm thinking I should be able to see PT in the Code Editor drop down in the upper left hand corner, but I don't?!?! – Ken Mar 25 '18 at 01:30
  • Ok, I moved it. I corrected the String/Variant issue and added the Init routine. Now when I compile I get an error in the .bas module. Public PT As New MyClass.PT "User Type not defined" – Ken Mar 25 '18 at 02:32

3 Answers3

3

You seem reluctant to share your .Net code. To give you a working example, please try to implement the following example that will detail the steps for creating a working class library exposed to COM. I do not have VB6 installed, but have tested this using MS Office VBA. It is my understanding that VBA is the language and VB6 is a host for the language. For more information on this see: Difference between Visual Basic 6.0 and VBA.

Step 1: Create a COM Class library. Start Visual Studio as an Administrator. This is so that VS can register the COM components in the registry. Create a new Project from the "Empty Project (.Net Framework) - Visual Basic" Template. I named the project "Demo COM Exposed Event". Go to the project properties - Application Tab and set the "Application Type" to "Class Library". You can also clear the "Root Namespace" box to force everything to be in the Global Namespace, but this is not mandatory as COM does not recognize namespaces. App Tab

Next open the Build Configuration Manager to define and active an X86 (32-bit) configuration for the project.

enter image description here

enter image description here

enter image description here

enter image description here

Next configure the project to Register the COM class. This is done on the "Compile Tab" in the project properties.

enter image description here

The VB.Net code below defines COMDemoClass. This class exposes a single event (Event1), a method (SingleFireEvent1) to raise the event, and a boolean property (AutoFireEvent1) that starts/stops a timer that can fire Event1.

Imports System.Runtime.InteropServices
Imports System.Timers

<ComClass(COMDemoClass.ClassId, COMDemoClass.InterfaceId, COMDemoClass.EventsId)>
Public Class COMDemoClass

#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class 
    ' and its COM interfaces. If you change them, existing 
    ' clients will no longer be able to access the class.
    Public Const ClassId As String = "db10634a-91fe-4cde-a2da-5421578e6142"
    Public Const InterfaceId As String = "22d6da07-b40c-4a04-9551-9c375bf99c06"
    Public Const EventsId As String = "2096fe58-d50c-4d19-9039-a6ad4193385c"
#End Region

    <ComVisible(False)>
    Delegate Sub _Event1(arg As String)
    Public Event Event1 As _Event1
    Private tmr As New System.Timers.Timer With {.AutoReset = True, .Interval = 500, .Enabled = False}
    Private _AutoFireEvent1 As Boolean

    ' A creatable COM class must have a Public Sub New() 
    ' with no parameters, otherwise, the class will not be 
    ' registered in the COM registry and cannot be created 
    ' via CreateObject.
    Public Sub New()
        MyBase.New()
        AddHandler tmr.Elapsed, AddressOf tmr_Elapsed
    End Sub

    Private Sub tmr_Elapsed(sender As Object, e As ElapsedEventArgs)
        SingleFireEvent1(DateTime.Now.ToLongTimeString())
    End Sub

    Public Sub SingleFireEvent1(Optional arg As String = "Hello")
        RaiseEvent Event1(arg)
    End Sub

    Public Property AutoFireEvent1 As Boolean
        Get
            Return _AutoFireEvent1
        End Get
        Set(value As Boolean)
            If value <> _AutoFireEvent1 Then
                _AutoFireEvent1 = value
                tmr.Enabled = value
            End If
        End Set
    End Property
End Class

At this point, build the project to generate the Dll and type library. These should be viewable in the Solution Explorer.

Generated Class and Tlb

Step 2: Consume the library in VBA code.

In the VB IDE, add a project reference to the type library created in Step 1.

Add Proj Ref

Add a "Class Module" and name it "ExposeVBNetLib" and add this code to the module. Note that you could add similar code to a Form or other class type module; you just can not define a WithEvents variable in a standard Module.

    Option Explicit

    Private WithEvents vbNetCOMClass As Demo_COM_Exposed_Event.COMDemoClass

    Private Sub Class_Initialize()
       Set vbNetCOMClass = New Demo_COM_Exposed_Event.COMDemoClass
    End Sub

    Private Sub Class_Terminate()
       Set vbNetCOMClass = Nothing
    End Sub

To test the VBA class, add the following code to a standard Module.

Option Explicit

Private instance As ExposeVBNetLib  ' a variable to keep the class reference alive between methods

Sub CreateAndLaunch()
   Set instance = New ExposeVBNetLib
   instance.SingleFireEvent1 "It works"
   instance.AutoFireEvent1 = True
End Sub

Sub ShutDown_instance()
   If Not (instance Is Nothing) Then
      instance.AutoFireEvent1 = False
      Set instance = Nothing
   End If
End Sub

Private Sub vbNetCOMClass_Event1(ByVal arg As String)
   Debug.Print arg
End Sub

Public Property Get AutoFireEvent1() As Boolean
   AutoFireEvent1 = vbNetCOMClass.AutoFireEvent1
End Property

Public Property Let AutoFireEvent1(arg As Boolean)
  vbNetCOMClass.AutoFireEvent1 = arg
End Property

Public Sub SingleFireEvent1(arg As String)
   vbNetCOMClass.SingleFireEvent1 arg
End Sub

This class subscribes to Event1 of the COM class COMDemoClass and handles the event in the method vbNetCOMClass_Event1. This method prints the received argument to the "Immediate Window".

Step 3 - Test It

In a standard VBA Module, place the following code:

Option Explicit

Private instance As ExposeVBNetLib  ' a variable to keep the class reference alive between methods

Sub CreateAndLaunch()
   Set instance = New ExposeVBNetLib
   instance.SingleFireEvent1 "It works"
   instance.AutoFireEvent1 = True
End Sub

Sub ShutDown_instance()
   If Not (instance Is Nothing) Then
      instance.AutoFireEvent1 = False
      Set instance = Nothing
   End If
End Sub

Run the method CreateAndLaunch to start the code. Run the method ShutDown_instance to stop the code.

Test Run GIF

Lastly, you should be able to debug your .Net code with in Visual Studio by configuring the project to launch the VBA host. In my case, this is Excel and your VB6. This is done from the "Debug Tab" under project properties by setting the "Start External Program" under the "Start Options".

enter image description here

TnTinMn
  • 11,522
  • 3
  • 18
  • 39
  • Thanks TnTinMn! I was able to use Phil Jollans method to accomplish the task. It's not that I'm reluctant to share, it's quite large, and I wasn't certain it was relevant. Thank you for spending the time to put together this step-by-step. I'm going to study it and make sure I understand it so that if this comes up again, I can solve it. – Ken Mar 26 '18 at 00:21
1

WithEvents does not have to be in a class module. It can be in

  • a class module
  • a user control
  • a form

but it cannot be in a module.

Your command handler (Command3_Click) is in a form. You can declare the timer in the same form.

Public WithEvents TimerEvents As New PrecisionTimer.PrecisionTimer

Since you have named it PT in the module, I would use a different name in the form, e.g. TimerEvents.

After you have declared this variable, you can select it in the list at the top left of the editor window...

enter image description here

and then select the event in the list at the top right of the editor window...

enter image description here

After this VB6 will automatically insert the event handler, which you can see at the bottom of the screen shot.

In your command handler, you must store a reference to the timer object in the new variable

Set TimerEvents = PT

So the complete function would look like this

Private Sub Command3_Click()

    Dim aa As Integer

    Set TimerEvents = PT

    aa = PT.SetTimerEvent(Val(Text6.Text))
    Select Case aa
        Case -1
            OkToInitiate = True
        Case False
            OkToInitiate = False
    End Select

End Sub

However, my guess it that there is a better place to initialize the TimerEvents variable.

If you want to stop handling events, set the variable to Nothing.

Set TimerEvents = Nothing
Phil Jollans
  • 3,605
  • 2
  • 37
  • 50
  • Thanks Phil! Placing the Publiic WithEvents in the General section of the form got the TimerEvent into the dropdown list. Now it works like it should! I also placed the Set TimerEvents =PT in the Form_Load routine. – Ken Mar 26 '18 at 00:15
-1

Try declaring PrecisionTimer using WithEvents:

Public WithEvents PT As New PrecisionTimer.PrecisionTimer

Then add an event handler for OTE:

Public Sub PrecisionTimer_OTE(ByVal ActualTriggeredTime As String) Handles PT.OTE
...
End Sub
Chase Rocker
  • 1,908
  • 2
  • 13
  • 14