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.