Making DLL calls with output parameters in Classic ASP [duplicate] - vbscript

In Classic ASP, I have an object, call it bob. This then has a property called name, with let and get methods.
I have a function as follows:
sub append(byref a, b)
a = a & b
end sub
This is simply to make it quicker to add text to a variable. I also have the same for prepend, just it is a = b & a. I know it would be simple to say bob.name = bob.name & "andy", but I tried using the above functions and neither of them work.
The way I am calling it is append bob.name, "andy". Can anyone see what is wrong with this?

Unfortunately this is a feature of VBScript. It is documented in http://msdn.microsoft.com/en-us/library/ee478101(v=vs.84).aspx under "Argument in a class". The alternative is to use a function. Here is an example illustrating the difference. You can run this from the command line using "cscript filename.vbs.
sub append (a, b)
a = a & b
end sub
function Appendix(a, b)
Appendix = a & b
end function
class ClsAA
dim m_b
dim m_a
end class
dim x(20)
a = "alpha"
b = "beta"
wscript.echo "variable works in both cases"
append a, b
wscript.echo "sub " & a
a = appendix(a, b)
wscript.echo "function " & a
x(10) = "delta"
wscript.echo "array works in both cases"
append x(10), b
wscript.echo "sub " & x(10)
x(10) = appendix( x(10), b)
wscript.echo "function " & x(10)
set objAA = new ClsAA
objAA.m_a = "gamma"
wscript.echo "Member only works in a function"
append objAA.m_a, b
wscript.echo "sub " & objAA.m_a
objAA.m_a = appendix(objAA.m_a, b)
wscript.echo "function " & objAA.m_a

Have you tried using with the keyword CALL:
call append (bob.name, "andy")
Classic ASP is fickel about ByRef and ByVal. By default it uses ByRef -- no reason to specify that. If you call a function with parenthesis (without the call), it will pass the variables as ByVal.
Alternatively, you could accomplish the same with:
function append(byref a, b)
append = a & b
end sub
bob.name = append(bob.name, "andy");
Good luck.

As this other answer correctly states, you are facing limitation of the language itself.
The only other option to achieve what you are after as far as I can see it, is to add such sub routine to the class itself:
Public Sub Append(propName, strValue)
Dim curValue, newValue
curValue = Eval("Me." & propName)
newValue = curValue & strValue
Execute("Me." & propName & " = """ & Replace(newValue, """", """""") & """")
End Sub
Then to use it:
bob.Append "name", "andy"
Less elegant, but working.

Related

Microsoft VBScript runtime error '800a000d' Type mismatch: 'LastID'

I do a function to assign an ID. But when I click button, this error comes out.
Microsoft VBScript runtime error '800a000d' Type mismatch: 'LastID'
Public function AssignSanctionID(DeptID,SectID,SanctionType)
REM obtain Transaction ID
dim CmdX
dim SQLX
dim RsX
dim Prefix
dim LastID
dim CurrID
dim NewCurrID
'- Set Connection
HariNi=now()
Tahun=year(HariNi)
Bulan=month(HariNi)
if len(bulan)=1 then
Bulan= "0" & Bulan
end if
If Cint(Tahun) < 2016 then
Pref1= DeptID & "/" & SectID & "/"
Prefix=DeptID & "/" & SectID & "/" & Tahun & "/" & Bulan & "/"
else
Pref1= DeptID & "/%/" & SectID
Prefix=DeptID & "/" & Tahun & "/" & Bulan & "/"
end if
set CmdX = server.CreateObject("ADODB.Command")
Set RSX = Server.CreateObject("ADODB.Recordset")
SQLX = " SELECT * FROM Sanction " _
& " WHERE SanctionID like '%" & Pref1 & "%' " _
& " ORDER BY ID DESC"
CmdX.ActiveConnection = objconn
CmdX.CommandText = SQLX
RsX.Open CmdX,,0,1
if not(RsX.BOF and RsX.EOF) then
If Cint(Tahun) < 2016 then
LastID = right(RsX("ID"),4)
else
LastID = mid(RsX("ID"),13,4)
end if
else
if Bulan="04" then
LastID=0
end if
end if
RsX.Close
set RsX = nothing
'Set ID
If LastID<>"" then
'CurrID = left(4)
CurrID=int(LastID)+1
end if
if len(currid)>0 then
select case len(currid)
case 1
newcurrid = "000" & currid
case 2
newcurrid = "00" & currid
case 3
newcurrid = "0" & currid
case 4
newcurrid = currid
end select
else
NewCurrID="0001"
end if
If Cint(Tahun) < 2016 then
NewCurrID=Prefix & NewCurrID
else
NewCurrID=Prefix & NewCurrID & "/" & SectID
end if
AssignSanctionID = NewCurrID
end function
Hard to help if I don't see the data.
From quick view of the code the issue is here:
CurrID=int(LastID)+1
You are trying to cast LastID but are you sure that it is convertible? Could list all possible values?
Short answer: CInt only works with Numerical values. If you have letters in your value, then Cint wont work.
Bit longer answer:
Having read the Blog that we should be more welcoming (https://stackoverflow.blog/2018/04/26/stack-overflow-isnt-very-welcoming-its-time-for-that-to-change/?cb=1), here is a very general answer, but that might lead you on the correct way to fix it yourself.
Type Mismatch is an error you can get when using a variable the wrong way. For example if you try to do numerical functions with Strings (which means the variable contains letters a-z etc) you will get "Type Mismatch" as you cant add or subtract text in a mathematical way... On the other hand you cant add Integer variables (the variable only contains a number AND isnt contained within "quote marks").
So below is a few ways to assigna a variable and what type it becomes:
LastID=1 'This makes LastID an INT (number)
LastID="1" 'This makes LastID a String but a CInt(LastID) can turn it into an INT because it ONLY contains numbers.
LastID="IT" 'This makes LastID a String that CANT in any way be cast to INT as it contains letters.
LastID=IT 'This row will either create an error except if you already have a variable called IT, then LastID will get the same value as the IT variable...
This should hopefully get you on your way to fix this issue...

VBScript Higher-Order Functions

Is there a way to write anonymous functions, pass them to other functions, in which they are invoked, in vbscript?
There are no anonymous functions/subs/methods in VBScript.
You can use GetRef() (see sample1, sample2) to get something like a function pointer that can be passed to functions/subs to be invoked there (callback). But there are no closures in VBScript, so tricks possible in other languages fail in VBScript.
For specific problems that can be solved with higher order functions in functional languages there may be (nearly) equivalent VBScript solutions involving classes/objects; but for discussing that approach you need to describe your/such a problem in detail.
VBScript has the ability to execute arbitatry code.
Execute and Eval just do what they say to a string containing code.
ExecuteGlobal adds code to your program, like a new function, new variables.
Script Control adds vbscript/jscript scripting language to any program including vbscripts. It can have access to the host's data.
If using ExecuteGlobal/Execute/Eval it is best to run through a scriptcontrol first to test for syntax errors (as you can't trap syntax errors, but you can trap the runtime error the script control gives off on a syntax error).
So you can build your program at runtime.
Set Arg = WScript.Arguments
set WshShell = createObject("Wscript.Shell")
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Sub VBSCmd
RawScript = LCase(Arg(1))
'Remove ^ from quoting command line and replace : with vbcrlf so get line number if error
Script = Replace(RawScript, "^", "")
Script = Replace(Script, "'", chr(34))
Script = Replace(Script, ":", vbcrlf)
'Building the script with predefined statements and the user's code
Script = "Dim gU" & vbcrlf & "Dim gdU" & vbcrlf & "Set gdU = CreateObject(" & chr(34) & "Scripting.Dictionary" & chr(34) & ")" & vbcrlf & "Function UF(L, LC)" & vbcrlf & "Set greU = New RegExp" & vbcrlf & "On Error Resume Next" & vbcrlf & Script & vbcrlf & "End Function" & vbcrlf
'Testing the script for syntax errors
On Error Resume Next
set ScriptControl1 = wscript.createObject("MSScriptControl.ScriptControl",SC)
With ScriptControl1
.Language = "VBScript"
.UseSafeSubset = False
.AllowUI = True
.AddCode Script
End With
With ScriptControl1.Error
If .number <> 0 then
Outp.WriteBlankLines(1)
Outp.WriteLine "User function syntax error"
Outp.WriteLine "=========================="
Outp.WriteBlankLines(1)
Outp.Write NumberScript(Script)
Outp.WriteBlankLines(2)
Outp.WriteLine "Error " & .number & " " & .description
Outp.WriteLine "Line " & .line & " " & "Col " & .column
Exit Sub
End If
End With
ExecuteGlobal(Script)
'Remove the first line as the parameters are the first line
'Line=Inp.readline
Do Until Inp.AtEndOfStream
Line=Inp.readline
LineCount = Inp.Line
temp = UF(Line, LineCount)
If err.number <> 0 then
outp.writeline ""
outp.writeline ""
outp.writeline "User function runtime error"
outp.writeline "==========================="
Outp.WriteBlankLines(1)
Outp.Write NumberScript(Script)
Outp.WriteBlankLines(2)
Outp.WriteLine "Error " & err.number & " " & err.description
Outp.WriteLine "Source " & err.source
Outp.WriteLine "Line number and column not available for runtime errors"
wscript.quit
End If
outp.writeline temp
Loop
End Sub
Vbs
filter vbs "text of a vbs script"
filter vb "text of a vbs script"
Use colons to seperate statements and lines. Use single quotes in place of double quotes, if you need a single quote use chr(39). Escape brackets and ampersand with the ^ character. If you need a caret use chr(136).
The function is called UF (for UserFunction). It has two parameters, L which contains the current line and LC which contains the linecount. Set the results of the script to UF. See example.
There are three global objects available. An undeclared global variable gU to maintain state. Use it as an array if you need more than one variable. A Dictionary object gdU for saving and accessing previous lines. And a RegExp object greU ready for use.
Example
This vbs script inserts the line number and sets the line to the function UF which Filter prints.
filter vbs "uf=LC ^& ' ' ^& L"<"%systemroot%\win.ini"
This is how it looks in memory
Dim gU
Set gdU = CreateObject("Scripting.Dictionary")
Set greU = New RegExp
Function UF(L, LC)
---from command line---
uf=LC & " " & L
---end from command line---
End Function
If there is a syntax error Filter will display debugging details.
User function syntax error
==========================
1 Dim gU
2 Dim gdU
3 Set greU = CreateObject("Scripting.Dictionary")
4 Function UF(L, LC)
5 On Error Resume Next
6 uf=LC dim & " " & L
7 End Function
Error 1025 Expected end of statement
Line 6 Col 6
User function runtime error
===========================
1 Dim gU
2 Dim gdU
3 Set greU = CreateObject("Scripting.Dictionary")
4 Function UF(L, LC)
5 On Error Resume Next
6 uf=LC/0 & " " & L
7 End Function
Error 11 Division by zero
Source Microsoft VBScript runtime error
Line number and column not available for runtime errors
the funny thing about function objects is that they by definition are a memory leak. This means that once you create a function object, you need to keep the scope it was created in intact, which threw me off.
Class VBCompiler
Public leaks
Public Sub Class_Initialize()
leaks = Array()
End Sub
Public Function Compile(code)
Dim compiler, result
Set compiler = CreateObject("MSScriptControl.ScriptControl")
Set portal = CreateObject("Scripting.Dictionary")
Dim name
compiler.Language = "VBScript"
compiler.AddObject "portal", portal, True
compiler.ExecuteStatement code
name = compiler.Procedures(1).Name
compiler.ExecuteStatement "portal.Add ""result"", GetRef(""" & name & """)"
' save the script control because if we go out of scope...
' our function object goes poof!
' leaks.Push compiler
ReDim Preserve leaks(UBound(leaks) + 1)
Set leaks(UBound(leaks)) = compiler
Set Compile = portal("result")
End Function
End Class
Dim z
Set z = New VBCompiler
Set z2 = z.Compile("Function Foo(s):MsgBox s:Foo = 2:End Function")
z2("Hi!")
z2 "Hello Again!"
Gives the two message boxes as desired
Class VBCompiler
Public Function Compile(code)
Dim compiler, result
Set compiler = CreateObject("MSScriptControl.ScriptControl")
Set portal = CreateObject("Scripting.Dictionary")
Dim name
compiler.Language = "VBScript"
compiler.AddObject "portal", portal, True
compiler.ExecuteStatement code
name = compiler.Procedures(1).Name
compiler.ExecuteStatement "portal.Add ""result"", GetRef(""Foo"") "
Set Compile = portal("result")
End Function
End Class
Dim z
Set z = New VBCompiler
Set z2 = z.Compile("Function Foo():MsgBox ""Well Met!"":Foo = 2:End Function")
z2("Hi!")
z2 "Hello Again!"
The above gives (29, 5) (null): Unspecified error. This error is in essence: your object has committed suicide.
This approach can be improved(in particular, the issue of wasteful one ScriptControl per compilation without any plans to release them).

vbs execute another vbs script with dictionary as parameter

i am trying to execute a vbscript from another vbscript. The think is, i have to pass a dictionary as parameter, but i always get the same error message.
Here is my code so far:
dim objShell
Set objShell = Wscript.CreateObject("WScript.Shell")
dim dicExp
Set dicExp = CreateObject("Scripting.Dictionary")
dic.add 0, 10
objShell.Run "C:\Users\groeschm\Desktop\ODBCAktuell.vbs " & dicString
But i always get this error message:
Error 800A01C2 - Wrong number of arguments of invalid property assignment.
Greetings,
Michael
You cannot pass an object reference to WScript.Shell.Run. See http://msdn.microsoft.com/en-us/library/d5fk67ky(v=vs.84).aspx, it says the command line argument is a string, and nothing else.
You cannot pass a Scripting.Dictionary reference, nor can you encode that reference into the string argument.
It´s as simple as that!
And even if you could, this would be useless because the called VBS does not share the same global scope as the caller code.
You should consider alternatives to Run. You could put the ODBCAktuell.vbs code into a function, and call that instead. Or you consider ExecuteFile or one of the related intrinsics.
(Without knowing what ODBCAktuell.vbs contains, and without knowing what exactly you are trying to accomplish, it is difficult to advise you further than that.)
There is a similar question based on the same brainbug: Create instance for a class(resides in B.vbs) from another .VBS file
The OT's code is messed up. dicString is undefined. It does not throw the error claimed, but an "Object Required", because the dictionary is named dicExp, not dic.
While TheBlastOne is right to state that you can't pass anything except strings via the command line, the wish to communicate other (more complex) types of data is legitimate. Making numbers or dates from command line args is standard procedure. And: wanting to re-use code via some kind of import/using/include mechanism isn't a brainbug but essential for good programming.
A general approach to serialisation (via strings) is JSON, but it's not easy to use it in VBScript (cf).
The starting point(s) for a 'roll your own' approach for simple cases (dictionaries with numbers/scalars/simple strings as keys and values) is trivial:
Stringify:
cscript passdic.vbs
cscript recdic.vbs "1 2 3 4"
1 => 2
3 => 4
passdic.vbs:
Option Explicit
Function d2s(d)
ReDim a(2 * d.Count - 1)
Dim i : i = 0
Dim k
For Each k In d.Keys()
a(i) = k
i = i + 1
a(i) = d(k)
i = i + 1
Next
d2s = Join(a)
End Function
Function qq(s)
qq = """" & s & """"
End Function
Dim d : Set d = CreateObject("Scripting.Dictionary")
d(1) = 2
d(3) = 4
Dim c : c = "cscript recdic.vbs " & qq(d2s(d))
WScript.Echo c
Dim p : Set p = CreateObject("WScript.Shell").Exec(c)
WScript.Echo p.Stdout.ReadAll()
recdic.vbs:
Option Explicit
Function s2d(s)
Set s2d = CreateObject("Scripting.Dictionary")
Dim a : a = Split(s)
Dim i
For i = 0 To UBound(a) Step 2
s2d.Add a(i), a(i + 1)
Next
End Function
Dim d : Set d = s2d(WScript.Arguments(0))
Dim k
For Each k In d.Keys()
WScript.Echo k, "=>", d(k)
Next
Code re-use:
cscript passdic2.vbs
cscript recdic2.vbs
1 => 2
3 => 4
passdic2.vbs
Option Explicit
Function d2s(d)
ReDim a(d.Count - 1)
Dim i : i = 0
Dim k
For Each k In d.Keys()
a(i) = "cd.Add " & k & "," & d(k)
i = i + 1
Next
d2s = "Function cd():Set cd=CreateObject(""Scripting.Dictionary""):" & Join(a, ":") & ":End Function"
End Function
Dim d : Set d = CreateObject("Scripting.Dictionary")
d(1) = 2
d(3) = 4
CreateObject("Scripting.FileSystemObject").CreateTextFile("thedic.inc").Write d2s(d)
Dim c : c = "cscript recdic2.vbs"
WScript.Echo c
Dim p : Set p = CreateObject("WScript.Shell").Exec(c)
WScript.Echo p.Stdout.ReadAll()
thedic.inc
Function cd():Set cd=CreateObject("Scripting.Dictionary"):cd.Add 1,2:cd.Add 3,4:End Function
recdic2.vbs
Option Explicit
ExecuteGlobal CreateObject("Scripting.FileSystemObject").OpenTextFile("thedic.inc").ReadAll()
Dim d : Set d = cd()
Dim k
For Each k In d.Keys()
WScript.Echo k, "=>", d(k)
Next

VbScript Reference Object by String variable

How can I reference an object by a string variable? I feel like there has at be a way to do this other than than using execute and losing Options Explicit
Example (where "Something is the command / method in question":
Dim strObj
Dim D1 : Set D1 = CreateObject("Scripting.Dictionary")
strObj = "D1"
Something(strObj).add "1" , "Car"
msgbox Something(strObj).Item("1")
Thanks!
Only functions (and subs) can be referenced by Set functionReference = GetRef("myFunction") but not objects.
When you want to have a string reference, you have to create one with each object you would want to refer to. You can use a dictionary for that:
Dim foo, bar
Dim StringObjectReference : Set StringObjectReference = CreateObject("Scripting.Dictionary")
' Lets create some objects
Set foo = CreateObject("System.Collections.ArrayList")
Set bar = CreateObject("Scripting.Dictionary")
' Register the objects for referral, now we can reference them by string!
StringObjectReference.Add "foo", foo
StringObjectReference.Add "bar", bar
' Manipulate the objects through their string reference
StringObjectReference("foo").Add "BMW"
StringObjectReference("foo").Add "Chrysler"
StringObjectReference("foo").Add "Audi"
StringObjectReference("foo").Sort
StringObjectReference("bar").Add "Bikes", array("Honda", "Thriumph")
StringObjectReference("bar").Add "Quads", array("Honda", "Kawasaki", "BMW")
' Retrieve values from the objects
' real:
msgbox "My Cars: " & join(foo.ToArray(), ", ")
msgbox "My Bikes: " & join(bar("Bikes"), ", ")
msgbox "My Quads: " & join(bar("Quads"), ", ")
' From reference
msgbox "My Cars: " & join(StringObjectReference("foo").ToArray(), ", ")
msgbox "My Bikes: " & join(StringObjectReference("bar").Item("Bikes"), ", ")
' Shorthand notation (without item)
msgbox "My Quads: " & join(StringObjectReference("bar")("Quads"), ", ")
In the instant you use the content of a string variable to reference some method/function/object in your code, you "lose" the Option Explicit benefits. You can put anything you want inside the variable and it will not be tested until code is executed.
But you can use Option Explicit with Execute. This code
Dim D1
Set D1 = CreateObject("Scripting.Dictionary")
Execute "Option Explicit : D1.Add ""1"",""car"""
Execute "Option Explicit : D2 = D1.Item(""1"")"
WScript.Echo D1.Item("1")
will complain for D2 not been defined. But it will do it at runtime. Comment this line and you will see the Add method working as expected.

Passing objects as arguments in VBScript

I'm working on a project to capture various disk performance metrics using VBScript and would like to use a sub procedure with an object as an argument. In the following code samples the object I'm referring to is objitem.AvgDiskQueueLength which will provide a value for the disk queue length. I haven't found a way to make it work since it is recognized as a string and then doesn't capture the value. My goal is to make it easy for anyone to change the counters that are to be captured by only having to make a change in one location(the procedure call argument). The way I'm going about this may not be the best but I'm open to suggestions. The sub procedure call is below.
PerfCounter "Average Disk Queue Length", "disk_queueLength", "objItem.AvgDiskQueueLength"
The following code is the sub procedure.
Sub PerfCounter(CounterDescription, CounterLabel, CounterObject)
Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_PerfFormattedData_PerfDisk_PhysicalDisk",,48)
args_index = args_index + 1
arrCriteria = split(command_line_args(args_index),",")
strDriveLetter = UCase(arrCriteria(0))
intCriticalThreshold = arrCriteria(1)
intWarningThreshold = arrCriteria(2)
For Each objItem in colItems
With objItem
WScript.Echo "objitem.name = " & objitem.name
If InStr(objItem.Name, strDriveLetter & ":") > 0 Then
intChrLocation = InStr(objItem.Name, strDriveletter)
strInstanceName = Mid(objItem.Name, intChrLocation, 1)
End If
If strDriveLetter = strInstanceName AND InStr(objItem.Name, strDriveLetter & ":") > 0 Then
If intActiveNode = 1 OR Len(intActiveNode) < 1 Then
WScript.Echo "CounterDescription = " & CounterDescription
WScript.Echo "CounterLabel = " & CounterLabel
WScript.Echo "CounterObject = " & CounterObject
If CInt(CounterOjbect) => CInt(intCriticalThreshold) Then
arrStatus(i) = "CRITICAL: " & strDriveLetter & ": " & CounterDescription
arrTrendData(i) = CounterLabel & "=" & CounterObject
intExitCode = 2
arrExitCode(i) = intExitCode
ElseIf CInt(CounterOjbect) => CInt(intWarningThreshold) AND CInt(CounterObject) < CInt(intCriticalThreshold) Then
arrStatus(i) = "WARNING: " & strDriveLetter & ": " & CounterDescription
arrTrendData(i) = CounterLabel & "=" & CounterObject
intExitCode = 1
arrExitCode(i) = intExitCode
Else
arrStatus(i) = "OK: " & strDriveLetter & ": " & CounterDescription
arrTrendData(i) = CounterLabel & "=" & CounterObject
intExitCode = 0
arrExitCode(i) = intExitCode
End If
Else
PassiveNode CounterDescription, CounterLabel
End If
End If
End With
Next
i = i + 1
ReDim Preserve arrStatus(i)
ReDim Preserve arrTrendData(i)
ReDim Preserve arrExitCode(i)
End Sub
Why cant you do this...
PerfCounter "Average Disk Queue Length", "disk_queueLength", objItem.AvgDiskQueueLength
To pass an object you have to pass an object, not a string. To make this method work as expected you would have to have the object prior to the procedure call, but in your code example it looks like you are trying to pass an object that you don't have. A working example:
Set objFSO = CreateObject("Scripting.FileSystemObject")
UseFileSystemObject objFSO
Sub UseFileSystemObject( objfso)
'Now I can use the FileSystemObject in this procedure.
End Sub
But calling the UseFileSystemObject procedure like this will not work,
UseFileSystemObject "objFSO"
because you are passing in a string not an object.
The only way I can think of to accomplish what you want is to use a select statement to write the appropriate attribute of the object, something like this.
Call PerfCounter "Average Disk Queue Length", "disk_queueLength", "AvgDiskQueueLength"
Sub PerfCounter(CounterDescription, CounterLabel, CounterObjectAttribute)
Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_PerfFormattedData_PerfDisk_PhysicalDisk",,48)
For Each objItem in colItems
Select Case CounterObjectAttribute
Case "ObjectAttribute1"
Case "ObjectAttribute2"
Case "AvgDiskQueueLength"
Wscript.Echo objItem.AvgDiskQueueLength
End Select
Next
End Sub
So in the select you would have to add a case for each attribute that can be used, but it would allow you to pass a string into the procedure. I might be way off on this, but I don't know how you can pass an object if you don't have the object first.

Resources