Performance impact of calling lots of functions in vbscript - vbscript

I have lots of sub routines and functions like these:
Sub LogInfo(Txt)
'Write an entry to the log file
If LogEnableInfo Then Log "Info: " & Txt
End Sub
Or this:
Function GetSettingValue(Key)
'Get the value of a setting or an empty string if the setting is not set
Dim Res
If Settings.Exists(UCase(Key)) Then Res = Settings(UCase(Key)) Else Res = ""
GetSettingValue = Res
End Function
Or this:
Sub DoExp(Exp, ErrTag)
'Execute an HS.Exp with Error handling and Debugging
Err.Clear
On Error Resume Next
HS.Exp Exp
If Err.Number <> 0 Then
LogError "HS.Exp """ & Exp & """ , Tag: " & ErrTag
Err.Clear
ElseIf LogEnablePut Then
Log "Put: HS.Exp """ & Exp & """ , Tag: " & ErrTag
End If
On Error GoTo 0
End Sub
They're really one liners that don't do much, but I use lots of times, so I don't want to type it out every time. I also use recursive functions a lot.
Since speed and memory use is critical to my application, what I would like to know is if the act of calling a function or sub in vbscript has a considerable performance impact? I know that in PHP, this has been cited as a performance issue. Can I just go ahead and create zillions of little nested functions to do everything or should I use them a little more sparingly?

In my experience using functions repeatedly is a bad thing in VBS, in a small test script I wrote I found that using functions were about 5 times slower.
startTime = Timer
x = 0
For i = 0 To 1000000000
x = x + 1
Next
endTime = Timer
WScript.Echo x
WScript.Echo CStr(endTime - startTime)
startTime = Timer
y = 0
For i = 0 To 1000000000
y = fooBooGoo(y)
Next
endTime = Timer
WScript.Echo y
WScript.Echo CStr(endTime - startTime)
Function fooBooGoo(p)
g = p + 1
fooBooGoo = g
End Function
The output I got was:
1000000001
306.6289
1000000001
1554.875

The rule "violate good practice (don't repeat yourself, clean program structure, ...) for speed gains!" is bad.
I doubt that there is even one real life VBScript program that becomes fast enough as soon as you inline functions/subs/methods - if you show me two, I'm willing to discuss using/writing a preprocessor for VBScript that expands macros and adds line numbering to scripts.
If speed is important, you should benchmark early/continously and optimize for speed by choosing better algorithms or better tools (COM, Net, Shell).
If (3) does not help, switch to a better language.
Samples to illustrate what I mean by "better tools" (#3):
using one ADO connection (created once (with late binding in VBScript)) to execute many SQL statements on a folder of .CSVs instead of using a FSO, .Readline loops, Splits, and complex IFs
using one .Net component (ArrayList, StringBuilder, ...) for many operations instead of using ReDim Preserve on native arrays or string concatenation in a loop
shelling out to dir /s /b c:\where\ever\*.vbs instead of a (faulty/inefficient) homegrown/stolen recursive directory walker that looks at all files to filter the .vbs

Functions are used to carry on a specific task or to perform specific operation. Same as in other languages, but in OOP they are usually called "methods", at least when they are attached to a class. It means to be able to work. Function is the process where a machine can work and make things efficient. here's an ex. that is used in a sentence. the machine won't work right. Or what you could say is the function is the ability to work so the mathematical definition would be the ability to solve an equation/problem. Function means working.
So, with regard to using function re-cursive's would be better but overall in object based languages like vbscript, objects should be created and set=nothing later for actually making system consume memory efficiently.
Thanks

Related

SAS Enterprise Guide with VBScript. Looping through SAS programs get stuck

I'm facing a random problem. When executing SAS programs with VBScript and the SASEGObjectModel.Application.7.1, looping through CodeCollection get stuck sometimes, even if the program execution was succeeded (the final data bases are correctly created in our server). The script simple doesn't go to the next program of CodeCollection (the prompt executing the script still open... ad infinitum). The SAS program It happens is random, also the frequency. I'm going with something like this:
Dim oSasApp
Set oSasApp = CreateObject("SASEGObjectModel.Application.7.1")
oSasApp.SetActiveProfile("some-profile")
Dim oSasProj
Set oSasProj = oSasApp.Open("some-project.egp", "")
Dim oProgramList
Set oProgramList = oSasProj.CodeCollection
Dim programOrder
Set programOrder = ...here I assign the SAS programs order array reading from a .txt...
For Each program in programOrder
For Each sasProgram in oProgramList
If sasProgram.Name = program Then
sasProgram.Run
sasProgram.Log.SaveAs "some-folder/" & sasProgram.Name & ".txt"
End If
Next
Next
oSasProj.Close
oSasApp.Quit
The problem is not the Log saving, as the log txt file of the stucked program is also correctly created.
Any idea? Maybe problems in our SAS server? Should I declare some kind of options?
SAS Guide version: 7.15
Windows: 10
Tks
So... for people facing the same problem. As I commented above, if I press enter on prompt the script flows again. So it is waiting for my input, for reasons I can't tell. I did 2 things to get around it. Not sure if all of them are necessary or if only one solves it, but here it goes:
First, by VBScript I turned off a list of generations and I applied a delay after the SAS program runs:
For Each program in programOrder
For Each sasProgram i oProgramList
If sasProgram.Name = program Then
sasProgram.GenSasReport = False
sasProgram.GenHTML = False
sasProgram.GenListing = False
sasProgram.GenPDF = False
sasProgram.GenRTF = False
sasProgram.Run
WScript.Sleep(2000)
sasProgram.Log.SaveAs "some-folder/" & sasProgram.Name & ".txt"
End If
Next
Next
Them, in my batch file, wich I use to call the VBScript with the "cscript" command, I set it to apply "y" to every single message the VBScript could ask:
cd ./script-folder
echo y | cscript script-file-name.vbs
And that is it.

Handling of Automation errors in VB

EDIT#1
I am developing a VB6 EXE application intended to output some special graphics to the Adobe Illustrator.
The example code below draws the given figure in the Adobe Illustrator as a dashed polyline.
' Proconditions:
' ai_Doc As Illustrator.Document is an open AI document
' Point_Array represented as "array of array (0 to 1)" contains point coordinates
'
Private Sub Draw_AI_Path0(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
Set New_Path = ai_Doc.PathItems.Add
New_Path.SetEntirePath Point_Array
New_Path.Stroked = True
New_Path.StrokeDashes = Array(2, 1)
End Sub
This simple code, however, can raise a variety of run-time automation errors caused by:
Incorrect client code (for example, assigning a value other than Array to the
New_Path.StrokeDashes)
Incorrect client data (for example, passing too large Point_Array to New_Path.SetEntirePath)
Unavailability of some server functions (for example when the current layer of the AI is locked)
Unexpected server behavior
EDIT#2
Unfortunately, since such errors are raised by the server app (AI, in our case) their descriptions are often inadequate, poor and misleading. The error conditions may depend on AI version, installed apps, system resources etc. A single problem can lead to different errors. Example passing too large Point_Array to New_Path.SetEntirePath (Windows XP SP3, Adobe Illustrator CS3):
For array size of 32767 and above, the error is -2147024809 (&H80070057) "Illegal Argument"
For array size of 32000 to 32766, the error is -2147212801 (&H800421FF) "cannot insert more segments in path. 8191 is maximum"
END OF EDIT#2
The traditional error handling can be used to prevent the client crash and to display the error details as shown below:
Private Sub Draw_AI_Path1(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
On Error GoTo PROCESS_ERROR
Set New_Path = ai_Doc.PathItems.Add
New_Path.SetEntirePath Point_Array
New_Path.Stroked = True
New_Path.StrokeDashes = Array(2, 1)
Exit Sub
PROCESS_ERROR:
MsgBox "Failed somewhere in Draw_AI_Path1 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
End Sub
As you can see, the error number and error description can be accessed easily. However, I need to know also what call causes the error. This can be very useful for large and complex procedures containing many calls to the automation interface. So, I need to know:
What error happened?
What call caused it?
In what client function it happened?
Objective #3 can be satisfied by techniques described here. So, let’s focus on objectives #1 and 2. For now, I can see two ways to detect the failed call:
1) To “instrument” each call to the automation interface by hardcoding the description:
Private Sub Draw_AI_Path2(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
Dim Proc As String
On Error GoTo PROCESS_ERROR
Proc = "PathItems.Add"
Set New_Path = ai_Doc.PathItems.Add
Proc = "SetEntirePath"
New_Path.SetEntirePath Point_Array
Proc = "Stroked"
New_Path.Stroked = True
Proc = "StrokeDashes"
New_Path.StrokeDashes = Array(2, 1)
Exit Sub
PROCESS_ERROR:
MsgBox "Failed " & Proc & " in Draw_AI_Path2 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
End Sub
Weak points:
Code becomes larger and less readable
Incorrect cause can be specified due to copypasting
Strong points
Both objectives satisfied
Minimal processing speed impact
2) To “instrument” all calls together by designing a function that invokes any automation interface call:
Private Function Invoke( _
ByRef Obj As Object, ByVal Proc As String, ByVal CallType As VbCallType, _
ByVal Needs_Object_Return As Boolean, Optional ByRef Arg As Variant) _
As Variant
On Error GoTo PROCESS_ERROR
If (Needs_Object_Return) Then
If (Not IsMissing(Arg)) Then
Set Invoke = CallByName(Obj, Proc, CallType, Arg)
Else
Set Invoke = CallByName(Obj, Proc, CallType)
End If
Else
If (Not IsMissing(Arg)) Then
Invoke = CallByName(Obj, Proc, CallType, Arg)
Else
Invoke = CallByName(Obj, Proc, CallType)
End If
End If
Exit Function
PROCESS_ERROR:
MsgBox "Failed " & Proc & " in Draw_AI_Path3 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
If (Needs_Object_Return) Then
Set Invoke = Nothing
Else
Invoke = Empty
End If
End Function
Private Sub Draw_AI_Path3(ByRef Point_Array As Variant)
Dim Path_Items As Illustrator.PathItems
Dim New_Path As Illustrator.PathItem
Set Path_Items = Invoke(ai_Doc, "PathItems", VbGet, True)
Set New_Path = Invoke(Path_Items, "Add", VbMethod, True)
Call Invoke(New_Path, "SetEntirePath", VbMethod, False, Point_Array)
Call Invoke(New_Path, "Stroked", VbSet, False, True)
Call Invoke(New_Path, "StrokeDashes", VbSet, False, Array(2, 1))
End Sub
Weak points:
Objective #1 is not satisfied since Automation error 440 is always raised by CallByName
Need to split expressions like PathItems.Add
Significant (up to 3x) processing speed drop for some types of automation interface calls
Strong points
Compact and easy readable code with no repeated on error statements
Is there other ways of handling automation errors?
Is there a workaround for the Weak point #1 for 2)?
Can the given code be improved?
Any idea is appreciated! Thanks in advance!
Serge
Think of why it is you might want to know where an error has been raised from. One reason is for simple debugging purposes. Another, more important, reason is that you want to do something specific to handle specific errors when they occur.
The right solution for debugging really depends on the problem you're trying to solve. Simple Debug.Print statements might be all you need if this is a temporary bug hunt and you're working interactively. Your solution #1 is fine if you only have a few routines that you want granular error identification for, and you can tolerate having message boxes pop up. However, like you say, it's kind of tedious and error prone so it's a bad idea to make that into boilerplate or some kind of "standard practice".
But the real red flag here is your statement that you have "large and complex procedures containing many calls to the automation interface", plus a need to handle or at least track errors in a granular way. The solution to that is what it always is - break up your large and complex procedures into a set of simpler ones!
For example, you might have a routine that did something like:
Sub SetEntirePath(New_Path As Illustrator.PathItem, ByRef Point_Array As Variant)
On Error Goto EH
New_Path.SetEntirePath Point_Array
Exit Sub
EH:
'whatever you need to deal with "set entire path" errors
End Sub
You basically pull whatever would be line-by-line error handling in your large procedure into smaller, more-focused routines and call them. And you get the ability to "trace" your errors for free. (And if you have some kind of systematic tracing system such as the one I described here - https://stackoverflow.com/a/3792280/58845 - it fits right in.)
In fact, depending on your needs, you might wind up with a whole class just to "wrap" the methods of the library class you're using. This sort of thing is actually quite common when a library has an inconvenient interface for whatever reason.
What I would not do is your solution #2. That's basically warping your whole program just for the sake of finding out where errors occur. And I guarantee the "general purpose" Invoke will cause you problems later. You're much better off with something like:
Private Sub Draw_AI_Path4(ByRef Point_Array As Variant)
...
path_wrapper.SetEntirePath Point_Array
path_wrapper.Stroked = True
path_wrapper.StrokeDashes = Array(2, 1)
...
End Sub
I probably wouldn't bother with a wrapper class just for debugging purposes. Again, the point of any wrapper, if you use one, is to solve some problem with the library interface. But a wrapper also makes debugging easier.
One would run it in the VB6 debugger. If compiled without optimisation (you won't recognise your code if optimised) you can also get a stack trace from WinDbg or WER (use GFlags to set it up). HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug is where settings are stored.
You can also start in a debugger.
windbg or ntsd (ntsd is a console program and maybe installed). Both are also from Debugging Tools For Windows.
Download and install Debugging Tools for Windows
http://msdn.microsoft.com/en-us/windows/hardware/hh852363
Install the Windows SDK but just choose the debugging tools.
Create a folder called Symbols in C:\
Start Windbg. File menu - Symbol File Path and enter
srv*C:\symbols*http://msdl.microsoft.com/download/symbols
then
windbg -o -g -G c:\windows\system32\cmd.exe /k batfile.bat
You can press F12 to stop it and kb will show the call stack (g continues the program). If there's errors it will also stop and show them.
Type lm to list loaded modules, x *!* to list the symbols and bp symbolname to set a breakpoint
da displays the ascii data found at that address
dda displaysthe value of the pointer
kv 10 displays last 10 stack frames
lm list modules
x *!* list all functions in all modules
p Step
!sysinfo machineid
If programming in VB6 then this environmental variable link=/pdb:none stores the symbols in the dll rather than seperate files. Make sure you compile the program with No Optimisations and tick the box for Create Symbolic Debug Info. Both on the Compile tab in the Project's Properties.
Also CoClassSyms (microsoft.com/msj/0399/hood/hood0399.aspx) can make symbols from type libraries.

Why won't my VBS Spam Bot work?

I am working on a Spam bot in VBS. However, something is wrong with the code. Whenever I run the VBS my computer lags then crashes. I know it is a bit basic, hence why I am asking the more experienced. Please give me the right code in order for it to work.
Option Explicit
Dim x,y,obj,v,o
Set obj=CreateObject("Wscript.Shell")
x=inputbox("What would you like to spam?")
v=inputbox("How many times would you like to spam?")
o=inputbox("How many seconds do you want to select the textbox?")
wscript.sleep o & 000
do until y=v
obj.SendKeys (x & "{ENTER}")
y=y+1
loop
wscript.quit
~ Thanks
A helpful tip you probably didn't notice as you did say you were quit inexperienced with VBS -
wscript.sleep o & 000
The ampersand (&) is a character with the logical equivalent of and, similar to or and not, in that they are logic, and is used in if statements to check whether both, for example, foo and bar are equivalenet to each other; for example:
variable1 = true
variable2 = true
if(variable1 & variable2) return true
else return false
This would return true, as both variables are set to true.
I understand you were trying to get the milliseconds from the user input of seconds. It might be better achieved by making a variable:
o = o * 1000;
Then, as you got this data in the form of string, parse it into an integer form:
milSeconds = CInt(o);
Probably a good idea to check whether o had non-numeric characters beforehand to prevent errors.
Hope this can help you continue working on it. Even though spam-bots are generally frowned upon here, I like to think they're a good learning exercise.

Obtaining a reference to a NotesView with NotesDatabase.Views(x) can be surprisingly slow

In VB6, I'm supporting code that loops through all the views in a Lotus Notes database thusly:
For lngdomViewidx = LBound(domDatabase.Views) To UBound(domDatabase.Views)
Set domView = domDatabase.Views(lngdomViewidx) ' note: this line right here is slow to execute
The amount of time it takes to retrieve a reference to the view by index with this method seems proportional to the number of documents in the view. This bit of code is just looping through the Notes database to build a list of all the view names. On really large databases this can take several minutes. Is there a faster way to get this information?
Thanks!
That is not a very efficient way, no.
Use something like the code below instead. It is Lotusscript, but it should be pretty much the same in VB. I haven't tested it, just copied it from a production database and modified it to look for views instead for forms like in my original...
Dim ncol As NotesNoteCollection
Set ncol = db.CreateNoteCollection(True)
Call ncol.SelectAllNotes(False)
ncol.SelectViews = True
Call ncol.BuildCollection
noteID = ncol.GetFirstNoteId
For i = 1 To ncol.Count
Set doc = targetdb.GetDocumentByID(noteID)
MsgBox "view = " + doc.GetItemValue("$Title")(0)
noteID = ncol.GetNextNoteId(noteID)
Next
Karl-Henry is absolutely right: The NotesNoteCollection is really the fastest way to loop through all views.
But you can speed up your code significantly by just changing the loop.
Instead of opening each view using its index, you coud, do something like:
Forall view in db.Views
'Do whatever you want
End Forall
Accessing elements in a collection in Lotus Notes using the index means, that it always has to count from the beginning, while using a forall- loop directly accesses the next element...

Detect if the contents of a folder have changed?

Conditions:
Windows 98 SE
WMI not available
I have code that looks like this, written using my steroidal wrapping of VBScript using MSScript.
do
a = files.collectfiles( "c:\userver", "" )
for i = 0 to ubound( a )
f = a(i)
if strings.endswith( f, ".usv" ) then
d = files.readfilee( f )
on error resume next
executeglobal d
nErr = err.number
sErr = err.description
on error goto 0
if nErr <> 0 then
trace "*** Error " & nErr & ", " & sErr
end if
files.deletefile f
end if
next
system.sleep 10
system.cooperate
loop
There's a lot of disk activity with that call to files.collectfiles. Is there some way of detecting a change in the contents of a folder without actually scanning the folder for files?
There is a sample which claims to work on all versions from Win95 up to at leas WinXP. Developed under Win98 with VB5. Using the (then? provided links to the docu below) undocumented SHChangeNotify* Functions.
SHChangeNotifyRegister: Receive Shell Change Notifications
SHChangeNotifyRegister Function
SHChangeNotifyDeregister Function
There is another solution using ReadDirectoryChangesW here:
VB6 WinAPI ReadDirectoryChangesW (check the 5th post from Yang Kok Wah)
Define "change in the contents of a folder".
If it means that a file was added, deleted, or renamed, then the modified timestamp of the folder is updated whenever such an event occurs.
If you're instead wanting to know when files are modified, then you'll need to read them.
That said, looking at what you're trying to do (scan a folder for new .usv files, and process them and delete them), then just keeping track of the timestamp on the folder and updating it right before you call collectfiles is best (note that the correct time to log is just BEFORE calling collectfiles, otherwise you run the risk of not waking up if a file gets added during the collectfiles call or immediately afterward).
You specifically asked for something in VB and running on win98 and I have no answer for this, but MS has a c/win32 example on how to achieve this on Windows2000+ with FindFirstChangeNotification. Another thing is that apparently "FileSystemWatcher" in .NET is not working/supported on Win98. What is my point? There maybe is no easy solution for this and you have to come up with something on your own.

Resources