I manage to create some simple script to capture news form website, but problem is in memory leak. It seems that script doesn't release memory :(
I tried to close, empty (nothing instead empty doesn't help)-check end lines.
Do While x<50000
Set oXMLHTTP = CreateObject("MSXML2.XMLHTTP.3.0")
WScript.Sleep 60000
oXMLHTTP.Open "GET", "http://www.news.com/sport", False
WScript.Sleep 900
oXMLHTTP.Send
If oXMLHTTP.Status = 200 Then
Set html = CreateObject("HTMLfile")
html.write oXMLHTTP.responseText
...some code...
html=Empty
oXMLHTTP=Empty
oXMLHTTP.responseText=Empty
oXMLHTTP.close
oStream.close
oStream=Empty
Loop
You need Set x = Nothing to release object x
You must not access members of an object you released before (oXMLHTTP)
In next script stub:
oXMLHTTP variable is declared global and defined once for use in the entire script;
html and oStream variables are local to the DoSomething procedure and are released out of it...
However, some statements stay unclear for me...
Option Explicit
Dim x, oXMLHTTP
Set oXMLHTTP = CreateObject("MSXML2.XMLHTTP.3.0")
Do While x<50000
WScript.Sleep 60000
oXMLHTTP.Open "GET", "http://www.news.com/sport", False
WScript.Sleep 900
oXMLHTTP.Send
DoSomething
oXMLHTTP.close
Loop
Sub DoSomething
Dim html, oStream
If oXMLHTTP.Status = 200 Then
Set html = CreateObject("HTMLfile")
html.write oXMLHTTP.responseText
'...some code...
End If
'...another code...
End Sub
Local Variables in Sub Procedures
The values of local variables in a Sub procedure are not preserved
between calls to the procedure.
Variables that are explicitly declared in a procedure (using Dim or
the equivalent) are always local to the procedure. Variables that are
used but not explicitly declared in a procedure are also local, unless
they are explicitly declared at some higher level outside the
procedure.
If variables are not explicitly declared in a procedure, a naming
conflict can occur if anything you have defined at the script level
has a duplicate name. If your procedure refers to an undeclared
variable that has the same name as another procedure, constant or
variable, it is assumed that your procedure is referring to that
script-level name. To avoid this kind of conflict, use an Option
Explicit Statement to force explicit declaration of variables.
Scope and Lifetime of Variables
A variable's scope is determined by where you declare it. When you
declare a variable within a procedure, only code within that procedure
can access or change the value of that variable. It has local scope
and is a procedure-level variable. If you declare a variable outside a
procedure, you make it recognizable to all the procedures in your
script. This is a script-level variable, and it has script-level
scope.
The lifetime of a variable depends on how long it exists. The lifetime
of a script-level variable extends from the time it is declared until
the time the script is finished running. At procedure level, a
variable exists only as long as you are in the procedure. When the
procedure exits, the variable is destroyed. Local variables are ideal
as temporary storage space when a procedure is executing. You can have
local variables of the same name in several different procedures
because each is recognized only by the procedure in which it is
declared.
Related
I have a really basic problem with my projects and I would like to know which approach is the best. I like to use (hated) globals, only for a few the most important objects in a workbook.
I am declaring e.g. my data tables in a such way:
'#Folder("Main")
Option Exclicit
Public tblDatabase As Listobject
Public tblReport As Listobject
Sub setMyTables()
Set tblDatabase = wsDatabase.ListObjects("tDatabase")
Set tblReport = wsReport.ListObjects("tReport")
End Sub
In the past I used this macro before actions on the table, e.g.:
Function getIdFromDatabaseTable() As Variant
' set variable-object to use
setMyTables <-- I used to table-setting-sub in every
macro which requires one of my table
' get ID from table
Dim arr As Variant
arr = tblDatabase.ListColumns("ID").DataBodyRange.Value2
' assign array to function result
getIdFromDataTable = arr
End Function
But why I had to begin almost every macro with calling setMyTables() macro? So I've started to use workbook open event to set my object variables:
[code in ordinary Module]
'#Folder("Main")
Option Exclicit
Public tblDatabase As Listobject
Public tblReport As Listobject
And call setMyTables() macro in Workbook_Open() event code. And here my problem is:
[TLTR] Setting variable-objects in Workbook-Open event seems unrielable. It seems it is not firing sometimes. I am sure that no macro error would reset the project and 'clear' already set variables, because sometimes it throws error on the very first macro run. It is not working occasionally and I don't know what pattern behind it is, I send Excel workbooks to my clients, and it's hard to debug what's realy going on there.
Additional comments
I've just read that this could happen if file is not in trusted localizations, I would like get to know best approach to handle declaring the most used objects globally (if possible without modifying someones trusted folders or another local-PC settings).
I know that I can set a 'flag' bool variable such as wasWorkbookOpenEventFired, but I would have to call checking function or make ifs on almost every Sub or Function in a workbook. So I think it isn't good solution too. Thanks for hints!
You'd have more robust results if you define public functions which each return a specific table, and use those instead of global variables:
Function DatabaseTable() As ListObject
Static rv As ListObject '<< cache the table here
'if your code gets reset then this will just re-cache the table
If rv Is Nothing then Set rv = wsDatabase.ListObjects("tDatabase")
Set DatabaseTable = rv
End Function
I am trying to create multiple instances of the same modeless UserForm in excel-VBA, with parameters, through a Sub.
I can make it work with two of the three parameters I want to assign, but the third one keeps returning me
"Run-time Error '91': Object variable or With block variable not set"
and I can't figure out why.
It may be an obvious typo that I didn't see, but I really can't point out the problem.
Here is my code:
Sub AskToClose(targetWksht As Worksheet, targetRow As Integer, test As String)
Dim newInstanceOfMe As Object
Set newInstanceOfMe = UserForms.Add("MOVE_TO_CLOSED") 'MOVE_TO_CLOSED is the name of my UserForm
newInstanceOfMe.targetWksht = targetWksht 'If I comment this line it works just fine, otherwise Run-time error 91
newInstanceOfMe.targetRow = targetRow
newInstanceOfMe.test = test
newInstanceOfMe.Show vbModeless
End Sub
_____________________________________________________________________
Sub test()
Dim openWksht As Worksheet
Set openWksht = Worksheets("OPEN WO") 'The worksheet exists and works just fine everywhere else
Call AskToClose(openWksht, 2, "test 2")
Call AskToClose(openWksht, 3, "test 3")
Call AskToClose(openWksht, 4, "test 4")
Set openWksht = Nothing 'I tried to comment this line just in case, same result...
End Sub
_____________________________________________________________________
'My MOVE_TO_CLOSED UserForm parameters
Public targetWksht As Worksheet
Public targetRow As Integer
Public test As String
newInstanceOfMe.targetWksht = targetWksht
This statement produces an error-level code quality inspection result for Value Required inspection in Rubberduck (an open-source VBIDE add-in project I manage). The inspection explains the situation as follows:
Object used where a value is required
The VBA compiler does not raise an error if an object is used in a place that requires a value type and the object's declared type does not have a suitable default member. Under almost all circumstances, this leads to a run-time error 91 'Object or With block variable not set' or 438 'Object doesn't support this property or method' depending on whether the object has the value 'Nothing' or not, which is harder to detect and indicates a bug.
There are two types of assignments in VBA: value assignment (Let), and reference assignment (Set). The Let keyword is redundant/optional/obsolete for value assignments:
Dim foo As Long
foo = 2 + 2
Let foo = 2 + 2 '<~ exactly equivalent to the above
So unless the Set keyword is present, VBA attempts to make a value assignment. If the object has a default member, that might just work - the VBA specs define how let-coercion mechanisms make that happen. That's how you can assign a Range to a value:
Sheet1.Range("A1") = 42
That's implicitly assigning to Range.Value, via an implicit member call to Range.[_Default], a hidden property of the Range class.
If the right-hand side of the assignment was also an object, then let-coercion would be happening on both sides of the operator:
Sheet1.Range("A1") = Sheet1.Range("B1") '<~ implicit default member calls galore!
Sheet1.Range("A1").Value = Sheet1.Range("B1").Value
But we're not looking at a Range here, we're looking at a UserForm, and a UserForm does not have a default member, so let-coercion can't happen... but the compiler won't validate that, so the code gets to run anyway... and blows up at run-time instead.
So, we're looking at a Let assignment with both sides holding an object reference, for a class type that doesn't define a default member.
Something.SomeObject = someOtherObject
But VBA doesn't care that there's no default member - because there's no Set keyword, it tries as hard as it can to do what you told it to do, and coerce these objects into values... and fails, obviously.
If Something.SomeObject (left-hand side) is Nothing, then the let-coercion attempt will try to invoke the inexistent default member -- but since the object reference is Nothing, the call is invalid, and error 91 is raised.
If Something.SomeObject is already holding a valid object reference, then the let-coercion attempt will go one step further, and fail with error 438 because there's no default member to invoke.
If Something.SomeObject has a default member (and the reference isn't Nothing), then the value assignment succeeds, no error is raised... but no object reference was assigned, and this might be a subtle bug!
Adding a Set keyword makes the assignment a reference assignment, and now everything works fine.
I call module2 from module1 where I name a workbook "x" in module2. But later when I try "x.Activate" in module1 I get an error "Run-time error '424': Object required"
I have a rather lengthy module that I would like to organize by breaking it up into multiple modules. So far I have created a module called "INPUTS" in this module I have a "Sub RT_CMM_DATA_COMPILER_INPUTS()" presumably in the future I will have other Subs in this module "Sub RT_Some_Other_Project_INPUTS()" I name a workbook in "Sub RT_CMM_DATA_COMPILER_INPUTS()" and try to activate that workbook by name in a separate module called sandbox. But it displays an error.
'RT_Sandbox Module
Sub sandbox()
Call RT_CMM_DATA_COMPILER_INPUTS
wkbwatchFolders_table.Activate
lastShtRow = LASTSHEETROW(ActiveSheet)
MsgBox lastShtRow
End Sub
'Inputs module
Sub RT_CMM_DATA_COMPILER_INPUTS()
watchFolders_filePath = "D:\RT_CMM_Data_File_Paths.xlsx"
Set wkbwatchFolders_table = Workbooks.Open(Filename:=watchFolders_filePath)
End Sub
Am I going about this attempt to organize my code completely wrong? Should I be using class modules for this instead? Or is it just some syntax I am missing?
The critical part you're missing is Option Explicit at the top of every module.
With that option, code will refuse to compile until all variables are explicitly declared.
Without it, watchFolders_filePath is an undeclared variable in both procedures, and in the scope where it is read but not assigned, its data type is Variant/Empty.
Rubberduck (free, open-source VBIDE add-in project that I manage) can help locate and fix these issues (and others) in your code:
OptionExplicit inspection
UnassignedVariableUsage inspection
UndeclaredVariable inspection
VariableNotAssigned inspection
VariableNotUsed inspection
As for your code, you don't need any global variables. Avoid global variables whenever possible. Use functions (and parameters) instead:
Function RT_CMM_DATA_COMPILER_INPUTS() As Workbook
Dim watchFolders_filePath As String
watchFolders_filePath = "D:\RT_CMM_Data_File_Paths.xlsx"
Set RT_CMM_DATA_COMPILER_INPUTS = Workbooks.Open(Filename:=watchFolders_filePath)
End Function
Sub sandbox()
Dim wb As Workbook
Set wb = RT_CMM_DATA_COMPILER_INPUTS
wb.Activate
lastShtRow = LASTSHEETROW(wb.ActiveSheet)
MsgBox lastShtRow
End Sub
Using Public statement will work here:
Public x As Workbook
Public your_var As Object
You need to declare these outside your procedure, at the top of the module. After declaring these you can access them anywhere in any module.
Read More here: Declaring Variables
I am perplexed why my global variable within a module fall out of scope at the conclusion of a sub procedure.
I declare the range at the top of the module out side of all subproc and functions
as below
Option Explicit
Dim TIMEDATA As Range
Dim FREQDATA As Range
Const StartLoc = "B4"
Const flowLoc = "F4"
Const dtLoc = "J8"
In my subproc I define one of the ranges.
Public Sub PortandConvertData()
<SNIP>
Set TIMEDATA = calcSheet.Range(Cells(2, 2).Address, Cells(2 + dataSize, 2).Address)
End Sub
After the sub completes in the watch window I see the variable TIMEDATA go from
Range/Range to Range and the value go from correct to simply out of context.
I want to store data in the module rather than pasting in a sheet or something.
Any help is much appreciated
Make sure that the Context in the Watch properties includes the Procedure/Module you are actually watching. You can make sure by setting the context to All Modules:
From the Watches panel: Right Click the Expression --> Edit Watch --> From the Context group set Procedure/Module to All.
If this is not the actual issue, then you are having the same issue I'm having from Access VBA.
This thread explains the same:
ThisWorkbook not holding global variable value to cancel ontime()
I have a VBA function I use in MS Access and MS Excel. When used in MS Excel I use Application.Volatile but when it is placed or used in MS Access it will not compile. Is there a way to make this line interchangeable without having to delete it when placed in MS Access?
Thank you,
Fred
You can ask for the name of the application:
If Application.Name = "Microsoft Access" then
'Do Nothing ......Or whatever you need to do.
ElseIf Application.Name = "Microsoft Excel" then
Application.Run "Application.Volatile"
End If
From Help.
Visual Basic for Applications Reference
CallByName Function
Executes a method of an object, or sets or returns a property of an object.
Syntax
CallByName(object, procname, calltype,[args()])
The CallByName function syntax has these named arguments:
Part Description
object Required; Variant (Object). The name of the object on which the function will be executed.
procname Required; Variant (String). A string expression containing the name of a property or method of the object.
calltype Required; Constant. A constant of type vbCallType representing the type of procedure being called.
args() Optional: Variant (Array).
Remarks
The CallByName function is used to get or set a property, or invoke a method at run time using a string name.
In the following example, the first line uses CallByName to set the MousePointer property of a text box, the second line gets the value of the MousePointer property, and the third line invokes the Move method to move the text box:
CallByName Text1, "MousePointer", vbLet, vbCrosshair
Result = CallByName (Text1, "MousePointer", vbGet)
CallByName Text1, "Move", vbMethod, 100, 100
Send feedback to MSDN.Look here for MSDN Online resources.