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.RemoteHost = "xxx.xxx.xxx.xxx",
Socket.RemotePort = "xxxx" and
Socket.Connect and there is no problem.
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.
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
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.
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:
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
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.