Printing to an O’Neil Route Printer RP2000-8000 Line Mode (COM) Printer using embedded Visual Basic (eVB) on Windows CE and Windows Mobile

System Requirements:

  • Microsoft embedded Visual Basic
  • Windows CE 2.11, 3.0, 4.0, 4.1, 4.2, 5.0
  • Pocket PC 2000, 2002
  • Windows Mobile 2003, 2003SE, 5.0

The Problem:

Although this article specifically addresses a problem that I had between a Symbol PDT8046 and the O’Neil Route Printer RP2000-8000, the issue and resolution should be applicable to any serial (COM) port based line printer.

If you have a raw ascii line printer, you can check to see if the printer works by echoing directly from the stdout into the COM port. Under DOS or the Windows command shell on the PC or Pocket DOS on Windows CE/Windows Mobile issue the following commands

mode com1: 9600,n,8,1,r
mode lpt1:=com1:echo I am a fish > lpt1

This will send I am a fish and a line feed to COM1 at 9600bps with no parity. Note that Pocket DOS doesn’t support the “,r” parameter.

So now that you have ascertained that the printer actually works, when you use the Microsoft Communications Control (Microsoft Comm Control) for embedded Visual Basic (eVB) to send the same data, why is it that nothing happens what so ever.

More Info

Let us look at the VB

‘ Set to COM1, 9600bps, no Parity.
‘ Enable DTR Flow Control, disable RTS Flow Control, disable handshaking
msComm.CommPort = 1
msComm.Settings = “9600,n,8,1,r”
msComm.DTREnable = truemsComm.RTSEnable = false
msComm.Handshaking = 0

‘ Attempt to get control of the COM port if it isn’t available
On Error Resume Next
if (msComm.PortOpen) then
msComm.PortOpen = false
end if
On Error Goto 0

‘ Open the port, Send the message, shutdown the port
msComm.PortOpen = true
msComm.Output = “I am a fish” & Chr(13) & Chr(10)
msComm.PortOpen = false

This is a logical flow of execution and basically what on the surface the DOS version is doing. Yet the message I am a fish will never arrive at the printer. In fact, the printer will not even acknowledge the presence of the message.

The Fix

This code is in fact completely logical, and is right (for the O’Neil RP2000-8000 anyway). However after some frustration and experimentation I discovered that there are two important pieces of the puzzle missing, one of them strongly recommended and the other very much mandatory.

Issue 1: Sleeping Printer

The O’Neil printer will go into standby mode after a couple of minutes of idle time so as to conserve power. If the printer is in sleep mode and you send data to it, it probably will not be awake to receive information and send it to the print head fast enough; best case nothing happens and worst case less than half of your message gets printed.

The O’Neil hardware manual has a very short section on this. In order to wake the printer up (or ensure that it is awake) you need to send a string of ASCII NULL characters (0x00) at the printer. For ONeil line mode printers operating at 9600bps you need to send this string 6+ times. For 38,400bps printers you should send this 24+ times. In our VB world that means:

msComm.Output = Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0)

7 times is a charm, however the manual also states that you should wait 150ms before sending data to the printer. See the next section on implementing Sleep().

If you repeat your test now, your printer should at least wake up even if nothing else happens

Issue 2: Speed = Distance / Time

I could wax lyrical on this, but Ill keep it brief.

There are a couple of buffers at play in the form of the OS and the printer, the embedded operating system and the eVB runtime processor. All of which cause delays. It follows something similar to this

  1. User Sends Print Request
  2. eVB Runtime Parses the command and sends it to the native code of the machine
  3. Operating System queues it in RAM as it gets processed through and send on its merry way
  4. Operating System queues it in the COM port transmit (TX) buffer
  5. Printer receives the message and queues it in the print buffer (512 bytes I *think* on the RP2000-8000)
  6. Printer processes it forward to the print head and the line at the front of the queue is printed

eVB does not care about steps 3 – 6, neither is it aware of them. As far as eVB is concerned, it has finished with the msComm.Output() command as soon as it has received an OK from the operating system that the requests has been through the processor and the data is in a queue.

Referring back to our original code

‘ Set to COM1, 9600bps, no Parity.
‘ Enable DTR Flow Control, disable RTS Flow Control, disable handshaking
msComm.CommPort = 1
msComm.Settings = “9600,n,8,1,r”
msComm.DTREnable = truemsComm.RTSEnable = false
msComm.Handshaking = 0

‘ Attempt to get control of the COM port if it isn’t available
On Error Resume Next
if (msComm.PortOpen) then
msComm.PortOpen = false
end if
On Error Goto 0

‘ Open the port, Send the message, shutdown the port
msComm.PortOpen = true
msComm.Output = “I am a fish” & Chr(13) & Chr(10)      <—— My work as VB is now over
msComm.PortOpen = false                                    <—— Shutdown the COM port

What is happening? eVB thinks that everything has been delivered and shuts down the COM port before the data has left the sending device and is safely received into the printers hardware buffer.

What is the solution? Well, you can be as eloquent or in-eloquent as you like. You can do a byte by byte check of data into the VB transmit buffer, ensuring that this has gone before you progress through the output stream. Alternatively if you are feeling lazy you could use the threading model and implement a fixed length sleep action. If however you are feeling somewhere between the two extremes you can try and weight the sleep timer based upon message length. Something along the lines of

delay (ms) = Absolute(((Length(Message) / ((Printer Bit Rate * 0.50) / 8 )) * 1000))

A weighting of 0.66 is assumed so that the link speed and buffer writing can be running at only 1/2 of line speed. Division by 8 is used to convert from Bits Per Second to Bytes Per Second. Multiplication by 1000 converts the value to milliseconds.

or for the RP2000-8000 with message “I am a fish <cr><lf>” [13 characters]

delay (ms) = Abs(13 / ((9600 * 0.66) / 8) * 1000)

or 21 ms

Onto this you may want to add a standard value that reflects the time the message takes to leave the operating system onto the wire. 50 – 100ms should do.

In order to use the thread sleep function in eVB you have to import the export from the C++ corelib as follows

Declare Sub Sleep Lib “Coredll.dll” (ByVal dwMilliseconds As Integer)

Dim strMessage
strMessage = “I am a fish” & Chr(13) & Chr(10)

‘ Set to COM1, 9600bps, no Parity.
‘ Enable DTR Flow Control, disable RTS Flow Control, disable handshaking
msComm.CommPort = 1
msComm.Settings = “9600,n,8,1,r”
msComm.DTREnable = true

msComm.RTSEnable = false
msComm.Handshaking = 0

‘ Attempt to get control of the COM port if it isn’t available
On Error Resume Next
if (msComm.PortOpen) then
msComm.PortOpen = false
end if
On Error Goto 0

‘ Open the port, Send the message, shutdown the port
msComm.PortOpen = true
msComm.Output = strMessage
call Sleep((Abs(((Len(strMessage) / ((9600 * 0.50) / 8)) * 1000)) + 50))
msComm.PortOpen = false

Note: This code is highly summarised it will not work directly as a copy/paste, you need to modularise it and create the msComm object.

With the sleep function in place and the correct tuning you should now find that the data gets to the printer before eVB closes the COM port.

Remember that you need to implement the function of Sleep(150) after sending the wake-up NULL string to the printer.

VBScript Multidimensional Arrays, limits, annoyances and ways around them

System Requirements:

  • Windows 95, 98, 98SE, Millennium
  • Windows NT 4.0, 2000, XP, 2003, Vista, 2008, 7, 8, 2012, 8.1, 10
  • VB 4, 5, 6, VBScript, ASP 3, ASP Classic

The Problem:

Microsoft implemented a proprietary method of handling multi-dimensional arrays in VBScript, one which doesn’t quite allow a full range of functionality that you would expect from creating an array primitive.

This article discusses a few of those issues and some solutions to the problems.

More Info

I like using arrays, I use them a lot, especially if I want to get off of a database server quickly and only need a unidirectional cursor – frankly you seldom need anything other than a forward only cursor if you’re creative enough.

What is a Multidimensional Array

The problem with that is that in order to return a database query you have to use multi-dimensional arrays. Not a big deal, a multi-dimensional array is nothing special. In pretty much any programming language a multi-dimensional array is nothing more than an array of arrays.

A Two Dimensional (2D) array is an array or more than one array.

array(array(), array())

A Three Dimensional (3D) array is an array or more than one array each themselves containing more than one array.

array(array(array(), array()), array(array(), array()))

Things work a little differently in the VBScript, VB 6 and ASP world. Not so much in the logic of how an array is structured as an array of arrays, but as how you an interface with that structure.

Dim array(1,4)

Would create a 2 column, 5 row 2D array

0,0 1,0
0,1 1,1
0,2 1,2
0,3 1,3
0,4 1,4

The Undimensioned Dynamic Array Problem

In VB you can also do something like this

Dim myArray()
‘ Things happen here

ReDim myArray(1,4)

This step would create an empty dynamic array [Dim myArray()] – a memory pointer to a storage location that the system expects will eventually become an array sequence. Later in the code execution the array is redimensioned to become a 2×5 2D array.

Imagine that the ReDim never happens

Dim i
Dim arrOut()
if (1 > 2) then      ‘ <———– 1 is never greater than 2, so it will never be ReDim’d
ReDim arrOut(2,10)
arrOut(0,0) = 1
end ifwscript.echo IsArray(arrOut)

IsArray(arrOut) will return True (-1), it is a VB type of array after all. In our multidimensional structure this is asking whether the wrapper array is an array, which it is. It just doesn’t have any contents yet. So what happens now if we do this:

if (IsArray(arrOut)) then
for i = 0 to UBound(arrOut,2)
wscript.echo arrOut(0,i)
next
end if

… or even you might assume this

if (IsArray(arrOut)) then
if (UBound(arrOut,2) > -1) then
for i = 0 to UBound(arrOut,2)
wscript.echo arrOut(0,i)
next
end if
end if

Funnily enough, with both examples you will receive:

array.vbs (11, 1) Microsoft VBScript runtime error: Subscript out of range.

The UBound function is a VB Native function that has two parameters. UBound(<array>,<dimension>).

We can issue both UBound(arrOut,1) AND UBound(arrOut,2) against the variable arrOut and both a scan for the size of the First Dimension and Second Dimension will result in a “Subscript out of range” error.

Now, despite the fact that arrOut is an array, it is just an empty memory pointer, there is no array structure behind it – think of it like the array being there but its value is null with 0 dimensions.

OK! We have an empty array. So lets confirm that!

if NOT (IsEmpty(arrOut)) then    ‘ <—– Our array has no dimensions and null contents
for i = 0 to UBound(arrOut,2)
wscript.echo arrOut(0,i)
next
end if

Yet the IsEmpty function will return FALSE: the array is NOT empty.

So, according to VB, we have an array that is an array but that has no dimensions and no contents but isn’t empty!

To clarify, with the above example the following conditions occur

IsEmpty(arrOut) = false
IsArray(arrOut) = true
UBound(arrOut,1) = <crash: subscript out of range>
UBound(arrOut,2) = <crash: subscript out of range>

This is just a quirk of the language, it’s the way it is. But how do you test your way out of this?

Dim iRows
On Error Resume Next
iRows = UBound(arrOut,2)if (err.number <> 9) then     ‘ <——– 9 is the return code for subscript out of range
for i = 0 to UBound(arrOut,2)
wscript.echo arrOut(0,i)
next
end if
On Error Goto 0

Yes, you can do that. But it is very expensive and hardly eloquent unless you put it into an equally expensive boolean procedure for “isDimensioned()”. So what else can be done?

The problem is due to the simplicity of the language and some unfortunate design decisions by Microsoft on what to cut from VB compared to VBScript – it is a lot like Java vs. JavaScript.

If you are using VB rather than VBScript/ASP, there is a language construct that you can use to escape from this problem without resorting to exception handling.

Dim arrOut()
if (NOT arrOut) then          ‘ <—– This checks to see if there is a pointer for the array
‘ Code if the array is not initialised is run here
end if

Again, it does not however work in VBScript/ASP and to my knowledge there is absolutely no way to get past it in a pure, one line language construct in VBScript. There is however a way to solve the problem. Be it one that uses more lines.

Never use the Dynamic Array Declaration

The issue that is causing you to experience the subscript out of range statement is not in fact the VB array, but the way you declare a Dynamic array.

Dim arrOut()

With this declaration, it will always throw an exception if you call UBound() on the array before it has been ReDim’d.

Instead, you should declare your arrays using the following

Dim arrOut : arrOut = Array()

So to summarise, against this syntax the following conditions will be present:

IsEmpty(arrOut) = true
IsArray(arrOut) = true
UBound(arrOut,1) = -1

That is actually want you want. What we are doing is declaring the arrOut. As this is VBScript, the variable will be created of type Variant. The variable is then converted into a new Variant Array with no size (-1), however this time the three logic tests that we are looking to see some common sense from actually tell the truth.

Dim arrOut
if (IsArray(arrOut)) then      ‘ <—– This obviously isn’t an array
‘ Code if the array is not initialised is run here
end if

Put another way. It works, but it is sloppy, the variable is never technically initialised as an array, you are actually just relying on Variant manipulation. It can be tidied up by using

Dim arrOut
if (IsArray(arrOut)) then      ‘ <—– This obviously isn’t an array
arrOut = Array()
‘ Code if the array is not initialised is run here
end if

Instead of using colon notation (“:”) you can be more clever and write it long hand. In the example below, arrOut is legitimately becomes a VB Variant Array (type 8192 + 12) at the line arrOut = Array(). This process is closer to how you would dimension an array in Java, however, unfortunately, there is a new problem. You cannot call ‘ReDim Preserve’ on arrOut otherwise you will receive the dreaded ‘Microsoft VBScript runtime error: Subscript out of range.’ e.g.

Dim arrOut
if (IsArray(arrOut)) then      ‘ <—– This obviously is NOT an array
arrOut = Array()            ‘ <—– It is an array now
ReDim Preserve arrOut(4,5)  ‘ <—– This will throw an exception
‘ Code if the array is not initialised is run here
end if

This is only going to be a problem if you are going to dynamically resize a multidimensional array and include the preserve key word. Oddly, everything is fine if you resize it to a single dimension array and include the preserve. The solution is to ensure that the first time that you ReDim the array, you are NOT calling the Preserve key word e.g.

Dim arrOut
if (IsArray(arrOut)) then      ‘ <—– This obviously isn’t an array
arrOut = Array()            ‘ <—– It is an array now
ReDim arrOut(4,5)           ‘ <—– This will NOT throw an exception
ReDim Preserve arrOut(4,10) ‘ <—– This will NOT throw an exception
‘ Code if the array is not initialised is run here
end if

You can then immediately and safely use the preserve key word… however by now you have already lost your data.

Again, this is another unfortunate quirk of the language.

Applying it to an Example using Manually Created Multi-dimensional Arrays

Logically take a One Dimensional (1D) array.

Dim myArray(4)

If you UBound(myArray,1) this, it returns 4. Effectively the logical difference is that you created the wrapper array, and into THAT put an array with 5 rows. Calling UBound correctly identifies that there is an array within the wrapper array and returns its size – 4 (5 rows).

Repeating the same process with a 2D array

Dim myArray(1,4)

If you now call UBound(myArray,2) it returns 4. Here we logically created the wrapper array, put two arrays into that, each with 5 rows. A scan of the second column dimension reveals 5 rows.

So take a practical example.

SELECT id, name, date FROM user;

Using ADO getRows return the results into an array.

If there are 5 users in the table, we get a 2D array that responds to UBound(myArray,1) [3 == id, name, date] and UBound(myArray,2) [5 users each with an id, name and date].

What if there are no users? We can’t UBound() the array or test it, so the program crashes unless we use On Error Resume Next.

There is a solution to this.

What VB’s built-in functions lack is the ability to test for the presence of a dimensioned inner array. Effectively what we want to do is:

IsArray(myArray(0))

If that looks familiar it is because it is how virtually every C-derived language tests for a multi-dimensional array, get the array and then test to see if there is a first element in that array which itself is an array. Logically, an initialised Column set with an uninitialised Row Set would look like

myArray(0) null
myArray(1) null

While initialised Column set with an initialised Row set would look like

myArray(0) Array()
myArray(1) Array()

We know that myArray – the wrapper array – is an array, but is its first element also an array? If the element doesn’t exist we want to see FALSE come back. Sadly the VB multi-dimensional array structure is such that it is not possible to test this dimension using the proprietary methods; however you can if you do it manually using nested arrays (primitive arrays).

Dim arrWrapper(1)           ‘ <——– This is the Column set, there are 2 initialised columns (always)
Dim arrCol1(4)              ‘ <——– This is the Row set for Column 1
Dim arrCol2(4)              ‘ <——– This is the Row set for Column 2arrWrapper(0) = arrCol1     ‘ Inset the Row sets
arrWrapper(1) = arrCol2

  • We now have a 2×5 multi-dimensional array
  • We have an Array Wrapper (arrWrapper)
  • It has a legal first dimension
  • We put an array into both of its rows, each with 5 rows

Logically, this looks like

myArray(0)
arrCol1(0) null
arrCol1(1) null
arrCol1(2) null
arrCol1(3) null
arrCol1(4) null
myArray(1)
arrCol2(0) null
arrCol2(1) null
arrCol2(2) null
arrCol2(3) null
arrCol2(4) null

Now if we test IsArray(arrWrapper) we get True just as before, the Column set has been initialised and is easy to test. We can also test IsArray(arrWrapper(0)) to see if there is any Row data available. In other words we can now categorically know that arrWrapper HAS dimensions and has been properly initialised.

The same also applies if we don’t initialise the inner arrays; we can now explicitly test whether the inner array is a dimensioned array:

Dim arrWrapper(1)
Dim arrCol1(4)
Dim arrCol2(4)if (1 > 2) then    ‘ <———– 1 is never greater than 2
arrWrapper(0) = arrCol1
arrWrapper(1) = arrCol2
end if

if (IsArray(arrWrapper(0)) then
‘ Happy days had by all as this will never execute as column(0)’s contents is NOT an array
end if

What is the functional difference between this and the VB Native method?

‘ To Call Column 1 row 3 in the VB Native Array:
response.write myArray(0,2)’ To Call Column 1 row 3 in the Primitives Array:
response.write myArray(0)(2)

If you are wondering why this looks spookily familiar, don’t fret. It’s exactly what you do in most other procedural C derived languages. e.g. Java

int[][] myArray = new int[1][4];

System.out.println myArray[0][2];

Now to preserve your sanity (and line count) you can achieve all of the above using an in-line statement and deal with the sizing of the array at a later time using the following

Dim arrTest : arrTest = Array(Array(), Array())

‘ Now to prove that it isn’t sized yet (it has zero rows)
response.write UBound(arrTest,1)    <—- This will return: 1 (i.e. 2 columns)
response.write UBound(arrTest,2)    <—- This will crash: <Subscript out of range>
response.write UBound(arrTest(0))   <—- This will return: -1 (unintialised)
response.write UBound(arrTest(1))   <—- This will return: -1 (unintialised)

Advantages of Traditional C-Like Arrays

So why else might you want to build your array structures manually rather than by using the native functions?

There is are two useful and occasionally very helpful reasons why managing your arrays in the traditional C-like sense can be helpful.

Number 1: Column extraction. If you need to extract an entire columns from an array and manipulate it separately without passing a large data set around, manually defined multi-dimensional arrays is how you do it

Dim arrWrapper(1)
Dim arrCol1(4)
Dim arrCol2(4)arrWrapper(0) = arrCol1
arrWrapper(1) = arrCol2

arrWrapper(0)(0) = 5
arrWrapper(0)(1) = 10
arrWrapper(0)(2) = 12
arrWrapper(0)(3) = 8
arrWrapper(0)(4) = 2

arrWrapper(1)(0) = “Hello”
arrWrapper(1)(1) = “World”
arrWrapper(1)(2) = “How”
arrWrapper(1)(3) = “Are”
arrWrapper(1)(4) = “You?”

Dim arrStrings : arrStrings = arrWrapper(1)

The arrStrings variable now contains the entire contents of column 1. no if’s not but’s. If you want to do that with a VB Native array you have to iterate the data out, which itself carries a performance penalty:

Dim i
Dim myArray(1,4)myArray(0,0) = 5
myArray(0,1) = 10
myArray(0,2) = 12
myArray(0,3) = 8
myArray(0,4) = 2

myArray(1,0) = “Hello”
myArray(1,1) = “World”
myArray(1,2) = “How”
myArray(1,3) = “Are”
myArray(1,4) = “You?”

Dim arrString()
ReDim arrString(UBound(myArray,2)) ‘ Hopefully this is actually dimensioned!
for i = 0 to UBound(myArray,2)     ‘ At this pint we have sized myArray 3 times, costly
arrString(i) = myArray(1,i)
next

 

Number 2: Jagged Arrays. you generally don’t use Jagged Arrays very often as programmers tend to use arrays as table sets, however when operating with large volume data sets if you use VB native arrays and don’t need to logically think of your data as being tabular, you can wind up wasting a large amount of memory on empty array slots e.g.

myArray(0)
arrCol1(0) 1
arrCol1(1) 2
arrCol1(2) 3
arrCol1(3) 4
arrCol1(4) 5
myArray(1)
arrCol2(0) 1
arrCol2(1) null
arrCol2(2) null
arrCol2(3) 6
arrCol2(4) null

In a Jagged Array you do not need to over bound the inner array at all, as long as the context of your program permits it. Visually this would look like the following (note the removal of the Null values from the second column’s Row set).

myArray(0)
arrCol1(0) 1
arrCol1(1) 2
arrCol1(2) 3
arrCol1(3) 4
arrCol1(4) 5
myArray(1)
arrCol2(0) 1
arrCol2(1) 6

If we simplify the maths and pretend that each unallocated array slot is using a 32-bit pointer + a 32-bit null, we just saved 24 bytes of memory from being consumed for absolutely no reason.

The advantage therefore is that you can only manipulate Jagged Arrays using manually created primitive VB Arrays and not through the use of native multi-dimensional arrays.

Disadvantages of Traditional C-like Arrays

The technique clearly has some uses. But is there any reason not to do it?

Yes, there is and it depends upon application and the size of the array.

The VB Native functions are such that they call much faster C++ code at a lower level, where as VB activities itself happen in a slower, less efficient language. This is reflected in the RAS/CAS seek times for the arrays.

A 43 column 9,000,001 row (9 million and 1) array was tested for read and write operations using the two scripts below:

Manual (Primitive):

Option Explicit

Dim i
Dim dblStart
Dim dblEnd

Dim bol
Dim str
Dim i1
Dim i2

Dim arrMain(3)
Dim arr1(9000000)
Dim arr2(9000000)
Dim arr3(9000000)
Dim arr4(9000000)

arrMain(0) = arr1
arrMain(1) = arr2
arrMain(2) = arr3
arrMain(3) = arr4

‘ WRITE

dblStart = Timer()

for i = 0 to UBound(arrMain(0),1)
arrMain(0)(i) = true
arrMain(1)(i) = 17
arrMain(2)(i) = “hello”
arrMain(3)(i) = (10 * i)
next

dblEnd = Timer()

wscript.echo (dblEnd – dblStart)

‘ READ

dblStart = Timer()

for i = 0 to UBound(arrMain(0),1)
bol = arrMain(0)(i)
i1 = arrMain(1)(i)
str = arrMain(2)(i)
i2 = arrMain(3)(i)
next

dblEnd = Timer()

wscript.echo (dblEnd – dblStart)

VB Native:

Option Explicit

Dim i
Dim dblStart
Dim dblEnd

Dim bol
Dim str
Dim i1
Dim i2

Dim arrMain(3,9000000)

‘ WRITE

dblStart = Timer()

for i = 0 to UBound(arrMain,2)
arrMain(0,i) = true
arrMain(1,i) = 17
arrMain(2,i) = “hello”
arrMain(3,i) = (10 * i)
next

dblEnd = Timer()

wscript.echo (dblEnd – dblStart)

‘ READ

dblStart = Timer()

for i = 0 to UBound(arrMain,2)
bol = arrMain(0,i)
i1 = arrMain(1,i)
str = arrMain(2,i)
i2 = arrMain(3,i)
next

dblEnd = Timer()

wscript.echo (dblEnd – dblStart)

the first thing that you will note is that in using Manually created multi-dimensional arrays it has been necessary to write more lines of code to do the same thing,. This itself may be a disadvantage for you!

If you are willing to proceduralise the process however, the line count can be reduced back down again by using the sample generator function provided at the end of this article.

Results of the experiment (Time shown in seconds):

VB Array Method Experiment Results

Seconds
Primitive Write 5.154063
Primitive Read 4.843750
Native Write 3.695313
Native Read 3.593750

As you can see, the write process was 28.3% faster and the read 25.8% faster for the native array structure.

There is a clear and very evident performance hindrance to this method, however keep in mind that this array structure contained over 9 million rows and 3 columns – 27,000,003 ‘intersections’ to be exact. If you are writing a program that requires an array of that size that will suffer from the performance drop that comes from this method, you may want to consider changing to a compiled language for what you are doing.

Summary

There are some limits to the native VB array structure system, and as this has demonstrated there are ways around that thanks to more traditional array structure paradigms. It cannot be denied that there is a slight performance hit for larger array, possibly to do with more of the work of managing the array being performed by VB itself rather than by underlying and cleaner C++.

The benefits of the alternative cannot be ignored however, particularly when array extraction and content presence validation are required without resorting to litter you code with On Error Resume Next statements.

If you got this far into the document, well done for reading through. A function can easily be created to automate the build process for multi-dimensional VB primitive arrays. For those interested I have included an example of one below.

‘ Create a Primitive in Primitive (nested) VB Array
‘   iCols = Number of Columns
‘   iRows = Number of Rows
‘ © C:Amie (http://www.c-amie.co.uk/)public function mdArray(ByVal iCols, ByVal iRows)
Dim arrOut
Dim arrTemp

ReDim arrOut(iCols)

for i = 0 to iCols
ReDim arrTemp(iRows)
arrOut(i) = arrTemp
next

mdArray = arrOut
end function

‘ Example of its use
Dim myArr
Dim i

myArr = mdArray(1,4) ‘ <—- Here we create the empty array

wscript.echo UBound(myArr)       ‘ <—- This returns the column count (1 i.e. 2 columns)
wscript.echo UBound(myArr(0))    ‘ <—- This returns the row count (4 i.e. 5 rows)
wscript.echo IsArray(myArr)      ‘ <—- This returns: true
wscript.echo IsArray(myArr(0))   ‘ <—- This returns: true
wscript.echo “======================”

myArr(0)(0) = 1
myArr(0)(1) = 2
myArr(0)(2) = 3
myArr(0)(3) = 4
myArr(0)(4) = 5

myArr(1)(0) = “a”
myArr(1)(1) = “b”
myArr(1)(2) = “c”
myArr(1)(3) = “d”
myArr(1)(4) = “e”

for i = 0 to UBound(myArr(0))
wscript.echo myArr(0)(i)
wscript.echo myArr(1)(i)
next

Happy coding.

How to Enable Web Based Enterprise Management (WBEM) / Windows Management Instrumentation (WMI) for Remote Access under Windows NT 4.0

System Requirements:

  • Windows NT 4.0
  • Service Pack 4 or Higher

 

The Problem:

This document outlines the steps required to connect to a Windows Millennium machine using WMI using either a Monkier (inherited session credentials) or SWbemLocator.ConnectServer (specific, stated logon credentials).

This is often difficult to achieve, especially in a domain environment or an environment where more modern versions of Windows need access to the remote WMI service.

 

More Info

Windows NT 4.0 does not ship with any of the WMI components. Microsoft released WBEM version 1.5 for NT 4.0 as a separate download.

This guide will show you how to properly install WMI in order to facilitate remote access connections over a Network. Please note that while this will enable WMI requests over a LAN, the settings will not discriminate between a LAN and an Internet connection. Consequently firewall technology should be employed when dealing with systems that expose a direct peer-to-peer Internet connection.

 

The Fix

The steps below will enable you to connect remotely over WMI under some circumstances (but not all). These steps must be applied to install WMI and configure it before you can change the advanced remote connection settings.

The WMI 1.5 installer for Windows NT includes the DCOMCnfg.exe utility that is required for GUI configuration of WMI for remote access. Use the following steps to configure WMI for remote access.

  1. Install WMI 1.5 for Windows 9x
  2. When prompted, reboot the computer

 

Configure DCOM

Once you have installed dcm95cfg.exe you must configure DCOM. To configure DCOM run the DCOMCnfg utility found in c:\windows\system

  1. By default the “Enable Distributed COM on this computer” check box on the Default Properties tab should be ticked. Often if WMI isn’t working, this has inexplicably become unchecked, disabling DCOM completely.DcomCnfg Screen 1
  2. This will enable WMI to run on the local computer, but not from remote network connections. To enable the basic level of remote access check the “Enable remote connection” box on the Default Security tab. If this doesn’t work for you, keep reading!DcomCnfg Screen 2
  3. Restart the computer.

Windows NT implements WMI as a system service, so as long as the service is running and DCOM is started you will be able to remotely load WMI classes.

All being well, you should now have a working Windows NT machine which supports Remote WMI from modern Windows systems. Do keep in mind that these steps do not cover basic troubleshooting. You should already have Windows Script Host 5.6 installed and Internet Explorer 6.0 SP1.

If you would like to enable all of these settings without launching their respective utilities, you can use this registry script to automatically configure workstations with the remote access settings.

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole]
"EnableDCOM"="Y"
"DefaultLaunchPermission"=hex:01,00,04,80,64,00,00,00,80,00,00,00,00,00,00,00,\
14,00,00,00,02,00,50,00,03,00,00,00,00,00,18,00,01,00,00,00,01,01,00,00,00,\
00,00,05,12,00,00,00,00,00,00,00,00,00,18,00,01,00,00,00,01,01,00,00,00,00,\
00,05,04,00,00,00,00,00,00,00,00,00,18,00,01,00,00,00,01,02,00,00,00,00,00,\
05,20,00,00,00,20,02,00,00,01,05,00,00,00,00,00,05,15,00,00,00,a0,5f,84,1f,\
5e,2e,6b,49,ce,12,03,03,f4,01,00,00,01,05,00,00,00,00,00,05,15,00,00,00,a0,\
5f,84,1f,5e,2e,6b,49,ce,12,03,03,f4,01,00,00

 

See Also

View: How to Enable Web Based Enterprise Management (WBEM) / Windows Management Instrumentation (WMI) for Remote Access under Windows 95, 98/98SE

View: How to Enable Web Based Enterprise Management (WBEM) / Windows Management Instrumentation (WMI) for Remote Access under Windows Millennium Edition

How to Enable Web Based Enterprise Management (WBEM) / Windows Management Instrumentation (WMI) for Remote Access under Windows Millennium Edition

System Requirements:

  • Windows Millennium Edition

 

The Problem:

This document outlines the steps required to connect to a Windows Millennium machine using WMI using either a Monkier (inherited session credentials) or SWbemLocator.ConnectServer (specific, stated logon credentials).

This is often difficult to achieve, especially in a domain environment or an environment where more modern versions of Windows need access to the remote WMI service.

 

More Info

Windows Millennium Edition ships with the latest version of WMI for Windows 9x, that being version 1.5.

This guide will show you how to properly install WMI in order to facilitate remote access connections over a Network. Please note that while this will enable WMI requests over a LAN, the settings will not discriminate between a LAN and an Internet connection. Consequently firewall technology should be employed when dealing with systems that expose a direct peer-to-peer Internet connection.

More Info: WMI on computers running Windows 98, 98SE and Windows Millennium Edition

 

The Fix

The steps below will enable you to connect remotely over WMI under some circumstances (but not all). These steps must be applied to install WMI and configure it before you can change the advanced remote connection settings.

Just as with the WMI installation for Windows 95/98 and unlike the Windows NT. The WMI 1.5 installer does not include the rather useful DCOMCnfg.exe utility that is required for GUI configuration of WMI for remote access. Thankfully the DComConfig utility was made available separately for Windows 9x. Despite being branded for Windows 95/98, the program works fine under Windows Millennium if you are more comfortable using the GUI to configure WMI than using the registry file at the end of this document.

DCOM Icon

Configure DCOM

Once you have installed dcm95cfg.exe you must configure DCOM. To configure DCOM run the DCOMCnfg utility found in c:\windows\system

DComCfg Utility Launch

  1. By default the “Enable Distributed COM on this computer” check box on the Default Properties tab should be ticked. Often if WMI isn’t working, this has inexplicably become unchecked, disabling DCOM completely.DcomCnfg Screen 1
  2. This will enable WMI to run on the local computer, but not from remote network connections. To enable the basic level of remote access check the “Enable remote connection” box on the Default Security tab. If this doesn’t work for you, keep reading!DcomCnfg Screen 2
  3. Restart the computer.

It is important to understand that unlike with Windows NT, Windows 9x doesn’t have the concept of a special system service. Consequently DCOM won’t start WMI when it is requested from the network. In order to enable on-demand access the WinMgmt.exe program in c:\windows\system\wbem needs to be running.

WinMgmt

This dos prompt command will run the process for the duration of the session. You can always confirm that it is running by pressing Ctrl + Alt + Del once and viewing the task manager

WinMgmt Process in Ctrl + Alt + Del Screen

If you want to persistently run WinMgmt.exe as a ‘Service’ add the following information to the registry as shown.

 

Setup WinMgmt as a start service

Advanced Remote Access Settings

The above settings should work between NTLM enabled Windows 9x boxes and NT 4 systems, however you may have problems if the Active Directory extensions are installed or if you are attempting to connect from a Windows 2000, XP, 2003, Vista, 2008 or 7 machine.

Connection difficulties seem particularly true if you are using SWbemLocator.ConnectServer or are in any way connecting via IIS.

Do appreciate that this guide isn’t covering more basic things like “synchronise your accounts and passwords” between workgroup systems, ensure that you have IE 6.0 SP1 installed or that you have Windows Scripting Host 5.6 installed and so on.

First off, re-register the WinMgmt stack using the following command in the c:\windows\system folder

RegSvr Command for WMI

WBEM ships with an additional configuration utility, the WBEM Control app, found in c:\windows\system\wbem\

wbemcntl.exe

On the advanced tab enable the “Enable Anonymous Connection with share level security” setting and restart the computer.

Enable Anonymous Share Access

This setting allows WMI to respond to network requests that do not carry the required account credentials registered as having access to the WMI class hierarchy.

If you are now able to connect using SWbemLocator.ConnectServer you may like to try to reconfigure WMI to use WMICntl to assign permissions to WMI. To do that simply use the Security tab and, selecting Root, press the Security button and add in the user accounts that you want to gain remote access with to the security permissions list.

WMI Control Security

All being well, you should now have a working Windows 98 machine which supports Remote WMI from modern Windows systems.

If you would like to enable all of these settings without launching their respective utilities, you can use this registry script to automatically configure workstations with the remote access settings.

REGEDIT4

[HKEY_LOCAL_MACHINE\Software\Microsoft\OLE]
"EnableDCOM"="Y"
"EnableRemoteConnect"="Y"

[HKEY_LOCAL_MACHINE\Software\Microsoft\WBEM\CIMOM]
"EnableAnonConnections"="1"
"EnableStartupHeapPreallocation"=dword:00000000

[HKEY_LOCAL_MACHINE\Software\Microsoft\WBEM\Scripting]
"Enable for ASP"=dword:00000000

[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices]
"WinMgmt"="C:\\Windows\\System\\WBem\\WinMgmt.exe"

 

See Also

View: How to Enable Web Based Enterprise Management (WBEM) / Windows Management Instrumentation (WMI) for Remote Access under Windows NT 4.0

View: How to Enable Web Based Enterprise Management (WBEM) / Windows Management Instrumentation (WMI) for Remote Access under Windows 95, 98/98SE