Skip to content
Advertisement

Variable not defined on VBA about socket

I’m making an edit to a project on VBA that uses a Socket to send information to a program on Netbeans on a specific server.

I did not write the code. I’m trying to understand what the original programmer did.

I have many forms, and a few send information with that socket. I’m trying to do the same with another form.

The error is

“Variable not defined”.

In all the project in any moment there is a declaration of the object/variable named “Socket”, but in the forms that originally runs the connection you can make any operations with it, like Socket.Close, Socket.RemoteHost = "xxx.xxx.xxx.xxx", Socket.RemotePort = "xxxx" and Socket.Connect and there is no problem.

I have Option Explicit on the beginning, but that same option is on the other forms.

I’ve looked the flow of the execution with F8 to see if a method or something where the variable is declared, but, or I’m missing something or there is no definition.

Advertisement

Answer

Sounds like your Socket variable is declared as a Public field in your form’s code-behind.

Forms are class modules with a visual designer. Being class modules, when you work with one you’re working with an instance of the class.

If you declare Public Socked As Object in any class module (i.e. any ThisWorkbook, Sheet1, UserForm1, or Class1 module), then yes that object is Public, but that doesn’t make it global: the public field belongs to the instance of the class it’s declared in.

That makes the Socket identifier when used anywhere other than in the form’s code-behind, effectively an undeclared variable – kudos for using Option Explicit here to catch this mistake at compile-time, otherwise that would have very likely gone unnoticed until run-time, where the undeclared Socket local variable would have been some Variant/Empty value, and something, somewhere likely wouldn’t have liked it and would have raised some error, potentially very far from the actual source of the problem!

You have several options.

1. Make Socket an actual global variable

Socket currently belongs to what I’ll call UserForm1. Since other components need this piece of data, you could expand its scope by moving the declaration to a standard module (.bas):

'Module1.bas
Option Explicit
Public Socket As Object

Now just remove the declaration from UserForm1 and everything should “just work”.

Except, it’s a global variable: everyone everywhere can overwrite this object reference at any time.

This isn’t ideal.

2. Make the other code consume the Socket where it lives

Socket belongs to UserForm1. Depending on how/whether you’re instantiating that form, you might be able to treat it like global state, because all UserForm modules have a VB_PredeclaredId hidden attribute set to True, which is exactly how you’re able to do this:

UserForm1.Show

When you do that, the UserForm1 object instance gets created by VBA at the . dereferencing operator, and then the Show method is invoked. If the instance doesn’t self-destruct, then when execution returns after the form is hidden (not unloaded), then you can access everything Public about it:

Dim theSocket As Object
Set theSocket = UserForm1.Socket

Now this obviously comes with a major caveat: you need to take steps to ensure VBA isn’t going to re-spawn a brand new instance (with a brand new Socket reference, set to Nothing) when the . dereferencing operator runs in that instruction.

You can do this by handling the QueryClose event and explicitly cancelling the destruction of the instance:

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = VbQueryClose.vbFormControlMenu Then
        Cancel = True
        Me.Hide
    End If
End Sub

If you have an OkButton doing something like this:

Private Sub OkButton_Click()
    Unload Me
End Sub

Or worse, something like this:

Private Sub OkButton_Click()
    Unload UserForm1
End Sub

Then change it to this:

Private sub OkButton_Click()
    Me.Hide
End Sub

This is critical: you MUST Hide the form, not Unload it, and you can’t let it be destroyed before execution returns – the calling code needs to be able to use exactly the same instance that the user has been interacting with.

3. Take the control yourself

UserForm1 is a class, like any other. New it up yourself instead of leaving VBA in charge: take the exact same precautions as described above, but now pretend there’s no default instance:

Public Sub DoSomething()
    With New UserForm1
        .Show

        Dim theSocket As Object
        Set theSocket = .Socket

    End With
End Sub

See my UserForm1.Show article for more information & details.


4. Use a separate Socket object

If the form’s Socket object has no business being used outside that form, then maybe it would make more sense for the other code to also define and setup a Socket to play with?

Variables should be as tightly-scoped as possible, especially when we’re looking at shared resources like a socket connection: if different procedures Set this socket to different things, then the only purpose of that variable being Public is to avoid the keystrokes of declaring it everywhere you need a socket, and that is abusing global state and will bite you in the rear end, one day or another.

Pragmatically speaking, a form has no business knowing how to wire-up a socket anyway: there should be a dedicated module for that, and everyone that needs to talk to a socket should go through that module.