vba11

113
How to Use VBA & Excel with the Bit3 to VME R. Angstadt March, 5 2004

description

vba um novo curso

Transcript of vba11

Page 1: vba11

How to Use VBA & Excel with the Bit3 to VME

R. Angstadt

March, 5 2004

Page 2: vba11

Why VBA? Why not VB? C?Great Glue Language:

1. VBA=Visual Basic (for) Applications. VB=Visual Basic is a standalone product that must be purchased separately. (“VB(A)” indicates features common to both!) VBA comes with Excel so there is no extra cost. If you are running some version of windows with Office installed then it is on your machine now! VB(A) allows reuse of the million(s) of lines of C code that Excel is written in to be reused. It provides an easy way to re-use code. No building of custom forms are required because it is so easy to use a Worksheet as a pre-defined form it’s usually not worth the trouble to make a custom one!

a. VBA knows about and can access all of the functions in Excel either natively or using the function “Application.ExecuteExcel4Macro (“an_Excel_Function_Here!”)” VBA runs in Excel’s context/scope. VBA can access ~99.9% of all the Excel menus and any objects, methods or functionality they perform.

b. VB(A) can call almost any function of any DLL on your computer including Kernel calls, anything you write or someone else writes in other languages such as C and/or inline assembler. The DLL it calls into could also be a “driver” DLL’s that goes to hardware! VBA and Excel are engineered to be extensible: DDE, OLE, COM & DCOM. Microsoft has published book on extending it. (“Excel Developers Kits” (various years) Book and disk(s) or CD.)

c. Spreadsheets analyze stock market quotes in real time and the original article Marvin read in EE times involved operating a Nuclear Reactor!

Page 3: vba11

2. VBA & Excel Enables Rapid code prototyping/development:

a. Saves lines of code! Engineered to be simpler than C. Savings can be as much as ~ 40 lines of C to one VB line! A simple peek, poke real windows program all in C takes > 1k lines excluding the Bit3 libraries/drivers. A Bit3 list processor can be done in < 100 lines excluding the same Bit3 libraries/drivers. Typical realized average savings in lines of code has been on the order of a factor of ~10.

b. Saves person power: The HV front panel application that took 6 to 8K lines of Pascal and ~2-3 PERSON YEARS was done in ~ 1,000 lines in ~ 4 months. (In terms of lines saved for this project at least a factor of 6 (conservatively) or more in savings.)

3. Originally suggested by M. Johnson as an easier way to move from DOS (Turbo Pascal) to Windows environment. (We actually tried something similar cerca ’86 with Lotus 123 macros but it was not modular and hard to maintain and the screen flashed annoyingly.) VBA and Excel have overcome these deficiencies.

Page 4: vba11

FYI only: Before GetOpenFileName in C can be called it takes 44 lines to fill the structure & call! void PopFileInitialize (HWND hwnd) { static char szFilter[] = "Data Files (*.DAT)\0*.dat\0" \

"Paint Files (*.BMP)\0*.bmp\0" \ "Text Files (*.TXT)\0*.txt\0" \ "All Files (*.*)\0*.*\0\0" ;

ofn.lStructSize = sizeof (OPENFILENAME) ; ofn.hwndOwner = hwnd ; ofn.hInstance = NULL ; ofn.lpstrFilter = szFilter ; ofn.lpstrCustomFilter = NULL ; ofn.nMaxCustFilter = 0 ; ofn.nFilterIndex = 0 ; ofn.lpstrFile = NULL ; // Set in Open and Close functions ofn.nMaxFile = _MAX_PATH ; ofn.lpstrFileTitle = NULL ; // Set in Open and Close functions ofn.nMaxFileTitle = _MAX_FNAME + _MAX_EXT ; ofn.lpstrInitialDir = NULL ; ofn.lpstrTitle = NULL ; ofn.Flags = 0 ; // Set in Open and Close functions ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = "dat" ; ofn.lCustData = 0L ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; }

BOOL PopFileOpenDlg (HWND hwnd, PSTR pstrFileName, PSTR pstrTitleName) { ofn.hwndOwner = hwnd ; ofn.lpstrFile = pstrFileName ; ofn.lpstrFileTitle = pstrTitleName ;

ofn.lpstrDefExt = "dat" ; ofn.Flags = OFN_HIDEREADONLY | OFN_CREATEPROMPT ;

return GetOpenFileName (&ofn) ; } // ..... here's the call in some other routine someplace else PopFileInitialize (hWnd) ; if (PopFileOpenDlg (hWnd, szFileName, szTitleName)) { .... open the file and read something from it.

Page 5: vba11

File Dialoque Boxes in VBA takes a few lines.• Information can always be read or written to a file. Furthermore it can be done using

the same “file dialogue” box Excel uses with just a few lines of VBA code. Here the whole get a file to read routine takes 14 lines. ~ 2 lines for arguments & 1 to make the call is ~3 lines max.

Sub getFileNameVRB() Dim szTitle As String Dim szFilter As String Dim szFileName As String Dim szOKwasPressedNoCancel As String ' Select a file to print, use the standard excel get a file dialogue box szTitle = "S record .hex file to process" szFilter = "S record .hex Files (*.hex), *.hex" szOKwasPressedNoCancel=Application.GetOpenFilename(FileFilter:=szFilter,Title:=szTitle) If szOKwasPressedNoCancel <> "False" Then szFileName = szOKwasPressedNoCancel Cells(12, 4).Value = szFileName ‘could open it here and do something! End IfEnd Sub 'getFileNameVRB“

• Here’s the VBA help notes dialogue box for obtaining a file to write (save) to taking ~1 lines (plus 3 for error handling!).

“fileSaveName = Application.GetSaveAsFilename( _ fileFilter:="Text Files (*.txt), *.txt") If fileSaveName <> False Then MsgBox "Save as " & fileSaveNameEnd If”

• See the VBA help notes for use of “Open”, “Close”, “Print”, “Write” ect.

Page 6: vba11

VBA has been Modernized!• Line numbers obsolete (though still supported!) It’s now a modern language with block structure

concepts like Pascal and C including Case statements. Has modules (ala Modulo), arguments passed by reference by default (like Fortran so arrays can be passed like many are used to. )

• Has a very nice IDE (Integrated Development Environment (ala Borland Turbo Pascal) with editor and real debugger, and context sensitive help.

• Has Short Comings but is Quite Useable. Any shortcomings can usually be worked around and/or overcome with some C code in a DLL! No show stoppers so far!

• It has maintained it’s backward compatibility very well. (Old code has not been broken. Of course macro recording on say Version 10.x and then running on Version 9.x may not work! Have to record on Version 9.x and run on Version 10.x!) (Even includes code from some early DOS versions as per Craig Symonds in ’96 Group Programmer Manager for the VB group (VB, VBA, and scripting for last 5 years from ’96. (pg xxx of VBA Developer’s Handbook, Sybex ’97 by K. Getz & M. Gilbert.) I can verify this as one time I used an old function IBM DOS function from an IBM Basic manual that I had laying around but was not Microsoft’s help at the time. When I typed it in it ran! (Integer is still 16 bits even after they ported it from a 16 bit version to a 32 bit version!)

• Will try to not speak too much about operating systems as it’s almost a moot point here but it will be unavoidable at some point so I would like to explain the following right now in the beginning.

1. “Win 9x” shall mean Windows 95, (OSR2), Windows 98 (1 and 2) through “ME” (“Millennium Edition”) because from the stand point of most driver development including my Bit 3 drivers they are the same. (Same driver for all of them!)

2. Likewise, “NT” shall also include Win2k, and XP in a generic sense as they all use basically the same driver model which is of course is a completely different driver model from #1 above. [Win 9x and NT were developed by two completely different programmer “teams” with different design goals and emphasis even thought they are from the same company so they are really pretty different operating systems. Though they share some components, their code base is different.] Go to “File Explorer”, “Help”, “About” in each and you will see NT is “Release 4” (Service pack 6) Win2k is “Release 5.0” (Service pack 3) and XP is (only) “Release 5.1”. So except for marketing and hype they are all still basically NT. The same goes for driver model. The same Bit3 driver of mine almost works on all of them except for XP which requires a tweak and has it’s own version. (Haven’t tried the XP tweak on Win2k and/or XP.) Thus, NT shall mean any and all of NT, Win2k, or XP unless XP or Win2k are specifically mentioned.

Page 7: vba11

Our First VBA program!• The key combo Alt-F11 brings up the VBA IDE. (If not then “Tools”,

”Macro”, “Visual Basic Editor”) brings it up. (Many ways to get back to Excel including Alt-F11.

• On the VB IDE click on “Insert” and then “Module” and “Module1” comes up in the property box. In the largest white area type in

“Sub hello() Cells(1, 1).Value = "hello world"End Sub“

• Go back to Excel (Alt-F11) and run it. Various ways but Alt-F8 works! And then run! “hello world” appears in upper left hand corner cell “A1”. Not so exciting by itself. But it could be put anyplace on the screen by changing the 1,1 to any row numbers of 1 to 65536 and the column number from 1 to 255. Also note that “cells” ends in an “s” (plural.) This is intentional and signals it is an array and/or “collection”. For arrays and/or collections within Excel this is amazingly consistent! The “.” is an separator for objects and/or methods. Thus “object.sub_object_child1…sub_object_n.final_object_or_method” is typical syntax. More and better concrete examples to follow.

Page 8: vba11

CountWrite: our 2nd Subroutine!• Slightly more exciting is:

“ Sub count2Ten() Dim lRow As Long For lRow = 1 To 10 Cells(lRow, 1).Value = lRow Next lRow End Sub “

• Although it is not required to declare everything, it is a good idea to do so. Putting “Option Explicit” at the top of the module requires that you declare everything.

• Another thing that is good to get in the habit of doing is after you make an edit in the VBA IDE is to go to the “Debug” menu pick and then “Compile”. One good reason to do this is that if you are just making a small change it may not be seen by VBA so the “Compile” will be grayed out! For example if you go to the 10 and type in 5 and then go run the macro chances are it will do the loop 10 times. To remedy this one must move the cursor off the line and/or go to the end of a line and hit the [Enter] key (it adds a blank line, you can delete it if you want!). But the point of all this is to make some changes in the code that it will see so that the “Compile” will not be grayed out. While some interpretation is done while you type, “Compiling” does a lot more including (argument) type checking of the whole project. It’s just a good idea and may save you having to come back and add a “Dim” statement as well as possibly helping to keep the Worksheet file size minimal. Also when the button is pushed there will be lower latency the first time as it won’t have to automatically compile it first: (assuming it was a big enough change that it saw…) it will already have been “compiled”!

Page 9: vba11

Our Third: Puts Formulas in Sheet!“Sub Circumference() 'puts formulas in the cells: sheet can update immedietly when user types in a new radius! Dim lAry(11) As Long Dim lRow As Long Dim lIndex As Long 'put up some headers Cells(1, 3).Value = "Radius" Cells(1, 5).Value = "Circum Formulas!" 'read output of sub count2Ten() For lRow = 1 To 10 lAry(lRow) = Cells(lRow, 1).Value Next lRow 'make a little radius, circumference table lIndex = 1 For lRow = 2 To 11 Cells(lRow, 3).Value = lAry(lIndex) Cells(lRow, 5).Formula = "=2 * Pi() * C" & Format(lRow) lIndex = lIndex + 1 Next lRowEnd Sub 'Circumference“

Page 10: vba11

Circumference2: Just the Values!“Sub Circumference2() ' another way: all from code: the macro must be re-run if any radi are changed! ignores user input! Dim lAry(11) As Long Dim lRow As Long Dim lIndex As Long Dim dPi As Double 'put up some headers Cells(1, 3).Value = "Radius" Cells(1, 7).Value = "Circum Values Only" 'read output of sub count2Ten() For lRow = 1 To 10 lAry(lRow) = Cells(lRow, 1).Value Next lRow ‘way cool: calling an excel function from within VBA! dPi = Application.ExecuteExcel4Macro("Pi()") 'make a little radius, circumference table lIndex = 1 For lRow = 2 To 11 Cells(lRow, 3).Value = lAry(lIndex) Cells(lRow, 7).Value = 2# * dPi * CDbl(lAry(lIndex)) lIndex = lIndex + 1 Next lRowEnd Sub 'Circumference2“

Page 11: vba11

Circumference 3: Using Functions“Sub Circumference3() ' uses functions for better code reuse Dim dAry(11) As Double Dim lRow As Long Dim lIndex As Long Dim dPi As Double 'put up some headers Cells(1, 3).Value = "Radius" Cells(1, 9).Value = "Circum Values Using Function calls in code" 'read output of sub count2Ten() For lRow = 1 To 10 dAry(lRow) = Cells(lRow, 1).Value Next lRow 'make a little radius, circumference table dPi = getPi() lIndex = 1 For lRow = 2 To 11 Cells(lRow, 3).Value = dAry(lIndex) Cells(lRow, 9).Value = doCircum(dAry(lIndex), dPi) lIndex = lIndex + 1 Next lRowEnd Sub“

Page 12: vba11

User Written Functions called by Circumference3! (can call from Excel!)

“Function getPi() As Double' this is a way cool thing: any function in excel you can call from vba!' pi() is an excel function returning pi to 15 places; it’s not intrinsic to vba! getPi = Application.ExecuteExcel4Macro("Pi()")End Function

Function doCircum(dRadius As Double, dPi As Double) As Double' you can call this from another vba sub or function or right from excel!' if excel doesn't have a function you need you can write your own!' even making use of the functions built into excel. doCircum = 2# * dRadius * dPiEnd Function“

Page 13: vba11

Icing on the (GUI) cake: Buttons• Lets Make a button and assign it to a macro: from Excel go to “View”, “Toolbars” and

make sure “Forms” is clicked. Then click on the fourth object on the “Forms” toolbar which is a “Button”. Move the mouse to the Worksheet and press and hold down the left mouse button and drag it somewhere and release it.

• You’ve made “Button 1” It can be attached directly to any subroutine (without arguments) that you write. Right click on it and attach it to one of the macros that we wrote earlier, say “Circumference”, change the text on the button, “Button 1”, to “Circumference”. Unselect the button by clicking anywhere on the sheet.

• Now any time you left click on the button the “Circumference” macro will be run. It took 0 code to make the button and assign it to the macro! Alternatively you could have done it all from code: You can change it’s name or re-assign which macro it is run with at any time. It’s all built into Excel.

• Macros can be attached to many objects: “TextBoxes”, Pictures, (.gifs) etc. • Note: there are 2 buttons, (2 kinds of objects). Global and restricted (or local scope).

(historically, old and new). I prefer the Global objects on the Forms: button because I believe they are more useful: one can attach them to generalized code that will work on the “Active” sheet. The other “Control Toolbox” buttons/objects are restricted and work only on the sheet they are attached to. They can’t be copied to another sheet. Further pain is that they are edited in “Design mode”. (Sheet cannot be saved when in “Design Mode”) They are just one pain after another and I believe they should be avoided (like the plague.... Button from hell, etc. You’ve been warned!)

Page 14: vba11

Subroutines and Functions: Which when?

• Though Functions can return something in a cell when they are placed in a cell they generally can NOT in any way act on the screen: e.g. can’t use Cells(row,col).Value in a function to write to the sheet. (Must use a “Sub”) Also:

Function try2callExcel4(szS As String) 'if szs="zoom(75)" it doesn't zoom the screen because a function can NOT

act on the screen Application.ExecuteExcel4Macro szSEnd Function

Sub zoom() ' however this changes the screen size of the active sheet! Application.ExecuteExcel4Macro "ZOOM(75)"End Sub

If something doesn’t work in a function try a sub!• Functions can NOT be hooked to a button or other object. (Must use a “Sub”). (Also

if a subroutine has arguments it cannot be attached to a button directly. So to test a generalized module (with arguments) must write a Test module to call the generalized module. Sometimes referred to as a “shell” sub or just “shell”.)

• But functions can be put in a cell and return a value! (“Sub” can NOT.)

Page 15: vba11

Real Power, Importing External Functions in DLL’s!• VB(A) has the ability to “Register” any external function in any DLL (Dynamic Link Library) on

your computer. This DLL could ultimately be connected through a driver to hardware! This can be as general or as specific as the DLL and driver it connect to. [Don’t get thrown by DLL. It just means that the address of an external (object, binary or library) function is found, resolved, loaded, and executed (run) dynamically when called by a Click of a button somewhere or by some piece of code doing something! This is in contrast to the older more typical Static Library that is linked to at Code Compile and Link time. Win9x and NT are both almost entirely based on “modules” of (typically C) code which can call into each other back and forth by the mechanism(s) of dynamic loading and linking (DLL)! What it allows is just a code module (DLL) to be recompiled and/or replaced (as long as no entry points are deleted or no arguments of functions have been reduced. Their can be more functions but not less.) It also saves having to “recompile the (whole) kernel”. One just has to recompile that DLL which may or may not be part of the Kernel.]

• For our first example of “registering” and calling an external DLL we’ll go right for the maximum gusto! Remember when Basics’ of yore included “peek” and “poke” (ing) to hardware? This is now once again made possible on NT if Dale Robert’s clever driver giveio.sys is installed. (Win 9x needs no driver, just a similar DLL!) (For more about this see DDJ May ’96. “Port I/O under Windows and NT” Dale’s driver also saves a few hundred clocks of NT CPU time each time a “peek” or “poke” is made so that NT is now as fast as Win9x!) The old (and new) Peek() and Poke() routines allow one to get out on both the ISA and PCI buses via the motherboard’s chipset(s) to physical address 0 to 65535 in I/O space. On Intel ’86 (all my stuff is only for Wintel platform: no other CPU is supported and only 1 CPU at that!) there is actually a physical CPU pin labeled “I/O”! When this pin (bit) is high then the address on the bus is to I/O. When not high it’s a normal (non-I/O) address. (Basically a 1 bit address modifier instead of VME’s 6 bit address modifier.) If NT is like eating at McDonald’s and Win98 is like eating at Burger King then this driver allows us to eat at McDonalds and “have it (our) way!” With this one can be destructive and corrupt your hard drive or you can be constructive and get to the Parallel Port directly or talk directly to CAMAC via an ISA bus “DSP” card board set written entirely in VBA? (Used in the village and else where.). (The NT Bit3 DLL is as fast as it is partly due to this driver.) The DLL is on “d0server4\users\angstadt\ntuitl\ntio\debug\ntio.dll” Giveio.sys install instructions are on the web:

http://d0server1.fnal.gov/users/angstadt/www/b3/b3_61x.htm

Page 16: vba11

Real Power, Importing External Functions in DLL’s! (2)• [There is a similar DLL and driver for normal non-I/O memory as well “d0server4\users\angstadt\

ntuitl\ntphsad\debug\ntphsad.dll so with these 2 drivers and DLL one can get to the hardware of their machine on a Virtual Operating System. Drivers may be written entirely in VBA!]

• Peeking probably won’t hurt your machine too much but (RANDOM) Poking is STRONGLY discouraged!

• So here it finally is the VBA code “modNT_IO”. Copy and paste it in to your spreadsheet or use d0server4\users\angstadt\ntutil\ntio.xls once giveio.sys is installed. Lets be careful out there! I/O address 0x200 is “nominally” safe as it is “nominally” the game port. However your machine may be different and/or not have one. Reading 0xff back is essentially “not there”, not initied, or in VME speak, “NO DTACK”. I/O address 0x378 is nominally LPT1 but this depends on the BIOS and “Ports”, “Resources” in the “Device Manager” of the “Control Panel”. Your mileage will definitely vary. Finally this is the VB(A) code:

“Declare Function pokeport Lib "ntio.DLL" (iAdd As Integer, iVal As Integer) As IntegerDeclare Function peekport Lib "ntio.DLL" (iAdd As Integer) As Integer

Function VB_pokeIO(iAdd As Integer, iVal As Integer) As Integer Dim i As Integer i = pokeport(iAdd, iVal) VB_pokeIO = iEnd Function

Function VB_peekIO(iAdd As Integer) As Integer Dim i As Integer i = peekport(iAdd) VB_peekIO = iEnd Function “

Page 17: vba11

The C code the VBC calls:• Part of ntio.c (so simple I didn’t make a ntio.h file. (Shame on me.) Normally the *.h file is what one looks at to

“register” (import) C DLL’s into VB(A) as above.) If I did have a ntio.h file it would have about 2 lines in it: DLLEXPORT short int WINAPI pokeport(long *plportadd, long *plval); DLLEXPORT short int FAR PASCAL peekport(long *plportadd);• The *.h file is supposed to hold the function “declarations” that are exported so other modules can include the *.h

file so that they may (static) link to it at compile time. I (unfortunately must have been in a hurry) and skipped making a *.h file. I evidently only cared about the VB(A) dynamic part. Below is the relevant part of ntio.c:

“…DLLEXPORT short int WINAPI pokeport(long *plportadd, long *plval)//writes ival to the p.c. port address specified by plportadd.{ int ilocalval; unsigned short wadd;

ilocalval=(int) *plval; wadd=(unsigned short) *plportadd & 0xffff; return _outp(wadd,ilocalval);};//{---------------------------------------------------------------------------}DLLEXPORT short int FAR PASCAL peekport(long *plportadd)//reads a value, itemp, from the p.c. port address specified by plportadd.{ unsigned short wtemp;

wtemp=(unsigned short) *plportadd; return _inp(wtemp);};…“

Page 18: vba11

FYI: MOST OF THE WHOLE NTIO.C DLL SOURCE clipped some comments:ALSO IS AN EXAMPLE OT WHAT EXCEL SAVES YOU FROM! (FYI only. No quiz on this.)

“….# include <windows.h>//# include <c:\cpp\xlcall.h> nothing from this really used# include <stdio.h># include <conio.h> // macro def for port i/o outp is in conio.h# include <dos.h>//# include <alloc.h> // nec. for malloc()# include <process.h> // nec. for exit()# include <string.h>//# include <math.h>//true defined in windows.h and other places//#define FALSE 0;//#define TRUE 1; HANDLE hDriver;

/* gets called at load time...when the function is "registered". *//* main entry point */BOOL WINAPI DllMain(HINSTANCE hDLLInst, DWORD fdwReason, LPVOID lpvReserved){

BOOLEAN bret;#ifdef _DEBUG OutputDebugString("\n\rIn DLLEntryPoint: DllMain\r\n");#endif switch (fdwReason) { case DLL_PROCESS_ATTACH: // The DLL is being loaded for the first time by a given process. // Perform per-process initialization here. If the initialization // is successful, return TRUE; if unsuccessful, return FALSE.

//5/17/99 try to open up giveio.sys

// give this process access to any i/o space transparently! hDriver = CreateFile("\\\\.\\giveio", GENERIC_READ, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hDriver == INVALID_HANDLE_VALUE)

{

Page 19: vba11

//printf("Couldn't access giveio device\n");bret=FALSE;

} else

{ bret=TRUE;

}//giveio modifies the i/o permission map in this task's tss // task segment selector (according to the author) so// once this is done, it's done! when the last dll user goes so// does the task and it's permission map so we don't need giveio.sys// anymore! (very very nice!) no cpu cycles are used on nt protection!

CloseHandle(hDriver); break;

case DLL_PROCESS_DETACH: // The DLL is being unloaded by a given process. Do any // per-process clean up here, such as undoing what was done in // DLL_PROCESS_ATTACH. The return value is ignored.

// release all driverrs and resources //if (callgateflg!=0) {

//if (!DeviceIoControl(hDevice, DMABUF_FUNC_FREEBUFFER, // &dmadesc, sizeof(DMA_BUFFER_DESCRIPTOR), // NULL, 0, &cbBytesReturned, NULL)

// ) //{ //printf("DeviceIoControl failed, error=%d\n", GetLastError() );

// should put return false here //}

} // close mapmem.sys handle (totalio.sys closed in init)

CloseHandle(hDriver); bret=TRUE; break;

case DLL_THREAD_ATTACH: // A thread is being created in a process that has already loaded // this DLL. Perform any per-thread initialization here. The // return value is ignored.

bret=TRUE; break;

case DLL_THREAD_DETACH:

Page 20: vba11

// A thread is exiting cleanly in a process that has already // loaded this DLL. Perform any per-thread clean up here. The // return value is ignored.

bret=TRUE; break; } return bret;}#define DLLEXPORT __declspec(dllexport)// this is the meat and potatoes//{---------------------------------------------------------------------------}DLLEXPORT short int WINAPI pokeport(long *plportadd, long *plval)//writes ival to the p.c. port address specified by iportadd.{ int ilocalval; unsigned short wadd;

ilocalval=(int) *plval; wadd=(unsigned short) *plportadd & 0xffff; return _outp(wadd,ilocalval);};//{---------------------------------------------------------------------------}DLLEXPORT short int FAR PASCAL peekport(long *plportadd)//reads a value, itemp, from the p.c. port address specified by usportadd.{ unsigned short wtemp;

wtemp=(unsigned short) *plportadd; return _inp(wtemp);};//{---------------------------------------------------------------------------}“

Page 21: vba11

Call the Kernel from VBA! This is modTime in some sheets (VB(A)) code!“Public Declare Function timeGetTime Lib "winmm.dll" () As LongDeclare Sub Sleep Lib "Kernel32.DLL" (ByVal dwMillisecconds As Long)Const lSLEEPTIME As Long = 12 'millisecondsSub AbenchRead() Dim l As Long Dim lTime As Long Dim lPlace As Long Dim ival As Integer Const lMAX As Long = 1000 Const lAdd As Long = 33536 '"&H8300" -> a neg. num! 'MsgBox (" get ready to set your stop watch") lTime = getTimerVal For l = 1 To lMAX ival = VB_readi(lAdd) Next l lTime = getTimerVal - lTime MsgBox (" done reading " & Format(lMAX) & " times took " & Format(lTime) & " milliseconds ")End Sub

Sub AbenchRead2() Dim l As Long Dim lTime As Long Dim lPlace As Long Dim ival As Integer Const lMAX As Long = 1000 Const lAdd As Long = 33536 '"&H8300" -> a neg. num! 'MsgBox (" get ready to set your stop watch") l = 1 lTime = getTimerVal Call Sleep(l) lTime = getTimerVal - lTime MsgBox (" done reading " & Format(lMAX) & " times took " & Format(lTime) & " milliseconds ")End Sub

Function getTimerVal() As Long ' adapted from pg 214 of "VBA developer's handbook" by Ken Getz and M. Gilbert (sybex,97) getTimerVal = timeGetTimeEnd Function“

Page 22: vba11

Registering Bit3 DLL Functions! (some) registered functions“'have managed to unmangle the names for the 617 driver!'Declare Function initio Lib "bntdv617.dll" (i As Integer, l As Long) As Integer' Besides initing the bit3, setting the address modifier, it initializes all' secondary error handling bite.dll flags to the correct state including' the internal static variable "abortflg"=0 =false (no failures) or 1= a failureDeclare Function islatcherri Lib "bntdv617.dll" () As Integer'returns true=1=a failure if a bit3 error detected since the last time'initio() or clrlatcherri() was called. call getlasterrps() to find last error!Declare Function clrlatcherri Lib "bntdv617.dll" () As Integer' set static variable "abortflg" to 0= false = no failures and should always return 0.' the "c" code is:'{ abortflg=FALSE;' return abortflg; };Declare Function getlasterrps Lib "bntdv617.dll" (ByVal s As String) As Integer' tells what the last bit3 error was in english (more or less).' this is a noteworthy example of how to return call a "c" function that' returns a pointer to a null terminated string which VBasic has troubles with.' the "byval" fixes the null terminated part but it still has trouble with the pointer so' also basic will not allow the byval keyword after the function name so' fake it out by telling basic it's an integer...' from page 779 of Using Visual Basic for Applications (Excel Edition)by Jeff Webb.Declare Function setb32flgi Lib "bntdv617.dll" (iflg As Integer) As Integer' set iflg=true=1 then bit3.dll will send out the high byte = full 32 bit(3) model 406 addressing' else iflg=false=0 then bit3.dll will not send out the high byte = 24 bit(3) model 403 addressingDeclare Function readvmew Lib "bntdv617.dll" (l As Long) As IntegerDeclare Function readvmeb Lib "bntdv617.dll" (l As Long) As IntegerDeclare Function writevmeb Lib "bntdv617.dll" (l As Long, i As Integer) As IntegerDeclare Function writevmei Lib "bntdv617.dll" (l As Long, i As Integer) As IntegerDeclare Function writevmeli Lib "bntdv617.dll" (lAdd As Long, lVal As Long) As IntegerDeclare Function setaddmoda Lib "bntdv617.dll" (i As Integer) As IntegerDeclare Function bootvme Lib "bntdv617.dll" () As Integer….“

Page 23: vba11

Bit3: Some Registered Functions WrappedFunction VB_InitIOi(addmod As Integer, Address As Long, model406flg As Boolean) As Integer Dim i As Integer Dim j As Integer Dim i406adapter As Integer If model406flg Then i406adapter = 1 Else i406adapter = 0 End If i = initio(addmod, Address) j = setb32flgi(i406adapter) ' constant set =1 or 0 after bit3.dll declarations above VB_InitIOi = iEnd Function

Function VB_getlasterrs() As String Dim s As String * 255 Dim iworked As Integer ' this is more of J.W's trick s = String(255, 0) iworked = getlasterrps(ByVal s) ' this is the final part of J.W.'s trick VB_getlasterrs = sEnd Function

Function VB_InitIOs(addmod As Integer, Address As Long, model406flg As Boolean) As String Dim i As Integer Dim s As String * 255 Dim j As Integer Dim i406adapter As Integer If model406flg Then i406adapter = 1 Else i406adapter = 0 End If i = initio(addmod, Address) s = String(255, 0) ' necessary now? s = VB_getlasterrs() ' do the trick only once in a vb routine j = setb32flgi(i406adapter) ' constant set =1 or 0 after bit3.dll declarations above VB_InitIOs = sEnd Function

Function VB_readi(Address As Long) As Integer Dim i As Integer i = readvmew(Address) VB_readi = iEnd Function

Page 24: vba11

Why Wrap Functions?• Initially it made things more robust. (Fewer blue screens of death when a c routine was passed the

wrong type in an argument.) Allows VB(A) to better trap common programmer type (checking) errors b4 making the c call.

• Can hide any tricks being used to call into the c code. (Strings are a bit tricky/weird/magic.) Keeps all the tricks in one place to prevent argument calling errors and thus aide robustness as above.

• However the best reason here is that it allows one to change Libraries (DLL’s) easily. Due to differences in price and capability and historical reasons, various test stand use different Bit3 models and even busses. By wrapping whatever DLL is called in it’s own module one may isolate and minimize any differences to the rest of the Worksheet. Thus a whole worksheet developed on one test stand may be run on a completely different test stand by changing out the Bit3 module. A worksheet that is moved this way can be changed to run on a different operating system (different ways of getting to the hardware: drivers) and/or use a different Bit3 model (different busses!) in ~ 5 minutes by cutting and pasting in different VBA “modules”. (Note the similarity to a language called “modula” which was a successor to “Pascal”.) This module feature has been extremely useful for us. Specific Bit3 DLL and modules info available at: http://d0server1.fnal.gov/users/angstadt/www/d0notes/2589/convertb3.htm. In general modules are a good thing (precursor of objects…) and should be used if and where ever possible. In most cases the cost is at most a microsecond or 2 at run time depending on the hardware one is running on.

• [Modules are also a way of dividing and conquering. If two people are working on the same sheet one could make their changes in one module and the 2nd in another. Periodically the new modules could be replace or updated and then “checked” to make sure they worked as intended together. If so then it could be “released”. Then the next round of changes would be made and the process repeated until the Workbook was “done”. This is good practice. If you take an existing Worksheet over and start to modify it then if possible please add new code modifications (your) own new module(s). That way if fixes/improvements/patches are made to the old ones in your sheet are developed by someone else the old modules can be easily changed and/or upgraded as necessary. This is easy to do as long as your changes are not mixed in with theirs. Then it’s all yours and you have to fix everything yourself! Modules can be a very good thing!]

Page 25: vba11

Typical Bit3 Calling Sequence • It is assumed InitVME() is called outside of and sometime before a VME access (reading or

writing) to the VME bus. Once it is called it should not have to be called again unless any of the following occurs:

1. Power is removed from the crate and turned back on.2. VBA execution is halted via the “Esc” key or “Ctrl-Brk”. (Best to do this but may not be

required…)3. If for some reason a piece of code is run where a lot of invalid VME addresses occur. After

a lot of “no DTACK” ing the Bit3 may be need to be reinitialized before valid addresses start working again. (After a big bunch something latches in the Bit3 that the quicker error clearing mechanisms don’t clear it. A total global restart is the only thing that brings it back but this nominally takes a second so it is not done automatically all the time in the driver. It usually takes seconds of invalid addresses before the Bit3 really latches like this. (On a fast machine in tight code this could theoretically be >=~300 to 400K times.) See next major section for more.

4. Any time a valid VME address is put on the bus and a VME module’s “DTACK” LED does not light. (Occurs rarely but it can happen. Reseating cards in the busses and/or cables may be the fix. Check VME crate power. Pull all VME modules but the arbitrator, Bit3 and target to check if of them has latched bus grant or some other VME bus signal. Check that all jumpers are correct on all boards. If not a VIPA crate and if the VME Bit3 card is not jumpered for Bus Arbitrator and not in slot 1 then Bus Grant 3 may not be making it to the VME Bit3 card. (The 1553 card does not jumper bus grant 3 across for example. (Crate configuration and jumpers may need to be changed.)

• After calling InitVME() one should wait at least a second (and perhaps 2) to be safe. (The latest “2nd” version for NT has a half second delay in it at the end of it (already) for the precision timing calibration it uses.) All of the Bit3 driver DLL’s keep a (latch on a Bit3 detected error) global flag in them, iabortflg. It is reset (false) only at power up and only whenever VB_clrlatcherri() is called. VB_islatcherri() always returns 0. VB_islatcherri() value is in re-setting the flag without going through the whole init Bit3 process and not in what it returns. This flag, iabortflg gets set True ( <> 0) for any detected Bit3 VME failures. It “latches” in this state until reset via a call to InitVME() or VB_clrlatcherri() is called. (Thus any granularity of error checking is achieved.)

Page 26: vba11

Typical Bit3 Calling Sequence 2• VB_getlasterrs() returns a string that indicate what the Bit3 and driver thought the last VME error

detected was. Possible values are " *VME TIMEOUT*“, " *VME BUS ERROR*“, " *VME PARITY ERR*“, " *DMA ERROR*“, "*connect giveio.sys failed* “, “ *connect mapmem.sys failed* “, "*no vme access: no buff from mapmem.sys* “. Any and all combinations of the first 3 may be seen together on a VME access and mean that for some reason “NO DTACK” came back for that address. The latter errors may occur only in the NT version of drivers and indicate that something is not working between the driver DLL and one of the drivers (*.sys binaries) it is using. They may not be running for some reason and/or installed. If "*no vme access: no buff from mapmem.sys* “ occurs it means something pretty bad happened in mapmem.sys and/or it’s connection was broken. Saving any unsaved work if desired and leave Excel entirely and come back into it. This seems to mostly happen when stopping VBA code execution via Ctrl-Break (during VBA development.) I apologize for not being able to totally make this go away entirely. Although it is an annoying nuisance it has not been a show stopper. Recently I’ve found that not putting “VB_InitIOs()” in any of the cells seems to delay the onset of this considerably. More on this later in “How Excel Recalculates: determinism (or lack of it.) ..”. Most VME errors are trapped but there are a few conditions that may not be. These rare and usually due to a botched driver install for some reason or other. Best is to call me if you are having troubles so I can come and look.

• Read routines have no error status back and one must use the above mechanisms for error checking. They just return the value they read. (This allows the function to be placed directly in an Excel cell!) Write routines return 0 on detected failure and a 1 on success. There is enough their so that one could re-write the wrappers to give back any kind of calling interface that could be imagined/desired.)

• If using getvmedmal() (only available for the 6xx models) then it returns it’s own error code in addition to the first mechanism described above. On return this must be checked after every DMA for a 1=true=data is valid. The user buffer is not zeroed in the interest of speed. (The caller may 0 some or all of their buffer before doing the DMA if they desire.) Other error codes are from bntdv617.h:

“…after the dma is complete the data will be copied to this buffer and// the routine will return success=1 or failure= false=0 or -99 if // it could not create a 64K special dma buffer. if the return is -99// close the program (unload the dll) and then reload it. “

Page 27: vba11

Example of a Typical Bit3 Calling Sequence after a call to VB_InitIOs() or VB_InitIOi() (and crate not power cycled.)

“Sub go53mhzOsc() Dim lAdd As Long Dim iIs1EqualTrueIfSuccessElse0EqualFalse As Integer Dim iPlace As Integer iPlace = VB_clrlatcherri ' clear the latching bit3 status error flg (iplace should always =0 !) lAdd = lVRBCBASEADD + 32782 ‘&H800E 'vba sign extension bug bites... iIs1EqualTrueIfSuccessElse0EqualFalse = VB_Writew(lAdd, 0) ' select normal operation

for autotest fpga If VB_islatcherri = 0 Then 'MsgBox "no errors detected processing list in sub ListProc“ ‘ may not honor empty block if! Else MsgBox "one or more NODTACK during go53mhzosc" End If ' Go to IDLE NRZ in order for the Sequencer to establish the framing bit ' Call goIDLEnrz End Sub 'go53mhzosc“

Page 28: vba11

A Word on Notation • In the code fragments presented so far you may have noticed odd things like an “ l” (lower case L) in

front of “lAdd” (Add is short for address: in this case a VME physical address.) “l” is short for long. In iPlace the “i” is short for integer. “sz” indicates a string with a zero terminating it (basically a C type of string as opposed to a VB string which probably will not have a 0 terminating it.) “b” is for Boolean. “mod” is short for module. “txt” is short for a text box. It must be stressed that this is a convention only. What really determines what type of the variable is the “Dim” statement wherever it is declared. Thus: “Dim iVal as Long” fails to follow the convention and is misleading: “i” implies a 16 bit integer but it is in reality a 32 bit “Long”.

• This is a form of “Hungarian Notation” (plenty under Google for this string!) originally described by Charles Simonyi, a Hungarian with an unpronounceable last name, hence it became known as Hungarian notation. Originally this was for c code. G Reddick and Lesznynski expanded it to be the Reddick VBA or RVBA naming convention. One of the things Simonyi argued pretty strongly (about if I remember correctly) was to put the type up front followed by a capital letter signifiying when the type ends and when the variable name starts as opposed to having it at the end which was previously more common before Simonyi’s writings.

• Naturally a lot of my C DLL code base is from code written before I knew of “Hungarian” or RVBA notations. It was based on suffix indicators such as “flg” for a flag, w, for (unsigned) word, “i” for integer, ect. After RVBA I begin using a mixture! Thus in the c variable iAbortflg the “i” is for (short 16 bit) integer (used as) a “flg”. I avoided a Boolean type because I wasn’t sure if VB and C would use the same number of bits.

• For VB I think Hungarian notation is useful because an integer in VB has always been 16 bits and a “Long” has always been 32 bits from the 16 bit version 4 (Office XP is ~10 now.) ~ 8 years ago. For C where the code “iVal int;” can change from 16 bits to 32 bits or vice-versa with a different platform and/or compiler! (or perhaps even 64 with a third platform!) So old C code on ports like this could require tons of editing to fix completely misleading notations. If it’s a large quantity of lines then probably no one is going to go and change all the names because of the high probability of introducing too many mistakes. Any notation is just meant to be an aide to quick understanding of code. If it does not succeed at that then it might not be worth the extra work.

Page 29: vba11

A Word on Notation (2)• Why so important? We’re doing mixed language programming so the arguments and return

values must match or a possible blue screen of death could result. We have to pay attention to our calling arguments and make them match the declarations in the Bit3 module. A word or two about the code may help you decipher it if you feel the need to have to look at it!

• Back to trying to clarify what you’re looking at. Here’s the C code for the islatcherri() function:DLLEXPORT __int16 WINAPI islatcherri(void){ return (short) iabortflg; }

Another example is “getlasterrps” the “p” is short for pointer and “s” is short for string. So the whole thing spelled out is “Get the last error returning a pointer to a string.”

• So for a lot of the Bit3 functions I used use my old suffix notation on the type of VME operation they will put on the bus and/or return: 8, 16 or 32 bits. It sort of works: “i” for signed 16 bits, “b” for byte=8. Very nice!

• For a model 6xx that support 32 bit operations then the wrapped VB_readl(), & VB_writel() and VB_readul() are provided that wrap the DLL readvmeul() and putvmeli() actual DLL entry point.

• Note that in all Bit3 functions is a misleading/confusing name ending “writevmeli()” [Another reason to just use the wrapped functions.] Here the conventions break down. I apologize for this. WriteVMEli() exported function available which the header file tries to clarify:

“DLLEXPORT __int16 WINAPI writevmeli(long *pdaddress,long *lval);/* this name and the second argument of writevmeli are misleading.** writevmeli does NOT do a 32 bit word vme bus cycle.** it does only a 16 bit vme bus cycle. the argument is 32 bits to** facilitate getting back a 16 bit unsigned quantity short to Visual Basic** as VB has no short intrinsic unsigned (__int16) __int16. in no way shape or form** does this do a 32 bits to vme. …“

A long is necessary to hold an unsigned 16 bit quantity. (More on next section.) • (The models 6xx can do 32 bit VME operations and there are a few additional calls for those

libraries. It is easy to move up to a 32 bit model by cutting and pasting in new VBA code but going down is only easy if 32 bit VME operations are not needed/used in the rest of the sheet.)

Page 30: vba11

A Word on Notation (3)• In one early sheet I found 2 possible wrappings of WriteVMEli which I thought might be

interesting because the wrapper adds functionality to return the last error string instead of a 0 or 1 (failure, success respectively). If you need a slightly different style then there is enough here I think to morph the arguments and return values to your needs! This is from a module called io_32 in hv5_32.xls. It’s for a 40x under Win 9x, “Bit3_32.Dll” but it would work the same under the equivalent NT DLL, “BNTDV40X.dll”.

Function VB_Writew(Address As Long, lvalue As Long) As Integer Dim i As Integer i = WriteVMEli(Address, lvalue) VB_Writew = iEnd Function

Function VB_Writews(Address As Long, lvalue As Long) As String Dim i As Integer Dim s As String * 255 ‘ s is declared here with space (a buffer) for 255 characters i = WriteVMEli(Address, lvalue) s = String(255, 0) ' every character of the string is now set to 0! s = VB_getlasterrs() ' this returns the last error string from the bit3 C driver VB_Writews = sEnd Function

• So this notation is to try to indicate that VB_writew() writes an unsigned word (returning the usual and/or default integer= 0,1 as a 16 bit integer) while VB_Writews() writes an unsigned word returning a string indicating an error or “ok!” if no error was detected. Both do this by wrapping the unsigned 16 bit word into a larger signed 32 bit word or long. Why do we have to package up an unsigned 16 bit quantity in a (signed) long of 32 bits?

Page 31: vba11

Some Problems with VBA and Some Work Arounds

• Unfortunately VB(A) has no intrinsic unsigned types. Everything is signed. An integer is always 16 bits = (0xffff) -32768 to +32767 (0x7fff) in VB(A) though other languages (usually) provide a 16 bit unsigned integer 0 to 65535 (0xffff)

• Long is also signed (0xffffffff)-2,147,483,648 to +2,147,483,647 (0x7fffffff) Unsigned Long would be 0 to 4,294,967,295 = 0xffffffff)• Note: something helpful to remember is that for any signed quantity the largest positive value it

will hold is 0x7f in the high byte (on a 2’s compliment machine, which most computers are including Wintel).

• Assigning &h8000 to a long doesn’t work! One gets 0xffff8000 and not 0x8000. (sign extension problem on any hex value > 0x7fff. This has been a real pain!

• Workarounds: 1. Promote everything to more Bits. Use a long to hold 16 bits being careful to use decimal

instead of hex for values > 0x7fff. 2. Pass it onto the C stack where the C argument is unsigned and/or larger (more bits) as

above. E.g., if you need 0 to 65535 then use a long. If > 2 billion then use a double. Etc. (can use arbitrary long integers with C and inline assembly. I have an example of using > 64 bits using the ADDC instruction using double and 2 longs so that modulo operations still work on the lower 32 bits.

3. Use strings! Can also pass it as a hex string and convert it to binary where necessary. • VB(A) doesn’t do true bit shifting… some fixes available in xutil.c and more elsewhere. • Conclusion/Summary: Due to it’s extensibility many/most limitations can be worked around.

(One that can’t be is the programmer’s lack of imagination and/or skill!)

Page 32: vba11

Concept of Lists• B4 objects there was “structured programming” with a fundamental tenet

being that one function (and/or subroutine) did one thing (in as generalized a way as possible) (Just as there are objects now…) One can expand this to the concept of a list.

• Make One list do one thing. Maybe you have a list for the first grocery store and a 2nd list for another due to a sale at the second but the first has better produce. Maybe another for the Hardware store, etc. Maybe after all your lists are made and organized you expedite them by “shopping”. The idea is the same, make a list of like VME read/writes, their addresses and values, and then expedite or execute that list! (Make the VME “DTACK” LED’s light!)

• (FYI: There are languages built around this list concept including a now somewhat obsolete language called “Lisp” which I think now is quite dead due to it’s difficult to use of parenthesis on top of parenthesis. It wasn’t pretty but it had power for those that could keep track of and parse the parenthesis.)

• Remember one of our first goals was to reduce the number of lines of code? One way is the concept of a list processor. Not new, many programs (had) have file based list processors of various sorts. The next slide is a simple 48 line VBA subroutine based on the Bit3 module that will process a VME list.

Page 33: vba11

A (simple) VBA VME List Processor (48 lines) :Sub ListProc() Dim iRow As Integer Dim iMax As Integer Dim iplace As Integer Dim lAdd As Long Dim lVal As Long Dim iVal As Integer Dim iAddMod As Integer Const iROWSTART As Integer = 16 Call allAutoOff iplace = VB_clrlatcherri ' clear the latching bit3 status error flg iRow = iROWSTART - 3 iMax = Cells(iRow, 4).Value iMax = iMax + iROWSTART - 1 For iRow = iROWSTART To iMax lAdd = Cells(iRow, 4).Value iAddMod = Cells(iRow, 11).Value If iAddMod <> 0 Then ' if not 0 then send a new add mod iplace = VB_setaddmoda(iAddMod) End If If Cells(iRow, 1).Value = 1 Then ' do byte If Cells(iRow, 2).Value = 1 Then ' write iVal = Cells(iRow, 9).Value iplace = VB_writeb(lAdd, iVal) Else ' read iVal = VB_readb(lAdd) Cells(iRow, 5).Value = iVal End If Else 'do word If Cells(iRow, 2).Value = 1 Then ' write lVal = Cells(iRow, 9).Value iplace = VB_Writew(lAdd, lVal) Else ' read lVal = VB_readw(lAdd) Cells(iRow, 5).Value = lVal End If End If 'do error handling now Cells(iRow, 12).Value = VB_getlasterrs Next iRow Call allAutoOn If VB_islatcherri = 0 Then 'MsgBox "Should be in IDLE mode = 00" Else MsgBox "one or more NODTACK during ListProc" End IfEnd Sub 'ListProc

Page 34: vba11

List of Lists (of Lists!)• If a Workbook had several Worksheets in it with each worksheet having one list then they could be chained

together for the cost of ~ 2 lines of VBA code per link. The following is a somewhat artificial example to try to make things clearer:

“ sub doAList Sheets(“InitBoard1").Select call ListProc Sheets(“InitBoard2”).select call ListProc

Sheets(“DoSomething1”).select call ListProc ….. ‘until all of the sheets are exhausted ‘could make branching statements using if and case based on values coming back in certain ‘cells. ‘ could also do one list multiple times ect.

‘ Can call other sheets and load them: Workbooks.Open FileName:= "C:\xls\xls_CTS\\List617_2.xls" Sheets(“Sheet VME1”).select call ListProc

‘mix it up and call another kind of list proc: 1553 Sheets(“Sheet 1553_1”).select call ListProc1553

• So with this scheme and/or others like it one can do (almost) programmingless programming.• Has been done with Fermi VME based 1553 module, Ballard ISA 1553 controller, and Ballard 1553 PCMCIA spy

card. Could be done with parallel port. I believe Neil Wilcer has done it with the serial port.• Has been done with CAMAC: there is a DSP/ISA CAMAC list proc in Excel!• (Could probably be done with CAMBUS if inclined/desired.)

Page 35: vba11

List of Lists (of Lists!) 2• Any list processor can be made “Quiet”, Silent, or Invisible with

“Application.Screenupdating=False” (It will also run faster!)• Just Remember to turn it on with “Application.Screenupdating=True”

when you are done! Else the worksheet will no longer paint the screen again until it is reopened! (Not widely used but it is available.)

• A few sheets have a quiet or deferred error handler. So they are “quiet” and don’t check for errors until all the lists have been completed. Not widely used but is available and/or could be done if desired.

• List Processing Concept is very powerful. Saves lots of programming time at some expense to run time (only on slow machines!) Most widely used with 1553 and Sequencer test where even a pass/fail test was developed with it using a procedural write- up. This technique has the advantage of the same code and Worksheets being used for debugging by the engineer and/or skilled technician. Engineer made up most of the lists. Took very little additional programmer time. No time wasted on “dead code” because all code (and Worksheets) is still in use (whenever a sequencer does need repairing)!

Page 36: vba11

Types or Purpose of WorkSheets in Use• What sheets do we have done that can be looked at? (for examples, lifting/reusing code ect.)

Tons! (Megabytes! > 5 Person years!) So many I am out of quota on the servers so if you don’t see it there ask me! All kinds including but not limited to (and in no particular order):

• Small 1 Crate DAQ’s: (both SASEQ and VRB,VRBC based) for both Silicon chips and AFE boards. (Way different sheets.)

• FPGA reprograming: A sheet for reprogramming VRB’s (downloads new firmware to battery backed RAM that it will boot from.) Also a VRBC download worksheet. These last two use the Bit3 to load new Altera programs to the FPGA’s on the board via VME & Bit3. (Jamieson uses the parallel port as an interface to his boards as they are not VME based. He as also done some JTAG testing using the parallel port as his hardware is not VME based.)

• Front Panel control and display substitue: A High Voltage Front Panel Display and Control Sheet(s).

• Front Panel control and display of the DZero clock and statistical testing. (run for months/years? In Feynman on a Win 9x box!.

• General Engineering Control and Exercising VME, CAMAC, & 1553 List Processors for all of the above and more including the DZero Clock (in Feynman), and Mike Utes Sequencer(s).

• Confirming VRB & VRBC operation and troubleshooting including engineering, Statistical (and/or FPGA emulation!) functions. Most recent sheet with many modules in it. Still under development for CTS. (A work in progress.) But a version allowed Ted Zmuda to come up with VLPC firmware that ran the VRB & 1 VRBC > 9.1 billion times without failure. (It used VME (Block Transfer= ~ DMA! & my Bit3 driver!) at L1 rate > 20Khz and L3 rate of ~ 6 Kilohertz on Lyn’s 2Gig machine (thanks Lyn!). Used Bit3 and Parallel port for trigger and handshaking. Kept track of everything and provided support for triggering Ted’s Logic Analizer via the parallel port. (A 24 bit (16Mega) wrapping problem could be exercised and looked at quicker on the logic analyzer then it could be simulated by the Altera software.) So it could trap errors and trigger things as well as do statistics. (9.1 billion events takes ~ 18 days. 0 errors. Way to go T. Zmuda!)

• 1553 Spy (Logic analyzer) Worksheet on a Laptop (Shoua’s.) Uses the Ballard driver for their 1553 PCMCIA card. Some versions have the ability to trigger a scope via the Parallel Port!

• AFE characterization: M. Matulik, P. Rubinov.• Pass/Fail Production Testing: (M. Matulik. and J. Anderson)

Page 37: vba11

Random Hints, (words to the wise).

• Don’t use spaces in object and/or sheet names. (If nothing else it may be hard to distinguish between a space and a “_” due to some line on the lower margin.) It almost works but sometimes….. Just don’t!

• VB is not case sensitive (it echoes case for clarity after you declare it.) except in side of quotes: “Sheet 1” is not the same as “sheet 1”

• When is VB not block structured? When the first block of the if is commented out! May have to change the sense of the if. (This recently burned me!)

• Best to be explicit in the if statement: avoid C usage of If. E.g. if (myVar) then … may not work. Best is to use if (myVar>0) or if (myVar=True) or if myVar=False) which leads us to the next thing.

• Spell things out sequentially. In general don’t stack things up in (). Make a variable and feed that to the next routine and/or set of (). Allows type checking and since it gives the parser less grief it gives you fewer gray hairs as well.

• For speed use “With”, “End with” blocks when dealing with many instances of the same daughter object. Supposedly it saves re-verification that the parent object exists. (Unfortunately “with”s do not nest.)

Page 38: vba11

A Word About Arguments (and Pointers)• Although VB & VBA does not EXPLICITLY support “pointers” the default argument

passing is “by Reference” (or basically a pointer!) This is as opposed to “by copy”. FORTRAN’s default is also “by reference” as opposed to C’s default of “by copy”. (C also support passing things “by reference” as well when a functions argument declaration(s) include the use of a “pointer” ((address) dereferencing operator) = “*”.

• When passing arrays back and forth between C and VB(A) one should be aware that C starts all arrays at 0. The default for VB(A) is also 0. However in VB(A) it can be changed via either “Option base 0” or “Option base 1” at the top of a module. When passing arrays back and forth between C and VB(A) I think it very wise to explicitly put “Option Base 0” at the top of the module. One less point of confusion for the interpreter and for the author/reader/programmer. It just keeps everyone on the same page.

• Generally, when matching arguments to C code in a DLL if it is not declared as a pointer (no “*”) then it typically needs the VB “byval” qualifier which tells VB it is passed as a copy. (Usually) Look in the *.h file to see how to call it or other documentation… (book or whatever.). (An exception is passing strings. For now just pretend it is in the realm of “magic”. But if you insist there is an example in the Bit3 library with VB_getlasterrs() with comments and a reference.

• [There is a newer dialect of VB out called “NET” VB which I’m not talking about here. Of course you know it’s default is the opposite of VB(A) and we are in no way referring to that! This was for a Web scripting environment as a Java competitor. At the moment we are just working locally… so we are less concerned about security and/or “protection” which is why they bit the bullet and changed it all around! (VB and VBA were built to be extended!)]

• Probably the most important thing to realize is that passing a variable “by reference” into say a subroutine is that if that sub you passed it into changes it, then its changed in the callers variable as well. This is good for passing arrays as in FORTRAN and C. However when overlooked it is the fast track to trouble.

Page 39: vba11

A Word About Arguments, Example 1• Though the following is a contrived example of above it illustrates the

point. Say you wanted 5 columns of numbers on the sheet counting from 1 to 5. These two rountines just do the indexes. (Some other routines would fill in something else later.) For indexing one might write a sub Count2MaxTest() which calls Count2Max():

“Sub Count2MaxTest() Dim lRow As Long Dim lCol As Long Dim lStartRow As Long Dim lMaxRow As Long

lStartRow = 1 lMaxRow = 5 For lCol = 2 To 10 Step 2 ‘loop over each column Call count2Max(lCol, lStartRow, lMaxRow) Next lColEnd Sub

Sub count2Max(lCol As Long, lStartRow As Long, lMaxRow As Long) While lStartRow <= lMaxRow ‘loop by rows Cells(lStartRow, lCol).Value = lStartRow lStartRow = lStartRow + 1 Wend End Sub“

• Only 1 column of numbers comes out when 5 are expected. What’s Wrong?

Page 40: vba11

Arguments Continued: Example 1• For our second example we try

“Sub Count2MaxTest2() Dim lRow As Long Dim lCol As Long Dim lStartRow As Long Dim lMaxRow As Long

‘ we decide we want to start 5 down from the top anyway. lStartRow = 5 lMaxRow = 5 ‘and we’ll do 5 from the start For lCol = 2 To 10 Step 2 ‘loop over each column Call count2Max(lCol, lStartRow, (lStartRow + lMaxRow)) Next lCol End Sub

“• Calling the same Sub count2Max for rows as before then we

get this->

Page 41: vba11

Arguments Fixed: Example 1• Finally we change Sub Count2Max()-> Count2MaxFix() & This Works:“

Option ExplicitSub Count2MaxTestFix() Dim lRow As Long Dim lCol As Long Dim lStartRow As Long Dim lMaxRow As Long

lStartRow = 5 lMaxRow = 5 For lCol = 2 To 10 Step 2 Call count2MaxFix(lCol, lStartRow, (lStartRow + lMaxRow)) Next lCol End SubSub count2MaxFix(lCol As Long, lStartRow As Long, lMaxRow As Long) Dim lRow As Long ‘local lRow = lStartRow ‘make a local copy While lRow <= lMaxRow Cells(lRow, lCol).Value = lRow lRow = lRow + 1 ‘only increment the local copy Not lStartRow Wend End Sub“

Page 42: vba11

A Word About Arguments, Example 2• This is a little more subtle and slightly different but can be a real world problem when using the

xutil.dll (library). Assuming the modXutil VBA wrapper code is in your sheet in modxutil:“…Declare Function xushl Lib "XUTIL.DLL" (lVal As Long, nBits As Long) As Long…Function VB_shl(lVal As Long, nBits As Long) As Long Dim lRet As Long lRet = xushl(lVal, nBits) VB_shl = lRetEnd Function “

• FYI: this is the C code the above calls:“DLLEXPORT unsigned long WINAPI xushl(unsigned long *ulval, unsigned long *nbits)//return lval shifted left nbits{ unsigned long ulret;

ulret= *ulval << *nbits;return ulret;

}”• Then if one wrote (I did write) the Very Legal and standard looking code which did not work when

I first tried it! Though it works on my current VBA & Excel Version and machine it is not to be trusted!

“Sub aMaybeShl() ' this can and has failed! Dim lVal As Long Dim lnBits As Long lVal = 1 lnBits = 4 '2^4=8 lVal = VB_shl(lVal, lnBits) Cells(1, 1).Value = lValEnd Sub “

Page 43: vba11

A Word About Arguments, Fix, Example 2 cont.• By adding another Variable (that is at a different location/address/pointer than the one passed in

guarantees that it will work:“Sub aShl()' never caught failing Dim lVal As Long Dim lnBits As Long Dim lValLshifted As Long ' make a separate variable at a different address to hold the result lVal = 1 lnBits = 3 ‘ 0001 -> moved 3 over = 1000 = 8 lValLshifted = VB_shl(lVal, lnBits) ‘Function input and ouput, lValLshifted and lVal, no longer share the same address and are totally

isolated. ‘the result is safe even if lVal is late popping off the stack. The intent is preserved regardless of

any CPU out of order problems and/or stack problem(s)! Cells(2, 1).Value = lValLshiftedEnd Sub“

• Having separate variables at 2 different addresses provides extra insurance of some unintended interpreter error or glitch or whatever across languages and versions of) VB(A) providing much greater robustness.

• Something to look out for and won’t hurt (very much) to make an extra variable when calling any similar routine(s) including all VB(A) ones!

• Why use pointers and/or pass by reference? Not using them would make problems like this go away, right? Yes, but then in every call one would have to use the visual basic “byval” qualifier which I was trying to avoid! Was trying to make the C routines as native as possible to VB(A). Also many routines intend to change the argument values on return. Also see “arrays” later.

Page 44: vba11

A Word About Arguments Example 3• What happens when a constant is passed to a pointer where the C routine changes the

argument? (On my XP (version 10.2) VB does the right thing and does not change the constant.) But when it’s passed into the C code what is going to happen????

• Technically it is not “best practice” to pass a constant to a routine that could change it! The literature is full of discussions of where the constant did get changed. (It might happen on an earlier version even though my machine did the right thing?)

• An example is the Bit3 VB_initios(iAddMod,lBit3PhysicalBaseAdd,is406flg) call. All of these arguments are pointers. So the C code could potentially change say the lBit3PhysicalBaseAdd variable. So if the following code was being used for say an ISA Bit3 model 406 then the following would work be acceptable as that Bit3 must be base address at 0xd0000 to work (and the driver doesn’t change it.)

“Sub initVME1() Dim iADDMod As Integer Dim dtime2waitperlongword As Double Dim dRet As Double Const lADDCONST As Long = 851968 '0xd0000 iADDMod = Cells(2, 4).Value 'For the 6xx series the base address on the PCI bus is returned. 'for the 40x series (ISA bus) cells(3,4).value must be 0xd0000 or it will never init! Cells(5, 3).Value = VB_InitIOs(iADDMod, lADDCONST, True) Cells(3, 3).Value = "'" & Hex(lADDCONST) ‘(if it’s a 6xx) show the user it’s base address on

the pci bus ‘ can be useful for diagnostics… End Sub

Page 45: vba11

A Word About Arguments, Better Example 3 cont.• But say the sheet gets moved to a machine using say a 617 on the PCI bus?? Although I

managed to keep the arguments the same across different models and busses the drivers function slightly differently. Apologies for this but I thought it o.k. to do this. (Worse was to change the number of arguments and then the blue screen of death might occur…). Anyway this is the way it is!

• The 617 (also 618, 620 or just 6xx as opposed to the 403 or 406 =40x) drivers all use the 2nd argument to return the physical address of where the 6xx is on the bus! (Physical addresses of boards on the PCI bus are dynamic and can be moved around…. I returned the address on those as a debugging diagnostic for anyone interested. That’s another story we won’t go into today.)

• So this is a fix:“Sub initVME() Dim iADDMod As Integer Dim lAdd As Long lAdd = Cells(3,4).value ‘ originally most sheets picked up 851968 0xd0000 from here! iADDMod = Cells(2, 4).Value 'For the 6xx series the base address on the PCI bus is returned. 'for the 40x series (ISA bus) cells(3,4).value must be 0xd0000 or it will never init! Cells(5, 3).Value = VB_InitIOs(iADDMod, lAdd, True) Cells(3, 3).Value = "'" & Hex(lAdd) ‘‘(if it’s a 6xx) show the user it’s base address on the pci

bus ‘ can be useful for diagnostics… End Sub

Page 46: vba11

A Word About Arguments, Best Example 3 cont.• What could/should be improved on in the above? (for really armored argument protection and

consistency? “True” is also an (intrinsic) VBA constant…..• Although none of my drivers ever assign that flag to anything it is NOT BEST PRACTICE! So

this is the last best and final answer:“Sub initVME3() Dim iADDMod As Integer Dim lAdd As Long Dim isModel406flg As Boolean

isModel406flg = True lAdd = 851968 '0xd0000 or cells(3,4).value

iADDMod = Cells(2, 4).Value 'For the 6xx series the base address on the PCI bus is returned. 'for the 40x series (ISA bus) cells(3,4).value must be 0xd0000 or it will never init! Cells(5, 3).Value = VB_InitIOs(iADDMod, lAdd, isModel406flg) Cells(3, 3).Value = "'" & Hex(lAdd) ‘(if it’s a 6xx) show the user it’s base address on the pci

bus ‘ can be useful for diagnostics… End Sub“

• (Note that the isModel406flg argument is not used for anything on the model 6xx drivers so its state for these boards is ignored!) For the 40x drivers it tells the driver whether to send out the highest byte of the address on the bus or not. (A model 406 has 3 LED’s on the front while a 403 has none. (BTW: Models 617, 618, & 620 have the same 3 LED’s as well. An easy distinguishing feature is that a 617 cable has thumb screw tabs instead of plain slotted screws. Models 618 and/or 620 have orange light pipes instead of copper cable.)

Page 47: vba11

About Arrays as Arguments• This needs to be mentioned because the VB help notes are misleading and/or not very helpful

and limiting and a lot of the C DLL’s do much more than the Official Excel/VBA Help Notes:“Understanding Parameter Arrays    A parameter array can be used to pass an array of arguments to a procedure. You don't have to

know the number of elements in the array when you define the procedure.You use the ParamArray keyword to denote a parameter array. The array must be declared as

an array of type Variant, and it must be the last argument in the procedure definition.The following example shows how you might define a procedure with a parameter array.

Sub AnyNumberArgs(strName As String, ParamArray intScores() As Variant) Dim intI As Integer Debug.Print strName; " Scores" ' Use UBound function to determine upper limit of array. For intI = 0 To UBound(intScores()) Debug.Print " "; intScores(intI) Next intI End Sub

The following examples show how you can call this procedure.AnyNumberArgs "Jamie", 10, 26, 32, 15, 22, 24, 16 AnyNumberArgs "Kelly", "High",

"Low", "Average", "High" “

Page 48: vba11

About Arrays as Arguments Part 2• In earlier help versions doing it their way limited the argument list to one array of type

variant. (More on variants later). It was very limiting. Realizing everything is a pointer one can get around this limitation and just pass what you want! The syntax is a little like some C code and/or FORTRAN so if you are familiar with that then you should be all set!

• Regardless of your previous experience here is a VB call to a C unpacker with many many arrays (which could be of different types… none are of type variant!) though in this one they are all (basically) all (32 bits) Long:

“ lNumbChanErrs = svxunpackVRB012(lrawAry(0), lStartRaw, lNum16BitWords, _ lStartchip, lLastChipAry(lWhichVRB), lChipIDAry(0), lFirstBadIndex, lChipIDerrAry(0), _ lchipLastIDVal(0), lChanErrCountAry(0), lStoreDataAry(0), lnumBadChipIDs, ulAFEBoardID(0), _ pulFirstBadVirtual, pulVirtualBadCount, pulVirtualCounted, ulVirtualData(0)) “Here’s the C *.h header declaration:“DLLEXPORT unsigned long WINAPI svxunpackVRB012( unsigned long lDMAary[],

long *plstartraw , long *plEndRaw , long *plStartChip, long *plLastChip, unsigned long lChipIDAry[],

unsigned long *pulFirstBadIndex, // end of input args, beginning of output argsunsigned long lchipIDerr[],

unsigned long lchipLastIDVal[],long lChanErrCount[],

unsigned long lStoreData[],long *plnumBadChipIDs,unsigned long ulAFEBoardID[],unsigned long *pulFirstBadVirtual,unsigned long *pulVirtualBadCount,unsigned long *pulVirtualCounted,unsigned long ulVirtualData[]);

Page 49: vba11

About Arrays as Arguments Part 3• In the above the “_” signifies to VB that the next line is a continuation of the previous. (Some

limit here of 5 lines? To overcome make longer lines! Have come close to the limit but has been long enough though I’m sure it could be broken…. Another solution is to make some of the arguments global if possible!)

• If svxunpackVRB012() was written in VB then the (in this case function would be declared similarly without the (pointer indicator) “*” and using parenthesis instead of square brackets: “[“ and “]”.

• When calling a subroutine or function as above one does not have to call it with 0 but can pass the array starting from any element (or variable indicating and element) in the array (say one wants to work on some increment of the array instead of the whole thing) by putting it inside the parenthesis instead of 0.

The Caveat (exception) to all this (you knew there would be an exception right?) is that MULTIPLE DIMENSIONED VB arrays and C arrays are not compatible. One is organized by row, column and the other by column, row in memory so things get big time scrambled passing multiple dimensioned arrays. But there is a workaround! (You knew that too!).

The answer is to use VB type declarations to make them multiple dimensioned “Collections” (a user defined type of array.) Then they can be passed to C routines with no problems! But the declarations must match exactly! (For array declarations in VB(A) it may make 1 more element than called for! Check the exact length in the debugger for your version! In C when you say int myAry[5] it will make 0..4. But VB(A) may make 6 to try to protect you from looping 6 times: 0 to 5. This is a very common mistake. The rule to remember is when counting N elements one declares them with N. When counting from 0 one only loops to (N-1)! When counting from 1, one loops to N. Both loops will go N times!) So with collections this must be accounted and corrected for very carefully! (For Collections VB(A) may make 5 elements, 0..4 as does C!) It’s a little goofy but it can be made to work in the end!

(The final Caveat is that for really large “Collections” (>64K bytes), VB will not permit and/or handle them. No workaround at present except to go back to single dimensioned arrays and use modulo operations to go to the next one…. Not pretty but it can be “forced” to work.)

Page 50: vba11

More about User Defined types!• What are they good for? They can help keep track of like things that go together instead of having a bunch of

separate constants, variables and arrays. More object like. • When used with arrays they become a “Collection” which is another name for an array. (Collections have a useful

property in that double dimensioned ones can be passed to C but arrays can not because internally arrays and “Collections” have opposite Row, Column memory order organization. So “collections” are very useful for VBA to C mixed language programming and can also be used with data unpacking routines.) This example is to make a structure holding the addresses of the registers for a DZero High Voltage VME module. The types below were originally translated from (Turbo) Pascal so the Pascal comment delimitters, “{ }” are still there. This example is from d0server4\users\angstadt\xls_teach\hv6_617.xls in the module “modHV”:

“Type Chantype lHVdac As Long 'integer ' { maps to address of hv dac setting } lCurlim As Long 'integer ' { maps to address of cur trip setting dac } lComstat As Long 'word '{ status and control register location } lPod_ID As Long 'word '{ where to read what kind of hv p.s. it is }End Type

Type Modtype Channels(iLASTCHAN) As Chantype ' each board has an array of channels: 1 record type lDigstat As Long 'word; {xxxxxxxx xstatuss register of a/d chip } lDigitise As Long 'integer; { 16 bit val of a/d } lSetadc As Long 'word; {xchnpara mxxxxxxx mux contoller of a/d for card } lMod_SN As Long 'word; { 16 bit card i.d. }End Type'Global HVchanAdd(iLASTSLOT, iLASTCHAN) As Chantype '[0..5, 0..7]of Chantype; original Global HVmodAdds(iLASTSLOT) As Modtype '[0..5] of ^modtype ;” 1 module / sheet here!

• Before using these structures one needs to init them in a routine like below and then be sure to call it before using any of the structures.

Page 51: vba11

Example of User Defined types!“Sub initAddType()' can't have constants in structure definition types and no pointers (no physical addresses either..)' so must init the structures to their addresses 1x before the structures are used and this is it!'less obvious and visible than a worksheet with the addresses all laid out but (surely) faster? Dim iSlot As Integer Dim iChan As Integer 'module stuff Dim lModBase As Long Const lCHANADDINC As Long = 16 'module stuff Const lSLOTINC As Long = 256 Const lDIGITOFFSET As Long = &H80 lSlot0BaseAdd = Worksheets("hvSlot0").Cells(7, 4).Value iBVAddMod = Worksheets("hvSlot0").Cells(2, 4).Value lBaseAdd = lSlot0BaseAdd For iSlot = iFIRSTSLOT To iLASTSLOT 'setup addresses for the slot With HVmodAdds(iSlot) For iChan = iFIRSTCHAN To iLASTCHAN With .Channels(iChan) .lHVdac = getBiasSetADD(iChan, lBaseAdd) .lCurlim = .lHVdac + 2 .lComstat = .lHVdac + 4 .lPod_ID = .lHVdac + 6 End With Next iChan

Page 52: vba11

Example of User Defined types! 2 iChan = 0 .lDigstat = getBiasSetADD(iChan, lBaseAdd) + lDIGITOFFSET .lDigitise = .lDigstat + 2 .lSetadc = .lDigstat + 4 .lMod_SN = .lDigstat + 6 End With lBaseAdd = lBaseAdd + lSLOTINC Next iSlotEnd Sub 'initAddType”

• Before using these structures we must first “call initAddType” at the top of any routine that wants to reference the structure or all the elements will be 0. 0 is the VB(A) default for any variable. So we could write code to call initAddType only if one of the elements were 0. This is a pretty good way to do things. The first time latency would be longer and subsequent references would take very little additional time. Short of making the addresses into full blown objects with object modules (which I have not yet done) is to have the structure filled when the worksheet is loaded into memory with an event by putting the following into the default “ThisWorkbook” module.

“Private Sub workbook_open() Call initAddTypeEnd Sub”

• According to our new text (Thanks John A.) on page 574 events must go in this sheet to work properly! (That’s why I couldn’t make them work reliably before!) But the routine that they call can be in any general module which is where I prefer to keep my code (because I prefer to try to write generlized/global code and not tie it to one sheet….). The only thing I found wrong with this event when working on changing any of the structures (any VBA edits to “initAddTyp”) caused the whole structure to go to 0 (until I saved it, closed it, and then reopened it again! Without some sort of “If (an_element=0) then call initAddType” at the top of a routine before use during development could result in bad addresses during development. Events look promising though I have stayed away from them in the past because they always seem to have some unwanted/goofy side effect like this! In some way or other they always seem slightly less than 100% reliable!

Page 53: vba11

Use of User Defined types more elegant!• So this is an example of using the address structure with the Bit 3 library. Note that

the call to init the structures, “call initAddType” is done by the caller before calling below else lAdd will be = 0 (VB’s default declaration value):

Function setBiasV(iSlot As Integer, iChan As Integer, iVal As Integer) As Integer Dim lAdd As Long Dim iStat As Integer Dim iPlace As Integer ' lAdd = HVmodAdds(iSlot).Channels(iChan).lHVdac setBiasV = VB_Writei(lAdd, iVal)End Function

• Using structures prevents using code as in slide 26 where an offset is added to a base address at run time thus saving some fraction of a microsecond at run time. For hardware that has a repeating pattern of addresses structures are definitely more elegant and easier to manage and maintain. Addresses could also be stored in a worksheet and read from that before running as well and read into an array or structure before running. However you still might want a structure to fill the sheet if there are a lot of addresses. Once the type structure, collection, object, whatever is constructed properly there won’t be any (editing) mistakes. I have not profiled structures verses just lAdd =base + offset to see which is faster. Lots of ways to do the same thing!

Page 54: vba11

Variants: Under the Hood lurks an “xloper”!• What’s a Variant? Same thing as an Excell Cell or “Cells” which are of type Variant.

Microsoft has published various “Excel Developer Kits” in Book and/or electronic form. (This is from “Excel97 Developer’s Kit”) in \include\xlcall.h on a CD included with a book. This is how we can assign a double, a long or a string to a cell(1,1).value! (No quiz on the following code, just glance at it’s magic!) FYI:

/*

** XLOPER structure

**

** Excel's fundamental data type: can hold data

** of any type. Use "R" as the argument type in the

** REGISTER function.

**/

typedef struct xloper

{

union

{

double num; /* xltypeNum */

LPSTR str; /* xltypeStr */

WORD bool; /* xltypeBool */

WORD err; /* xltypeErr */

short int w; /* xltypeInt */

struct

{

WORD count; /* always = 1 */

XLREF ref;

} sref; /* xltypeSRef */

struct

{

XLMREF far *lpmref;

DWORD idSheet;

} mref; /* xltypeRef */

Page 55: vba11

What’s a variant (xloper)?struct { struct xloper far *lparray; WORD rows; WORD columns; } array; /* xltypeMulti */ struct { union { short int level; /* xlflowRestart */ short int tbctrl; /* xlflowPause */ DWORD idSheet; /* xlflowGoto */ } valflow; WORD rw; /* xlflowGoto */ BYTE col; /* xlflowGoto */ BYTE xlflow; } flow; /* xltypeFlow */ struct { union { BYTE far *lpbData; /* data passed to XL */ HANDLE hdata; /* data returned from XL */ } h; long cbData; } bigdata; /* xltypeBigData */ } val; WORD xltype;} XLOPER, FAR *LPXLOPER;

Page 56: vba11

Variants continued.• Above is just something to puzzle over if you are interested. The VB side is

much easier to understand. As shown many times earlier code similar to:“Dim lRow as longDim lL(100) as longFor lRow = 1 to 100 lL(lRow)=Cells(lRow,1).ValueNext lRow”

Usually is sufficient. But say column 1 is filled with “Hex numbers”. “Hex numbers” are really human readable ASCII strings representing a number. Hex “ffff” is really a string in the cell and could be in 1 row while the next row may have “1234” in it as binary! (Hhuman readable ASCII hex could be an xloper type integer, long or a string! So we use a variable of type variant:

Dim lRow as longDim vV(100) as VariantFor lRow = 1 to 100 vV(lRow)=Cells(lRow,1).ValueNext lRow”

• We’ve read them into VB o.k. but now what? How to get them solidly into something of one type, say binary, that we can consistently feed to another routine!

Page 57: vba11

Example Variant Hex to Bin Conversion

• The followning 2 functions were lifted from “modBitErr” in “all_clean_cal_40x_24.xls

Function hexNum2realDec(vS As Variant) As Integer

' vs assumed prev. checked as either double,long, or int but is hex (no A-F in the numb)

' need to convert to an int (computer thinks its a decimal but its really a hex value

' to convert to a decimal....)

Dim szS As String

Dim szS2 As String

Dim iVal As Integer

iVal = CInt(vS)

szS = Format(iVal)

szS2 = "&H" & szS

hexNum2realDec = Val(szS2)

End Function

Page 58: vba11

Example Variant Hex to Bin Conversion 2Function convertV2I(vS As Variant) As Integer Dim iRet As Integer Dim szType As String Dim szS As String iRet = 0 szType = TypeName(vS) ' Returns "String", "Integer", "Long" .. more Select Case szType Case "Double" iRet = hexNum2realDec(vS) Case "Integer" iRet = hexNum2realDec(vS) Case "Long" iRet = hexNum2realDec(vS) Case "String" szS = "&H" & vS iRet = CInt(Val(szS)) Case Else MsgBox ("convertv2i failed") End Select convertV2I = iRetEnd Function“

Page 59: vba11

Example Variant Hex to Bin Conversion 3

• This is the same thing rewritten to return a long. It will return a 16 bit positive number. The integer previous is only good for a 8 bit positive byte! (Remember the signed/unsigned intrinsic types above.) This is “modHexVariant”:

“Option ExplicitFunction hexNum2realDec(vS As Variant) As Long' vs assumed prev. checked as either double,long, or int but is hex (no

A-F in the numb)' need to convert to an int (computer thinks its a decimal but its really

a hex value' to convert to a decimal....) Dim szS As String Dim szS2 As String Dim iVal As Integer iVal = CInt(vS) szS = Format(iVal) szS2 = "&H" & szS hexNum2realDec = VB_uval(szS2)End Function

Page 60: vba11

Example Variant Hex to Bin Conversion 4Function convertV2I(vS As Variant) As Long Dim lRet As Long Dim szType As String Dim szS As String lRet = 0 szType = TypeName(vS) ' Returns "String", "Integer", "Long" .. more Select Case szType Case "Double" lRet = hexNum2realDec(vS) Case "Integer" lRet = hexNum2realDec(vS) Case "Long" lRet = hexNum2realDec(vS) Case "String" szS = "&H" & vS lRet = VB_uval(szS) Case "Empty" lRet = 0 Case Else MsgBox ("convertv2i failed") End Select convertV2I = lRetEnd Function”

Page 61: vba11

Example Variant Hex to Bin Conversion 3

• Using the above 2 functions we can now write:Dim lRow as longDim vV as variantDim iVal(100) as integer ‘Above only good for 16 bits! Must be re-written for

type Long!For lRow = 1 to 100 vV=cells(lRow,1).value” iVal(lRow)= convertV2I(vV)Next lRow

• Variants are a powerful tool but complicate the code: besides using more memory they may cause slight increases in run time performance. They are very good for trying to read input from the sheet if the cells contain hex values or if you have no idea what the (sometimes seemingly malicious) user typed in.

• If someone right clicks on a cell and “Formats” it as a string that some VBA code tries to read as a binary long then VBA will call up the familiar dialogue box with the “Run, “Reset”, “Debug”, “Cancel” buttons on it. (Press “debug” to see which cell got corrupted. Then press “Run”, “Reset” on the VB IDE menus and go back and try to reformat the cell to a number. Also check to make sure there is no “’” (an apostrophe) as the first thing in this cell. An apostrophe tells the xloper variant to be of type string! Hopefully one will not have to resort to reading it in as a Variant and go through similar code to get it to binary. But in case one needs to one can start with the above as an example.

Page 62: vba11

Be careful with Variants!

• Generally I try to avoid Variants because it is also possible to write ambiguous (non-deterministic) code with them too easily! Consider:

“Function add2Vars(vV1 As Variant, vV2 As Variant) As Variant add2Vars = vV1 + vV2End Function

Sub Ambig1() Dim vResult As Variant vResult = add2Vars("1234", "5678") Cells(1, 1).Value = vResult vResult = add2Vars(1234, 5678) Cells(2, 1).Value = vResult vResult = add2Vars("1234", 5678) Cells(3, 1).Value = vResultEnd Sub”

• Knowing that “+” can mean addition for binary numbers and is also the same as “&” = for strings appending and concatenation what result will be in each cell and especially the last? Here it is easy to see the type mis-match but what if variables are declared throughout a 100 lines of code or so? Mistakes will be made and VB can not and will not catch the type mis-match for you. There is no safety net for you and no guarantee across versions that it will act the same!

Page 63: vba11

Be careful with Variants! 2• The following is a step in the correct direction but still compiles and runs on my machine! “ Option Explicit …

Function add2Longs(lL1 As Long, lL2 As Long) As Long add2Longs = lL1 + lL2End Function

Function conCat2s(szS1 As String, szS2 As String) As String conCat2s = szS1 & szS2 'though "+" will work here, "&" is preferable as it less ambigousEnd Function

Sub Ambig2() Dim szResult As String Dim lResult As Long szResult = conCat2s("1234", "5678") Cells(1, 1).Value = szResult lResult = add2Longs(1234, 5678) Cells(2, 1).Value = lResult

lResult = add2Longs("1234", 5678) ‘“1234”is a string and it got interpreted as a binary! Cells(3, 1).Value = lResult

szResult = conCat2s("1234", 5678) ‘ 5678 is binary and it got interpreted as a string! Cells(4, 1).Value = szResultEnd Sub“

Page 64: vba11

Be careful with Variants! 3• Finally, using the 2 functions in the previous slide, we write something that will not compile on the

3rd & 4th function call because the types are not correct! Declaring all variables beforehand allows the compiler to catch some common mistakes (ambiguities) at compile time. Thus we are more likely to get something that requires less debugging later and/or is much more robust!

“Option Explicit …

Sub AmbigNot() Dim szResult As String Dim szS1 As String Dim szS2 As String Dim lResult As Long Dim lL1 As Long Dim lL2 As Long szS1 = "1234" szS2 = "5678" lL1 = 1234 lL2 = 5678 szResult = conCat2s(szS1, szS2) Cells(1, 1).Value = szResult lResult = add2Longs(lL1, lL2) Cells(2, 1).Value = lResult lResult = add2Longs(szS1, lL2) ‘must change argument 1 to a long Cells(3, 1).Value = lResult lResult = conCat2s(szS1, lL2) ‘must change argument 2 to a string and lResult->szResult Cells(4, 1).Value = lResultEnd Sub”

• Declaring everything allows the compiler to thoroughly do type checking! This results in the fewest run time bugs and surprises. Don’t trust the compiler to do what you mean because it may not be what you intended. The few extra lines really help catch these very common mistakes and help write clear code!

Page 65: vba11

Performance Tuning! (I feel the need for Speed!)• The following VBA code is from a worksheet I have on my machine addtest.xls in a module “modAdd”:

“Option Base 0Private Declare Function timeGetTime Lib "winmm.dll" () As LongDeclare Sub Sleep Lib "Kernel32.DLL" (ByVal dwMillisecconds As Long)

Sub AsleepTest() Const lSLEEPTIME As Long = 10 Dim lTime As Long lTime = timeGetTime Call Sleep(ByVal lSLEEPTIME) lTime = timeGetTime - lTime MsgBox (" measured time slept (for nominal 10 milliseconds) was= " & Format(lTime) & " milliseconds ")End Sub

Sub addTest() Dim lAdd As Long Dim lLoop As Long Dim lTime As Long Const lMAX As Long = 10000000 Const LONE As Long = 1 lAdd = 0 lTime = timeGetTime For lLoop = 1 To lMAX lAdd = lAdd + LONE Next lLoop lTime = timeGetTime - lTime MsgBox (" for loops = " & Format(lMAX) & " time in milliseconds was= " & Format(lTime) & " milliseconds ")End Sub“

Page 66: vba11

Performance Tuning! (I feel the need for Speed!)• With code like the little snippet above inserted in your project you may be able to

do rough profiling of your code and see where it is spending it’s time. However millisecond resolution may require looping and averaging for things that don’t take much time. This can be a lot of work! However for longer things they are better than a stopwatch and a thumb. The following is for a PII @ 400Mhz running Win 9x. (I don’t think the operating system would matter … it was probably Windows 98 first edition or OSR2? I did not try various operating systems.)

• The VB(A) Sub AddTest (average of several tries) did ~5,348,308 (maximum) additions in 1 second. [This is all internal to VB. Writing to the screen is much longer. Depends on hardware but think in terms of Milliseconds! Also things depend on if a Cell is visible or not! (say ~.5 or .6 milliseconds for non-visible and about a millisecond for visible cells.) In terms of calling convention(s) their appears to be very little a difference that I could discern between VBA and C when they make a call to a C DLL. VBA is slightly slower depending on the number of arguments but all in all there is almost no time penalty for VBA verses C! (Probably the stack in VBA is slightly slower!)]

• Similar code written in C to Sub AddTest initially did ~62,000,000. additions. (~16 times faster than VB for lval=lval+1. (After some tuning it went to ~67 million.)

• The maximum I obtained was using inline assembler in C. Pulling out all the stops I could think of including some loop unfolding I got a max of 387,116,923 additions. This was close enough to the theoretical limit of 400Mhz of the CPU that I stopped here! (Also I was out of ideas/tricks/”imiagination” for further improvements!) This last benchmark nearly matches the clock and supports claims in the literature that the PII and above is a combined CISC/RISC CPU. Not actually achieving 400 Megahertz could be attributed to other things going on in the machine such as the “Kernel” and/or network activity (besides my “lack of imagination” and/or my poor (assembler) programming skills.)

Page 67: vba11

Performance Tuning! (I feel the need for Speed!) 2

• The above is significant to keep in mind while you are building/designing your application so it will have reasonable performance. (Can write it first and fix it later. We’ve done this!) Also with multiple Gigahertz CPU even the 1553 list processor sheets (quite a bit of Worksheet Cells access) go by so fast one hardly has time to get a glimpse of the error color codes before it changes to the next sheet and does that list as well! Delays have to be added! (Because data is kept in the worksheet itself that access is slower than if it was in an internal array. One easy trick that works well if looping is involved is to read the data in the sheet into an array and then work off that array from subsequent loops.

• If the loop is in a DAQ then it may be worth moving it to some C code. A VB unpacker may take ~8 milliseconds to unpack a Chain of SVX’s while the C code takes < 1 millisecond (less than can be easily measured with the profile routines above.)

Page 68: vba11

Performance Tuning! (I feel the need for Speed!) 3• The last VRBC/VRB test program I was working on Used the parallel port to trigger

the SCL card on the VRBC. One problem I had was that on a 2 Gigahertz machine the pulse was sometimes too short at ~ 2 microseconds:

“ iPlace = VB_pokeIO(iPPortBaseAdd, iValHi) iPlace = VB_pokeIO(iPPortBaseAdd, iValLow) “ The obvious easy fix is to just paste in as many “iPlace = VB_pokeIO(iPPortBaseAdd, iValHi)” lines as one needed to add microseconds ~1 at a time until it the pulse was as long

as desired. (We initially wanted a solid ~ 4.2 microsecond pulse.) Although we kept adding lines until we had a pretty good 5 microsecond pulse we found we had to pop in an extra one or two to “make sure” as sometimes the pulse out was rather short and we would get out of synch in additions to wasting time. Also if and when there was a failure in the test we were not always sure that perhaps something else similar had taken less time.…

• Also after the trigger we needed additional (variable) delays that were guaranteed to not be less than some amount “X”. There were other critical places where precision (microsecond) delays would really really be nice. These delays could (and would because of the Kernel and random network traffic) always be longer but would be guarenteed to NEVER be shorter: microsecond precision time hold offs. (It would also be nice if they were as low a latency as possible so that one could go down to ~ a microsecond for the call it would take!

Page 69: vba11

Performance Tuning! (I feel the need for Speed!) 4• Eventually after ~ a week of some long days this kludge:

“ iPlace = VB_pokeIO(iPPortBaseAdd, iValHi) iPlace = VB_pokeIO(iPPortBaseAdd, iValHi) iPlace = VB_pokeIO(iPPortBaseAdd, iValHi) iPlace = VB_pokeIO(iPPortBaseAdd, iValHi) iPlace = VB_pokeIO(iPPortBaseAdd, iValLow) “

was able to be replaced with a guarenteed holdoff:“ dMicrosecs2wait = 1 iPlace = VB_pokeIO(iPPortBaseAdd, iValHi) dRetElapsed = waitmicrosecondsd(dMicrosecs2wait, dTicksPerMicroSec) iPlace = VB_pokeIO(iPPortBaseAdd, iValLow) “

• On a 2 Gigahertz machine the pulse this makes is very close to 3 microseconds! And it’s that way for billions of times! (May be longer but never shorter!)

• The above along with a (sub-)microsecond timer/benchark/profiler routine is now available! An example spreadsheet is us2.xls

• The module, modHiResTime uses sequtil.dll which is meant to be copied from d0server4\users\angstadt\sequitl\debug\sequitl.dll into the target machine’s c:\winnt\system32\ (or equivalent.) The source is there as well in one sub-directory up as sequtil.c. Use of the inline assembler instruction “RDTSC” which gets a 64 bit count of the clock into the CPU. (Yes, > 2 Gigabit / second are built into the CPU of PII and above counters!) (Note, you have a choice of where you can pick these routines up from. They are also in a 2nd newer version of the NT Bit3 drivers for the 617 which does DMA. These routines are put in here as well because they are used by the driver. Right now they are in both modules in case someone wants precision timing while using a 40x and/or doesn’t need any Bit3 DLL: sequitl.dll requires no drivers but the Bit3 dll’s do. So for the moment these microsecond timing routines are duplicated in both DLL’s. (You can “Register” them from either DLL, whichever is more convienent for the work you are doing!)

• Various other cool stuff is in sequitl.dll including a 64 unsigned (huge) counter with overflow (could add another 32 bits to make it 96 bit counter with no loss of precision down to the last LSB! We needed modulo operations on the low 32 bits. Also AFE unpackers that work in ~ 600 micoseconds or less for 8 full boards (64 chips). (Started around 2 milliseconds but used the microsecond profilers along with C macros to reduce that.) One millisecond binners and more!

Page 70: vba11

More on Objects and Methods (& speed)• One common problem is to erase a bunch of cells. If one only

knew what has been shown so far one might write something like:“Sub eraseSheet() Dim lRow As Long Dim lCol As Long For lRow = 1 To 65536 For lCol = 1 To 255 Cells(lRow, lCol).Value = Empty Next lCol Next lRowEnd Sub”

• While this works it runs so slow on my 2.5Gigahertz machine I stopped it via Ctrl-Break. No need for hi-resolution timers/profilers here! Why? (Besides the fact that 255 * 65535 = 16,711,425 cells.)

One reason is that the sheet is trying to recalculate itself after every “Cells(lRwo,lCol).value=Empty” assignment statement. That’s a lot of recalculation. Too much! In most Bit3 sheets you will see a Bit3 helper module “iomodl” or maybe “iomodlx” where the x at the end is a version letter. (Presently the latest is now “x”=“e” or “iomodle”. “iomodl” was for something like “input output module library”. In this are 2 pretty useful routines: allAutoOff and allAutoOn which turn off and on recalculation. Realizing all the above and rewriting so we call these before and after the loops results in waiting and waiting and waiting. (Minutes!)

Page 71: vba11

More on Objects and Methods (& speed) 2• Turning off auto calculation doesn’t work either!

“Sub eraseSheet2()' still too slow way 255 * 65535=16,711,425 operations! Dim lRow As Long Dim lCol As Long Call allAutoOff For lRow = 1 To 65536 For lCol = 1 To 255 Cells(lRow, lCol).Value = Empty Next lCol Next lRow Call allAutoOnEnd Sub”

• Something else is needed! One could always try:Sub eraseSheet3()' fast! Cells.Value = EmptyEnd Sub

• Eureka it works in a few seconds! What if it didn’t work? (It works on my XP machine…. What if it didn’t work on an earlier version? What if the particular object you are working with today supports this type of aggregate operation and the next object doesn’t? This has happened to me. ) Or is there something even faster? How to find out? Let’s see how Excel would record it!

Page 72: vba11

Recording Macros: (Let Excel Show you the Objects!)

• If we wanted to delete everything on the sheet how would we do it by hand? The fastest way I know (maybe you know another way faster) is to select the whole sheet by (left) clicking the upper left corner cell just above A1 that is in the darker tan and then press the “delete” key. So lets do that but first we are going to turn on the macro recorder. From the Excel sheet go to “Tools”, “Macro”, “Record New Macro” and then click on the “OK” button on the dialogue box and do as above. Click on the upper left corner cell about the “1” and to the left of the “A” and the whole sheet is now selected. Press the “delete” key and the screen flashes. The whole screen is still selected so we decide to just select a cell by clicking on say “A1”. Now we stop recording by clicking on the stop button that should have popped up on the screen somewhere… if it didn’t go to “Tools”, “Macro”, and then “Stop Recording”. Then we go find what it recorded:

“Sub Macro1()'' Macro1 Macro' Macro recorded 3/19/2004 by me!'

' Cells.Select Selection.ClearContents Range("A1").SelectEnd Sub”

Page 73: vba11

Recording Macros: (Let Excel Show you the Objects!) 2

• In the very beginning I asked why VBA and not VB and one of the reasons was because VBA was in the context of Excel and all of it’s objects? Well the cells collection (double dimensioned array) is really a member of a much longer string of collections (structures) which are optional to specify but if one is aware of them they are waiting to be used to completely control and automate your worksheet! One could write a routines to insert sheets, delete them, erase all of them, and/or some of them ect. One can basically construct a Custom Worksheet from scratch! Including renaming the sheets from code and saving the whole thing as a new Workbook! All from VBA. (And yes I’ve seen articles on even modifying the code that could modify itself: self-modifying code, but we’re not going there!)

“Sub EraseActiveSheet()‘macro 1 synonym #1 (“ActiveSheet” was always implied!) ActiveSheet.Cells.Select Selection.ClearContents Range("A1").SelectEnd Sub”

Page 74: vba11

Recording Macros: (Let Excel Show you the Objects!) 3• And yes there are more objects in front of the ActiveSheet! There’s also an ActiveCell!

The following are all valid forms of the same thing! ( Where this syntax breaks down is at the application level. There is no “ActiveApplication” and/or “Applications” although you can put an “Application” in front of most of these but I haven’t found a use/need for it for the things I’ve had to do. I’ll leave that between you and your help notes and your own time if you think you need to journey there.) These all erase the active sheet quickly and are effectively synonyms.

Sub EraseActiveSheet3() ' active active! ActiveWorkbook.ActiveSheet.Cells.Select Selection.ClearContents Range("A2").SelectEnd SubSub EraseActiveSheet4() ' mix active and names! Dim szSheetName As String szSheetName = ActiveSheet.Name Workbooks("hello.xls").ActiveSheet.Cells.Select Selection.ClearContents Range("A3").SelectEnd SubSub EraseActiveSheet5() ' equivalent to active active! Dim szWorkBookName As String Dim szSheetName As String szWorkBookName = ActiveWorkbook.Name szSheetName = ActiveSheet.Name Workbooks(szWorkBookName).Worksheets(szSheetName).Cells.Select Selection.ClearContents Range("A3").SelectEnd Sub

Page 75: vba11

Recording Macros: (Let Excel Show you the Objects!) 4• This is very important/instructional because many things follow this model and because this

allows control over the whole Worksheet and not just the active components. It is also a prelude for acting on things without selecting them and/or making them active! (This will also be the fastest! If it is not visible then the screen does not have to be redrawn!)

• The following just does it without making them visible. BAM!Sub clear1Sheet(szSheetName As String) Worksheets(szSheetName).Cells.ClearContentsEnd SubSub aaaTestClear1Sheet() Call clear1Sheet(“Sheet1")End Sub

• Various forms become immediately obvious:Sub aaaClear3Sheet() Call clear1Sheet("Sheet1") Call clear1Sheet("Sheet2") Call clear1Sheet("Sheet3")End Sub

Sub aaaClearLoop() Dim szS As String Dim i As Integer For i = 1 To 3 szS = "Sheet" & Format(i) Call clear1Sheet(szs) Next iEnd Sub

Page 76: vba11

Recording Macros: (Let Excel Show you the Objects!) 5• But the best presents another level (look at wSheet and the for loop) saves having to find out how many

sheets are in the workbook! Saves lots of program lines:Sub aaaClearAnyWorkBook()' best and preferred but can you say "WIPEOUT“ as in blank every and ALL Sheets!?' note the new fancy but very powerful syntax Dim szS As String Dim wSheet As Worksheet

For Each wSheet In ActiveWorkbook.Sheets szS = wSheet.Name Call clear1Sheet(szS) Next wSheet End Sub

• The following is from an existing sheet is rather useful just erasing a range of cells. Most were recorded!Sub EraseA8toC5k() If ActiveSheet.Name = "VRB-VRBC" Then Range("C34:F5000").Select ' Clear the contents of the cells, being careful not to clear the Bit-3 Selection.ClearContents ' related stuff at the top! Range("C9").Select End IfEnd SubSub ErasePrevErrors()' erases any vme errors from reading 0xc0d0 and/or 0xc0d2 If ActiveSheet.Name = "VRB-VRBC" Then Range("E29:E30").Select ' Clear the contents of the cells, being careful not to clear the Bit-3 Selection.ClearContents ' related stuff at the top! Call ColorCell(29, 5, 2) ' white Call ColorCell(30, 5, 2) ' white Range("C9").Select End IfEnd Sub

Page 77: vba11

An Introduction to Zorder…• Collections of objects allso have an index number. We’ve been referencing them by name, but we could also

reference them by an index number. There is no particular advantage either way. Sometimes names are easier and sometimes an index number is easier. The previous sub clear1Sheet() easily morphs to clear1SheetIndex() with a change of arguements:

Sub clear1SheetIndex(lSheet As Long) Worksheets(lSheet).Cells.ClearContentsEnd SubSub aaaTestClear1SheetbyI() Dim lSheet As Long lSheet = ActiveSheet.Index Call clear1SheetIndex(lSheet)End Sub

• One could make 1 routine that would work either way passing lSheet in as a variant or as a worksheet or object but for the most part it is extra overhead that I don’t feel is necessary (slower?). Also I don’t entirely trust it to ALWAYS do the right thing and make the right decision/branch/whatever. I prefer spelling things out as explicitly as I can and keeping track of the calls myself. (Call it a stylistic choice to help to get >9 billion events!) Less is more!

• In the snippet above “lSheet” is a user variable that holds the activesheet Zorder (index) number. A possibel caveat when working with Zorder/index numbers is that in the beginning (~8 years ago) they were immutable and in some sense were sort of a creation number (index). Apparently in the XP version on my computer there are some methods and newer “postion” properties to manipulate them and their order but I don’t have much experience with them and in some older version of Excel I am pretty sure they would not be there?

• If your project involves several objects of a particular type and manipulating them in code via their index number or Zorder number maybe a better approach/plan then using names. The caveat is that you may have to start with a brand new sheet and carefully create your objects in the order you want without any deletions. That way they will be in a known order with no holes or skips in their index and/or Zorder numbers. An example was the first spreadsheet I wrote which had 480 textboxes all hooked to the same macro: 1 generalized macro rather than 480 macros for each textbox. When the user clicked one of the the first thing that happened was the macro figured out which one had been clicked based on their Zorder. So I needed to know which one had been clicked and then go set a bit (in VME via the Bit3) based on that click. I needed them of a uniform size and position and I needed a uniform Zorder. Doing this by hand was impossible without messing them up so badly I needed to just delete it… Then I would have to start over with a clean worksheet because the Zorder was then wrong. The only way I could do it was to create them all uniformly in code!

Page 78: vba11

An Introduction to Zorder 2• It is possible to create and delete buttons and other objects from VB with the “.add”, “.delete”

methods. Also it is possible to reposition them on the screen and to hide them and make them visible again. Macro names may also be attached to them through VBA code using the Zorder property if they are added to a Worksheet in the desired order to begin with!

• One thing that should not be done is to use “.add” and “.delete” methods over and over again in a loop! Once the object exists by whatever means use the “.Visible=“True/False” property if it is available. Besides being way faster it will prevent any memory leaks from accumulating and internal index numbers from wrapping and structures growing out of (memory) bounds and who knows what kind of bad internal things from happening. (Blue screens of death, whatever!)

• It is much better to make the maximum number of things you need and then manipulate them as necessary to cover all the bases. If you don’t want to see a chart at the moment but will want another one just like it in a second then try something like:

“ActiveSheet.ChartObjects("Chart 1").Chart.Visible=True” and /or ActiveSheet.ChartObjects("Chart 1").Chart.Visible=False”

• Some examples including code samples that manipulate a chart to plot a user specified histogram of say an SVX chip from VBA took me some time but are available on d0server4\users\angstadt\xls_CTS\VRBdaqx.xls where the 2nd to the last “X” is a (the highest) version number. For example “VRBdaq3.xls”. This was not obvious (to me) code and took me a while to get it as far as it is so maybe it can be a help to someone who may need/want to do something similar.

szS = "10:10," & Format(lRow) & ":" & Format(lRow) ‘ construct the range

'ActiveSheet.ChartObjects("Chart 1").Activate ‘1st half of does it but activates the chart first. Below just does it!

ActiveSheet.ChartObjects("Chart 1").Chart.SetSourceData Source:=Worksheets("VRBbin1").Range(szS), PlotBy:=xlRows

ActiveSheet.ChartObjects("Chart 1").Chart.HasTitle = True

ActiveSheet.ChartObjects("Chart 1").Chart.ChartTitle.Text = “your chart title here”

ActiveSheet.ChartObjects("Chart 1").Chart.Axes(1).AxisTitle.Caption = “your x axis caption”

ActiveSheet.ChartObjects("Chart 1").Chart.Axes(2).AxisTitle.Caption = “ your y axis caption”

• The fact the object (creation) Zorder is sticky when objects are created and destroyed means a

history of them are kept…. This is also true of Excel in general and VBA in particular. Cleaning removes mainy things including the Zorder!

Page 79: vba11

Cleaning a Worksheet: VBA under the Hood.• When is WYSWIG NOT WYSWIG? When it’s the VB IDE! By all I’ve read in many/most VB

books, shockingly, VB does not store your code as you type it in! It stores it in it’s own internal format that may be thought of as an intermediate form. (This technique is not new and is often referred to as p-code.) This is useful and we can think of it as that and call it that. The only thing that is preserved exactly as you type them in is the variable names in some sort of internal name table and strings inside of quotation marks.

• So when the IDE is called up of a big sheet and the VB windows are popping up and the VB code is being displayed for us the VB compiler/interpreter/whatever is converting p-code and nametables back to something close to what was originally typed in! The original ASCII text was never stored anywhere except for variable names that are in the “name table” (structure) internal data base. (For more on compiling and p-code see slide 81.)

• From page 25 of “Microsoft Excel 97 Developer’s Kit”: “ Code Cleaning As you are writing a VBA program, name spaces and other structures are created

invisibly in the background to track and manage your project. As you rewrite and move code and objects, unused structures build up in your project. These development leftovers do not affect the average user, since they don’t add up enough to make a significant difference. But large and/or complex development efforts can benefit from periodic removal of these unused structures. Removing these development leftovers is often called “cleaning your project” or “stripping your code.”

Cleaning a project involves saving all of it modules and forms out to text files, deleting the old modules and forms, and then (at this point I recommend saving the file as another file name. Leaving Excel entirely and then re-opening the file just saved and then) reimporting the modules and forms from the text files. Larger projects may see a 25-percent reduction in file size once you’ve cleaned them. This reduction in file size may or may not make any real difference in the load time or the run time of your project.

Be sure to recompile your project after this cleaning. The load time saved by the reduced file size can be negated if the modules must re-compile on startup….

Page 80: vba11

Cleaning a Worksheet: VBA under the Hood. 2

• With my last project I just cleaned a WorkBook that had been around for years and years through tons of revisions by various people and was up to over 20 Megabytes. I didn’t just clean it, I reconstructed it from scratch to fix any Zorder goofiness that was in it. It is now down around 10-12 Megabytes. A savings of almost %50. (It ran no faster. Perhaps an imperceptible amount slower.) (Originally it was descended from a workbook called “VRBfast32.xls” This name was stuck as the name of the code modules. This can happen when a workbook is copied from one name to the other via the operating system instead using the file “Save as” menus. One should always use the “Save as” feature VB module title will be uncorrupted and track the filename of the workbook rather than copying the file by some external means such as the operating system to prevent old wrong names getting stuck in the sheet! (Fixing this by hand with an editor makes the Excel file unusable with a checksum error! Argh!)

• This almost brings up another topic in and of itself, what I call Excel’s “stickyness”. It’s dripping with it!

Page 81: vba11

Cleaning a Worksheet: VBA under the Hood 3• Excel is extremely sticky: if something is copied from one Workbook to another Workbook

and there are any formulas or buttons or charts or other objects (not just values) then the new Workbook will have links and/or references back to the original Workbook. Also, copying Buttons and/or Charts from one Workbook to another will change the Zorder on the same object in the new Workbook. Any Macros assigned to them will still be attached to the original workbook unless they are re-assigned to get them to refer to the code that presumably has been imported into the Target Workbook.

• Though there are some menu selections on the main menu for attempting to change links after the fact: “Edit”, “Links” and “Insert”, “Name”, “Define” the best approach is to not create the Links in the first place! This is because if the *.xls file is viewed with an editor of some sort one can clearly see that old links are still maintained in the Excel file. They just may no longer appear anywhere you can get at them. This is similar to old VBA code! If objects are copied from one worksheet to another to another it may use a lot more disk space then necessary and/or desired.

• The best thing to do is to realize this and re-create/reconstruct copies of Sheets from one book to the other and not copy the whole sheet. (Within a workbook it is o.k. to copy a Sheet. In fact this is one way to reconstruct a workbook! Say that the source workbook has 5 list processor sheets of some type. In the target create the buttons for 1 sheet by hand using the “Forms” toolbar as described earlier. Assign and edit the button to look like the source Sheet. (Once one button is created this way it may be copied as long as it is within the same Workbook!) Once all of the objects are created then high light the source cells to be copied making sure no buttons are high lighted. When pasting into the target sheet do a right click and select “Paste special” and then values and/or text only. This may have to be repeated a few times to avoid bringing in any unwanted buttons. (One can delete them on the target but just be sure not to save it!) Check to make sure that all formulas did not develop any “oldsheet.xls!” before the formula. If they are there then back up and try again or edit them out! After one “clean” list processor sheet is fully created in the target workbook then it may be copied within the same workbook as many times as desired. Once the sheets are there then for each sheet go back to the source and do the copy and “Paste special” for the different values and then check and kill any unwanted links. This may take some time but is the only way I know to really cleanly severe all links with the old and/or original Workbook(s).

Page 82: vba11

Cleaning & Generalizing Macros you Record• Excel is so sticky that many times when recording a macro it puts in the current

spreadsheet name followed by a “!” and then the actual macro name. This is especially true when selecting worksheets. The first level of generaliziation is to edit the macro and then remove the specific Worksheet file name. Generally this is a good idea. If it is not done and the Worksheet is saved as a different filename then the Macro will suddenly stop working because the first and original file name was in front of the “!”. This is not what we generally want. It is worth going in and editing out the file name.

• When a macro is recorded it puts in the actual values you recorded. Many times we may want to do that same thing over again with different values or ranges. We can record something to find out the objects. Then we can generalize it to do any range we want. A common thing to want to do is to erase a range of cells. If we just record we could get a bunch of routines that did a bunch of different ranges like on slide 68. Here’s one:

Sub DelRangeC8J34() Range("C8:J34").Select Selection.ClearContents Range("C8").SelectEnd Sub

More Useful and general is something like this: Sub DelACellRange (szRange2Del as String, szCellLeftActive as String)

Range(szRange2Del ).Select Selection.ClearContents Range(szCellLeftActive ).SelectEnd Sub

Page 83: vba11

Cleaning & Generalizing Macros you Record 2• One could break it down further if one desired. Though with this routine it may seem contrived but one

can also do something with numbers in a loop using string concatenation “&” with the “format()” function (changes binary to ASCII string) as far as needed:

“Sub DelACellRange2 (szColStart as String, lRowStart as Long, szColEnd as String, lRowEnd as Long)

Dim szS as string szS=szColStart & Format(lRowStart) & “:” & szColEnd & Format (lRowEnd) Range(szS ).Select Selection.ClearContents ‘we’ll have a default active cell be the upper left of the range passed in, though it could be a

separate ‘argument as in the previous routine. szS=szColStart & Format(lRowStart) Range(szS ).Select End Sub”

• If one wanted to make all of the arguments in the above routine numbers of say type long then one could use another (helper) function to convert the Column numbers to a Column letter of type string:

“Function lnum2ASCIcolFrom1D(lNum As Long) As String ' n = 1 to (255) to column A through IV (D at the end is for finally debugged... finally works)' clamps to first and last column of sheet if <=0 or >256. Dim lMantissa As Long Dim lASCI As Long Dim szRet As String Dim lRemaind As Long 'limits are clamped to the end columns If lNum <= 0 Then lNum = 1 '0 is disallowed If lNum >= 256 Then szRet = "IV" GoTo EarlyOut End If

Page 84: vba11

Cleaning & Generalizing Macros you Record 3 If lNum <= 26 Then szRet = get1ASCI(lNum) GoTo EarlyOut End If 'lMantissa = lNum \ 26 '"\"=integer division: fractional part is truncated 'lmantissa = lnum \ 27 ' both these fail. 52\26 = 2 & z is left out ' lnum \27 doesn't go to 2 until 54... a case will do it but it is ugly... but it works. Select Case lNum Case 1 To 26 lMantissa = 0 'should not get here Case 27 To 52 lMantissa = 1 Case 53 To 78 lMantissa = 2 Case 79 To 104 lMantissa = 3 Case 105 To 130 lMantissa = 4 Case 131 To 156 lMantissa = 5 Case 157 To 182 lMantissa = 6 Case 183 To 208 lMantissa = 7 Case 184 To 234 lMantissa = 8 Case 235 To 256 '> 255 is an error: do what? lMantissa = 9 Case Else Call MsgBox("number 2 hi in lnum2ASCIcolFrom1") End Select

Page 85: vba11

Cleaning & Generalizing Macros you Record 3

szRet = get1ASCI(lMantissa) ' first char

lRemaind = lNum - (lMantissa * 26) 'mod does not work here but this does. szRet = szRet & get1ASCI(lRemaind) '2nd char EarlyOut: lnum2ASCIcolFrom1D = szRetEnd Function 'lnum2ASCIcolFrom1D”

• Here’s a helper called by the above function. This is done several times in several places so do it here 1 time as a function:

“Function get1ASCI(lNum As Long) as String'lnum assumed to be 1..26 in for A..Z out. no checking here; checking must be external. Dim lASCI As Long Dim szRet As String lASCI = lNum + 64 'lnum is 1..26 so 1 + 64= 65 is "A" szRet = Chr(lASCI) get1ASCI = szRetEnd Function”

Page 86: vba11

Cleaning & Generalizing Macros you Record 5• Probably some of you are wondering what the heck is ASCII and what does “Chr()” do?

Briefly, ASCII is a commonly used way of representing the alphabet internally in a computer. It is a mapping of binary numbers to the alphabet. See the VBA help notes and/or the Web or find a programming book. Chr() is a VBA intrinsic function that returns a character (string) given the ASCII binary number. (To go from a number to the ASCII Character it represents use “Asc()”. I’m just introducing it here in case you run across the need for something like this but look at the help notes or the Web for more details. Also see “unicode”.

• Now we could re-write DelACellRange2() to use all the same type of arguments: “Sub DelACellRangeL(lColStart As Long, lRowStart As Long, lColEnd As Long, lRowEnd As

Long) ' this one has consistent arguements. may be easier to call from a loop? Dim szS As String Dim szColStart As String Dim szColEnd As String szColStart = lnum2ASCIcolFrom1(lColStart) szColEnd = lnum2ASCIcolFrom1(lColEnd) szS = szColStart & Format(lRowStart) & ":" & szColEnd & Format(lRowEnd) Range(szS).Select Selection.ClearContents 'we’ll have a default active cell be the upper left of the range passed in, though it could be

a separate 'argument as in the previous routine. szS = szColStart & Format(lRowStart) Range(szS).SelectEnd Sub 'DelACellRangeL”

Page 87: vba11

Cleaning & Generalizing Macros you Record 6• Now that we have our routine in all numeric input we can easily add a check to make sure that

we don’t exceed the column limits of 1 to 256 and the row limits of 1 to 65536 with a little routine like this:

“Function lClamp(lNum As Long, lMin As Long, lMax As Long) As Long If lNum < lMin Then lNum = lMin If lNum > lMax Then lNum = lMax lClamp = lNumEnd Function”

I’ll leave you to add it to DelACellRangeL() as an exercise. In earlier version of Excel if the cell range was exceeded a blue screen of death could result. In later versions they seem to trap this better for you but it is worth trapping it yourself for robustness.

• When do you make something into a function or sub? When it is called in multiple places. It will save you time in the long run because when it’s starts being called from more and more places if a change must be made then (hopefully) you can just change that function! Though changes percolate through the code especially with argument changes. This can be a problem.

• Generally speaking one thing I recommend is to try to just use type “Long” as opposed to “Integer” (unless you need an integer for a call to a library you are using). The CPU register is 32 bits=long. There is hardly any gain to be had by using “integer” and later you may want more than 16 bits as 32767 is really not that much. Same with “Double” and “Single”. The floating point is all done as “Double” (actually extended) in hardware so one is just throwing away precision that is already there if one doesn’t use at least “Double”. “Single” may actually take longer because it’s converted to “Double” and then back to “Single” each time! Also precision is lost. Nothing significant is gained and speed and precision are lost by using the smaller types.

• See the VB help notes for more on using these helpful intrinsic conversion routines available: CBool(expression), CByte(expression), CCur(expression), CDate(expression), CDbl(expression), CDec(expression), CInt(expression), CLng(expression), CSng(expression),

CStr(expression), and CVar(expression). (Of course be careful when going from a larger type to a lesser type that you are not throwing away bits you need.)

Page 88: vba11

Compiling a Worksheet.

• The following are selected excerpts starting from pg xxxvii of VBA Developer’s Handbook (op cit.) Craig Symonds again:

“ Compilation means taking source … (and doing) ..a syntactical analysis of the code,... a semantic analysis of the code, and in some cases we do some minor optimizations of the generated code. We actually do things like internally generating compilaion trees and then emiting the trees for generating the actual, what we call the ex-codes, or p-code, depending on the situation. In Visual Basic, we actually serialize these p-codes into Window code segments. We generate a true portable executable format (PE Exe), and we use the Windows loader to load our p-code into code segments. That way, Windows is doing all the swapping in and out of the p-code, just as it would do for native code… The only difference is that the resulting code that is generated is not directly understood by the X86 chip set. It is understood by an interpreter that runs on any of these file types….

… if you have a constant expression, we will actually evaluate the expression and use the value inline. We do things like optimize local allocations, so if you have a large allocation as a local variable, will actually do a heap allocation for that thing on entry to the procedure instead of using a stack space for this. But if it’s below a certain threshold, we will do a stack allocation for it. …

(Actual profiling of this very thing on slide 107! ) At the time you enter the code, we actually parse it as soon as you enter it….we put it in a

form we call op-codes with is really a parsed version of the p-code. We take out all the names, and we add them to a name table. We identify all the keywords and put that into the op-code stream, and we can list back the op-code stream very fast. As soon as you enter your line the source as you entered it has gone away and is replaced with this (canonical) binary representation (format), regenerated for you on the fly….

Page 89: vba11

Compiling a Worksheet 2 “But back to the parsing phase: once we’ve converted you text into op-codes, we actually take

multiple steps to get to the compilation phase. We know all the module-level declare statements, constant declarations, and module-level variables. We also know all the procedures you define in your modules, so we can pick all of those out without going and compiling the whole thing. That is how we can do things like populate the drop-downs, and how the Object Browser can show the members of your project without actually compiling it, and those sorts of things. Even if there are syntax errors or compilation errors in the body of a procedure, we can still pick out all of these module members. Then we go through a couple of other stages where we can figure out all the variables that are declared--even local variables. We then go through a binding phase. And the final phase is where we actually emit the generated code…”

(Code can be generated in one of 2 ways, on demand, or via the “Compile” option. On demand is when you hit the F5 button in the VBA IDE and it (tries to) run the current procedure.)

“…There are two main things that are in the storage. One is the op-code stream I talked about. That’s the parsed representation of the source which contains pointers into a name table (which contains all the names you’ve supplied).

The second major thing is the actual compiled code. We serialize the compiled code into the storage in addition to the source code, if the code’s compiled. This I the ex-code stream I talked about earlier.

In addition we store any run-time structures necessary to load and run the ex-code stream. We serialize a resource descriptor table for handling construction and destruction of complex datatypes like objects or structures. There is a table for public entry points that we serialize so that we can answer questions that the host asks us, like, “Can you give me a list of all the public procedures?” We serialize fix-ups that we do at load times, so, depending on when we get loaded, we need to fix up the jump addresses. We actually use native code entry points for all our procedures. Calling a Basic procedure, even though it is p-code, we generate a native code stub that we call into, and this stub turns around and calls the engine, passing the pointer to the p-code….

…Also, (demand) compilation only happens the first time you use the function… (so it will only run slower the first time if you don’t do a “Debug”, “Compile” (all)).

Page 90: vba11

Compiling a Worksheet 2 “Even our own run-time is written in C, so it is already native code. So anytime

you call Sin, for instance, you’re not executing p-code, you’re executing native code. The only thing that would help is translating these ex-codes into native code. That way, you could just execute the native code without jumping to this p-code engine.

We found that in the average case, only around 5 percent of the time was spent inside the p-code! Even in what you would consider fairly high-performance cases, for example, sitting in the loop doing string concatenations, we spent just under 50 percent of the time in the p-code. The other part ot the time was calling into the Windows subsystem for allocations or calling into Automation to do string manipulation. And all that’s written in native code already.”

“ …. That’s not to say that native code isn’t important. There are definitely snippets of any solution where the code is computationally bound like sorting an array or doing some computational operation that could benefit from the performance of the native code, but in end-to-end scenarios. It provides a less dramatic improvement than you might think.”

The above is pretty much what we’ve found. Calls in VB are almost native fast. Working on large arrays can be slow. Here calling some C code that does the operation on the array and works “natively” works very well. (Not much speed would probably be gained by doing the whole application in C. By the time this was done for all of the applications that are in use at DZero we wouldn’t need them anymore as they would have taken a lot longer to write as all the functionality of VB and Excel that was used would be lost and have to be reinvented as well.

Finally what the heck really is p-code? Basically it is a kind of abstracted and/or virtualized assembler that some sort of interpreter can map quickly to the native assembler for that platform. The following quotes are from the web:

http://compilers.iecc.com/comparch/article/91-10-067

Page 91: vba11

P-code example from the Web “… The P-codes: P-codes are the machine language of the imaginary P-machine. P-codes occupy four bytes each (in this particular example). The first byte is the operationcode (op-code). There are nine basic P-code instructions, each witha different op-code. The second byte of the P-code instruction contains either 0 or a lexicallevel offset, or a condition code for the conditional jump instruction. The last two bytes taken as a 16-bit integer form an operand which isa literal value, or a variable offset from a base in the stack, or aP-code instruction location, or an operation number, or a special routinenumber, depending on the op-code.

5.1 P-code details:p-code hex descriptionop-code-----------------------------------LIT 0,N 00 load literal value onto stackOPR 0,N 01 arithmetic or logical operation on top of stackLOD L,N 02 load value of variable at level offset L, baseoffset N in stack onto to of stackLODX L,N 12 load indexed (array) variables as aboveSTO L,N 03 store value on top of stack into variable locationat level offset L, base offset N in stackSTOX L,N 13 store indexed variable as aboveCRL L,N 04 call PROC or FUNC at P-code location N declaredat level offset L

Page 92: vba11

P-code example from the Web 2“INT 0,N 05 increment stackpointer (T) by N (may be negative)JMP 0,N 06 jump to P-code location NJPC C,N 07 jump if C=value on top of stack to P-code locationN (C can = 0 or 1)CSP 0,N 08 call standard procedure number N

4. P-code instructions: (Note: POP X menads remove the top element of the stack and load itinto X (the stack is now one smaller). PUSH X means place the valueof X onto the top of the stack (the stack is now one bigger).)

LIT 0,NN literal: push nnOPR 0,0 process and function, return operationOPR 0,1 negate: POP A, PUSH -AOPR 0,2 ADD: POP A, POP B, PUSH B+AOPR 0,3 SUBTRACT: POP A, POP B, PUSH B-AOPR 0,4 MULTIPLY: POP A, POP B, PUSH B*AOPR 0,5 DIVIDE: POP A, POP B, PUSH B/AOPR 0,6 LOW BIT: POP A, PUSH (A and 1)OPR 0,7 MOD: POP A, POP B, PUSH (B mod A)….”

• Why p-code? By abstracting to this level they can then only write the part that is the p-code interpreter for each platform they want to run on, PowerPC, ’86, Alpha whatever and not a whole real compiler for each one thus saving themselves some time. (The newer “Net” version software supposedly runs in “native mode.” But then the whole code base would be need to be modified to work with the different calling convention. Could be done but has not been attempted yet. The old still works! Also “Net” is a standalone product like VB and would require (lots?) of $.)

Page 93: vba11

How Excel Recalculates: Determinism (or lack of it.)

• We have a lot of power and we can do lots of things in different ways. An important thing is that we can put functions in a cell and have them directly update the value in that cell or we can put the function in VBA code and poke the result in a cell. Which is better? (Ignoring speed/time for the moment) the (other part of) the answer depends on if one needs a completely deterministic response? If one does then it is best put in VBA code directly or in a list processor. Either is guarenteed to be deterministic. If it doesn’t matter then it can be put in a cell. The question is if it is put in a cell when will it get updated?

• Given “=VB_readi(lAdd)” is put in a cell and the Bit3 interface boardset is initialized and lAdd is equal to a VME address where a module with a VME “DTACK” (acknowledge or a logic analyzer is running) LED then will light when it is addressed, will it always light up when recalculation is performed??

• Presumably when F9 (“Calc Now”) is pressed? Unfortunately not always! In fact if no cell values are changed by the user then after about the third recalculation or so then the LED will stop flashing! This is because Excel has no way to know that the function is going out and reading an external device on a different bus where the value may not be the same as it was before. After several times of recalculating it sees no changes and it’s algorithm for recalculation is satisfied. So the LED no longer flashes. This is not what we want.

• One way to force recalculation is (with VBA code in a macro) turn off auto calculation, via a call to allautooff, then read a cell reference that all of the “lAdd” addresses are based on, poke in any goofy valid (a zero?), then poke in the original value which is presumably the desired value and then turn on auto calculation via a call to “allautoon. If run in the debugger the LED will always light when the spreadsheet is recalculated. This is what we want! It works and is the way many initial “initVME” routines worked and were written.

Page 94: vba11

How Excel Recalculates: Determinism (or lack of it.) 2

• It is fine as far as it goes and for something like some of the memory dump pages can be made to works by basing all of the lAdds on a base address. When it is changed and put back it will force a recalculation of all the cells that point back to this base address and the function in the cell will get executed and the value on the screen updated.

• Possible problems with this scheme may not matter but they include the following:

1. A given address may be read up to a 100 times! The maximum is determined on the “Tools”, “Options”, “Calculation”, “Maximum iterations” menu. The default is 100.

2. On this same “Tools”, “Options”, “Calculation” the “Maximum change” options (which has a default of “0.001” ) also affects how many times cells are recalculated.

3. What this says it to keep going through every cell until the maximum change between cells is 0.001 but not to exceed 100 iterations. (On large worksheets this can take a while and some cells could easily be executed 100 times!)

Page 95: vba11

How Excel Recalculates: Determinism (or lack of it.) 3

• This is an example of a “heuristic” algorithm. A heuristic algorithm is one that is a workable solution to a problem but not necessarily the best or perfect solution. (Some problems may have no know perfect solution. Traveling salesman problems “shortest distance” graph theory problems historically have had “heuristic” solutions. Because combinatory problems go off scale rapidly especially for large number of nodes (cells) it may not be possible to go through every combination. So some number of combinations are gone through and the best of those examined is selected. The salesman may visit all of his customers but because of other constraints (say schedule?) they may not be able to take the shortest path even if they had calculated all of the possible path combinations and knew for a certainty no paths were shorter.

• Excel has lots of features and I did finally find a function one could insert into another that would supposedly force the target function to be called when F9 (recalculation) took place but I’ve lost track of it because I did not find it reliable enough in the version of Excel I was using. It helped but was not perfect.

• So if 100% determinism is required then one must do it in code or a list processor of some sort. (The latest list processor’s are all code based. The data is stored in the spreadsheet. Some of the older 1553 spreadsheets may have an older version which still have a few functions in the cells. They should be converted to the newer code 1553 code modules esp. if other code is poking in new values and the listproc1553 is called again from the same sheet. (An abuse of original intent: People were supposed to make more sheets but … With more code it becomes more deterministic. The original intent was to enable writing less code.)

Page 96: vba11

How Excel Recalculates: Determinism (or lack of it.) 4

• And am still learning! Originally in many sheets, initvme() (in an early version of “iomod1” was :Sub initVME() Dim ss As String Dim sAdd As String sAdd = "C5" Range(sAdd).Select ' select the cell ss = ActiveCell.FormulaR1C1 ' read (save) whatever the user has typed in the cell ActiveCell.FormulaR1C1 = ss ' put it back in. this makes the macro dynamicEnd Sub

• It worked with the sheet. What was expected to be in C5 of each sheet that the button was on was something like:

“=VB_InitIOs(D2,D3,FALSE)” Basically a formula in a cell which got recalculated as if the user just typed it in or clicked on a

cell and then clicked on the little checkmark to the right at the top in the edit box which is one of the few places that will force the LED to light ALL the time if things are set up as before on the 2nd item of the first slide of this series.

Page 97: vba11

How Excel Recalculates: Determinism (or lack of it.) 5

• But a side effect of this is that (especially for large Workbooks with many sheets) with one of VB_Initios() function could (can) be called over and over and over and over and over again as many times as there are sheets whenever a recalculation occurs. This is not really what one wants to be doing: It was never meant to be called over and over again! In the beginning with relatively simple non-threaded Win 9x boxes and rather simple ISA bus boards with small 700 line or so driver DLL this was still hardly noticable.

• But with multi-threaded NT and thousands of a lines of driver DLL code and PCI versions of Bit3 with (and with the latest Version 2 drivers) which have a half second delay in them to try to determine the CPU clock frequency with some accuracy for precision timing. We start having troubles. The most obvious is for large sheets it takes a half second per sheet wherever VB_Initios() is found. They need to be removed now from cell C5 and sub initVME updated to its latest form in iomod1e:

Page 98: vba11

How Excel Recalculates: Determinism (or lack of it.) 6

“Sub initVME() Dim ss As String Dim sAdd As String Dim lAdd As Long Dim iADDMod As Integer Dim dtime2waitperlongword As Double Dim dRet As Double iADDMod = Cells(2, 4).Value lAdd = 0 'Cells(3, 4).Value for the 61xx series this address is returned 'for the 406 series (ISA bus) it must be 0xd0000 Cells(5, 3).Value = VB_InitIOs(iADDMod, lAdd, True) Cells(3, 3).Value = "'" & Hex(lAdd) dtime2waitperlongword = Cells(4, 3).Value dRet = setdmawaitconst(dtime2waitperlongword) Cells(4, 4).Value = dRetEnd Sub”

Page 99: vba11

How Excel Recalculates: Determinism (or lack of it.) 7

• The last 3 lines are optional depending on if you are using the newer 6xx drivers (version 2) on NT. Comment them out for older drivers. If you leave them in will need to get and replace the latest io617nt.txt VBA module. The version 2 drivers and VBA code has the new (working) precision timing and allows setting a variable in the bntdv617 that controls how long to wait for each word on the DMA transfer in microseconds. It controls a minimum hold off before the Bit3 boardset is polled to determine if the DMA is done. If this is not there in some machines the DMA will be terminated if polling for the end of DMA occurs too soon. In the older drivers this is set too conservatively because there is a faulty Win32 API function QueryPerformanceFrequency() that fails on 2 out of 3 machines I’ve tested it on. It is supposed to return the Frequency but usually reports a constant that is way too slow. The version 2 drivers calibrate the frequency themselves via a call to the “Sleep(500)” milliseconds. This is only of interest if using DMA. Also the constant is not really a constant but a function of the length of the DMA. Thus I exported it to “tune” it for the best performance. This is not the most refined software but is available, robust and useable. See me and I will help you with your DMA application else comment it out and don’t worry! Examples of using this stuff are on d0server4\users\angstadt\xls_daniel\sasdmaNewBench2ntXPdmaErr_B6V3.xls (for saseq’s) or sasdmaNewBench2ntXPdmaErr_VRB__B6V3.xls for VRB/VRBC’s. Call me if you want to use this and I’ll get you started! Apologies for it not being so polished right now but it is the latest and greatest hot off the press. (It works; >9.1 billion times no DMA errors running around 6,000 DMA’s per second. (Small longword lengths.)

• The other thing that this latest sub initVME() subroutine does is to perhaps delay the onset of the familiar and infamous “*connect mapmem.sys failed*” message that eventually occurs under NT which to date is only guaranteed to be recoverable by leaving Excel and coming back in to and reloading the Excel file. (This problem occurs more frequently on sheets with “=VB_initVMEs()” in each sheet especially in the 2nd version of the driver with the delay in it. (Something to do with NT multi-threading and my lack of handling it?) It also it occurs if doing a lot of VB development and especially Ctrl-break to stop VB macro’s. If one remembers to run call initVMEs() after doing that chances of frustration are less. (Always look in C5 to see the “ok!” before continuing. If you don’t have that, the rest won’t work!)

Page 100: vba11

How Excel Recalculates: Determinism (or lack of it.) 8

• In what order do cells get calculated? Nominally in the books I’ve read it is supposed to be from top to bottom and from right to left. Organizing the logic of the sheet that way might (still) result in the fastest recalculation. With a logic analyzer in the crate and a worksheet that does a memory dump of VME functions with the functions in the sheet we can see the order that the cells calculate in! In the worksheet below columns D through K hold addresses all calculated from the address in C7 (from Left to right and Top to bottom) and L through S hold functions that read and display the value of that address. Thus L11 displays the value last read from the address at E11. Similarly M11 displays the value read from F11 and so on going across from left to right and then down. The highlighted area in blue are the values read and the other numbers are addresses that all reference back to the base in cell C7. When it is changed the whole sheet is recalculated including the functions that ultimately go through the Bit3 and read the VME addresses in the Worksheet.

Page 101: vba11

How Excel Recalculates: Determinism (or lack of it.) 9. A VME trace of the above worksheet!

******************** TRACE STARTED AT 10:41:16 04/01/04 ******************** CYCLES BEFORE TRIGGER = 000000 CYCLES AFTER TRIGGER = 000000=======!==================TRANSFER===========!==INTERRUPT=!======BUS======!=EXT FRAME !R ADDRESS DATA AM AS LW DS ER AK! REQ AK AK!GRANT REQ BY CR!XXXX !W 10 !7654321 IO!3210 3210 !4321 -------!-------------------------------------!------------!---------------!---- +TRIG !R 00008300 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000001!R 0000840E FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0111 1111 0 1!1111 +000002!R 0000840C FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0111 1111 0 1!1111 +000003!R 0000840A FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0111 1111 0 1!1111 +000004!R 00008408 FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0111 1111 0 1!1111 +000005!R 00008406 FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0111 1111 0 1!1111 +000006!R 00008404 FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0011 1111 0 1!1111 +000007!R 00008402 FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0011 1111 0 1!1111 +000008!R 00008400 FFFFFFFF 2D 0 1 00 0 1!1101111 1 0!0001 1111 0 1!1111 +000009!R 000083FE FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000010!R 000083FC FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000011!R 000083FA FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000012!R 000083F8 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000013!R 000083F6 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000014!R 000083F4 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000015!R 000083F2 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000016!R 000083F0 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000017!R 000083EE FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000018!R 000083EC FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000019!R 000083EA FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000020!R 000083E8 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000021!R 000083E6 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000022!R 000083E4 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000023!R 000083E2 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000024!R 000083E0 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000025!R 000083DE FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000026!R 000083DC FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000027!R 000083DA FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000028!R 000083D8 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000029!R 000083D6 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000030!R 000083D4 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000031!R 000083D2 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1011 1111 0 1!1111 +000032!R 000083D0 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000033!R 000083CE FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000034!R 000083CC FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000035!R 000083CA FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000036!R 000083C8 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000037!R 000083C6 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000038!R 000083C4 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1011 1111 0 1!1111

Page 102: vba11

How Excel Recalculates: Determinism (or lack of it.) 10. A VME trace of the above worksheet!

+000039!R 000083C2 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000040!R 000083C0 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000041!R 000083BE FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000042!R 000083BC FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000043!R 000083BA FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000044!R 000083B8 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000045!R 000083B6 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000046!R 000083B4 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000047!R 000083B2 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000048!R 000083B0 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000049!R 000083AE FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000050!R 000083AC FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!0111 1111 0 1!1111 +000051!R 000083AA FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000052!R 000083A8 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000053!R 000083A6 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000054!R 000083A4 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1011 1111 0 1!1111 +000055!R 000083A2 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000056!R 000083A0 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000057!R 0000839E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000058!R 0000839C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000059!R 0000839A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000060!R 00008398 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000061!R 00008396 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000062!R 00008394 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000063!R 00008392 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!0011 1111 0 1!1111 +000064!R 00008390 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000065!R 0000838E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000066!R 0000838C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000067!R 0000838A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000068!R 00008388 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000069!R 00008386 FFFF0056 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000070!R 00008384 FFFF72FF 2D 0 1 00 1 0!1101111 1 0!0111 1111 0 1!1111 +000071!R 00008382 FFFF0002 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000072!R 00008380 FFFFFF25 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000073!R 00008302 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000074!R 00008304 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000075!R 00008306 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000076!R 00008308 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000077!R 0000830A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000078!R 0000830C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000079!R 0000830E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000080!R 00008312 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000081!R 00008314 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000082!R 00008316 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000083!R 00008318 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111

Page 103: vba11

How Excel Recalculates: Determinism (or lack of it.) 11. A VME trace of the above worksheet!

+000084!R 0000831A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000085!R 0000831C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000086!R 0000831E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000087!R 00008322 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000088!R 00008324 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!1100 1111 0 1!1111 +000089!R 00008326 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000090!R 00008328 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000091!R 0000832A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000092!R 0000832C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000093!R 0000832E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000094!R 00008332 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!1000 1111 0 1!1111 +000095!R 00008334 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000096!R 00008336 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!1011 1111 0 1!1111 +000097!R 00008338 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000098!R 0000833A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000099!R 0000833C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!0011 1111 0 1!1111 +000100!R 0000833E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000101!R 00008342 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000102!R 00008344 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!1100 1111 0 1!1111 +000103!R 00008346 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!1011 1111 0 1!1111 +000104!R 00008348 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000105!R 0000834A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000106!R 0000834C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000107!R 0000834E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000108!R 00008352 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000109!R 00008354 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!0010 1111 0 1!1111 +000110!R 00008356 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!0011 1111 0 1!1111 +000111!R 00008358 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000112!R 0000835A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000113!R 0000835C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000114!R 0000835E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000115!R 00008362 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000116!R 00008364 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000117!R 00008366 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000118!R 00008368 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000119!R 0000836A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000120!R 0000836C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000121!R 0000836E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000122!R 00008372 FFFF76C8 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000123!R 00008374 FFFFFF87 2D 0 1 00 1 0!1101111 1 0!0100 1111 0 1!1111 +000124!R 00008376 FFFFF7FF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000125!R 00008378 FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000126!R 0000837A FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000127!R 0000837C FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111 +000128!R 0000837E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111

Page 104: vba11

How Excel Recalculates: Determinism (or lack of it.) 12. A VME trace of the above worksheet!

+000128!R 0000837E FFFFFFFF 2D 0 1 00 1 0!1101111 1 0!1111 1111 0 1!1111

+000129!R 00008310 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111

+000130!R 00008320 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111

+000131!R 00008330 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111

+000132!R 00008340 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111

+000133!R 00008350 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111

+000134!R 00008360 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111

+000135!R 00008370 FFFF0000 2D 0 1 00 1 0!1101111 1 0!0000 1111 0 1!1111 +000136! ********* NO TRACE DATA ********* ! ! !

+000137! ********* NO TRACE DATA ********* ! ! !

• One can see that all (8 & 17=136) cells got updated 1 time using this technique! As far as going from top to bottom and across as one reads it sort of does that a little mixed in with some sort of random stack operation?

• [If you are wondering why the addresses in the spreadsheet are 0xfc008xxx and the VME bus operation is only 0x00008xxx the reason is because this trace was done by using a different DLL, “d0server4\users\angstadt\ntutil\ntphysad\debug\ntphysad.dll”. This is a generalized PC physical address memory dump program. It was run after using the Bit3 program that set up the Bit3 registers to go to a HV module using address 0x2d (VME short I/O). The base address of the Bit3 is at 0xFC000000 on the PCI bus. Once the Bit3 is initialized to a particular VME address then the registers are set up so that any access to that address results in a VME operation. The base address of the High Voltage module is at 0x8300. Thus once the registers are set up then an access to 0xFC008300 causes the Bit3 to do the VME operation. (Bus mapping!). This is just a fun diagnostic. The Bit3 drivers do this (double) memory mapping automatically. In practice one would normally use those. This is an additional diagnostic. This is a 2 fer. If it is too confusing pretend the addresses in the spreadsheet say 0x00008xxx as they would with the VME Bit3 DDL because that is what is going on under the hood so to speak!]

Page 105: vba11

Whats wrong now? Why doesn’t this sheet work when I log in?Miscellaneous gotcha’s and dependencies.

• You’ve just logged into a different (new) test stand for the first time and the sheet that you just saw someone else using a minute ago doesn’t work for you!

1. Almost every single sheet depends on two excel functions that somewhere along the way got moved into an “Add-In”. This add-in must be enabled so the “Dec2hex()” and “Hex2dec()” functions in it give a result other than the “#NAME?” error. In Excel go to “Tools”, “Add-Ins” and make sure “Analysis ToolPak” is checked. This is the one you need but if you want you can click on “Analysis ToolPak-VBA” to be safe. Then click on “OK” and you are good to go. (This usually “sticks” on somehow. If it doesn’t do it again later.)

2. Macro’s are not enabled initially! In Excel go to “Tools”, “Macro”, “Security” and click on “Medium”. “Low” will work as well but there is of course more risk. It’s up to you. With “Medium” you know if there are macros in the sheet when you open it and it asks if you want to enable them for that sheet: You get a choice. With “Low” it doesn’t ask. They’re enabled. Probably not what you want if you do a lot of web surfing.

3. (If at any time during all of the above, the sheet you were interested in did actually come up and you saw the “#NAME?” error come in a zillion cells BE SURE NOT to SAVE at the “Do you want to save?” prompt that comes up before you leave or you may have trouble getting it all to recalculate correctly. If F9 doesn’t work click on one of the name cells and then on the check box next to the edit box at the top of the screen. Once one of them goes then (hopefully) Excel (may finally) see that function? You may have to repeat for every different function in the sheet! Usually once Excel sees one it wakes up and does the rest after F9 is pressed. If it doesn’t then try running the VB routine “allautoon” via “Tools”, “Macro”, “Macro…” list and then “Run”. This may have to be repeated for every function it couldn’t find. (Sometimes it’s easier to find a fresh copy on the server that hasn’t been saved with the “#NAME?” error all over the place.

Page 106: vba11

Whats wrong now? Why doesn’t this sheet work when I log in? Miscellaneous gotcha’s and dependencies. (2)

• If "*no vme access: no buff from mapmem.sys* “ occurs it means something pretty bad happened in mapmem.sys and/or it’s connection was broken. Saving any unsaved work if desired [note that for some reason some sheets may get stuck into and/or just go into “Design mode” especially if the Workbooks cannot be saved in “Design mode” you must exit “Design mode” before saving.] and leave Excel entirely and come back into it. This seems to mostly happen when stopping VBA code execution via Ctrl-Break (during VBA development.) I apologize for not being able to totally make this go away entirely. Although it is an annoying nuisance it has not been a show stopper. Recently I’ve found that not putting “VB_InitIOs()” in any of the cells seems to delay the onset of this considerably. See the discussion “How Excel Recalculates: determinism (or lack of it.) ..” especially slides 5 and 6 of this series probably around slide 80 and 81. Most VME errors are trapped but there are some goofy driver install (failure) conditions that I have not managed to trap. These are typically rare once really installed and working. Best is to call me if you are having troubles. (Also I will be happy to install the drivers on any new machine for you as some installations are tricky especially for the older Bit3 models. It is just better for all if I do it. I will be happy to do it. Just ask me! [Else it would take another 90 slides to try to explain it all!])

• If one gets a “*connect mapmem.sys failed*” message chances are that > 1 application is trying to connect to it. It has logic in it that tries to prevent more than one thread (one application and/or DLL) of talking to it at a time. Thus if the stand alone Window’s peek/poke application “Bnt617ex.exe” is running o.k. and then Excel is opened up with a worksheet to the Bit3 then when the Bit3 is inited in the worksheet it will give “*connect mapmem.sys failed*. The same thing will happen if two separate instances of Excel are started. However 2 worksheets in one instance of Excel will be accepted. (It’s just the way NT works and the drivers are written.)

Page 107: vba11

Whats wrong now? Why doesn’t this sheet work when I log in? Miscellaneous gotcha’s and dependencies. (3)

• If you get "*connect giveio.sys failed* “ and/or “ *connect mapmem.sys failed* “ then leave Excel and try again. If you still get it then for some reason one or both drivers is not installed and/or not running. If the drivers are installed correctly but not started (for some reason) they may be re-started via step 3 of:

http://d0server1.fnal.gov/users/angstadt/www/b3/b3_61x.htm

They should have also been installed as the Administrator account and not as your_account_with_admin_privileges especially if others besides your account intend to use it. The reason is I have seen it where the drivers were not “On” for when the (real) “Administrator” logged on and tried to use them. Unless the one and true “Administrator” installs the drivers they may not tree down through all the accounts. (This is also true of driver in NT in general. If a driver is installed in one account_with_admin_priveledges and then attempted to be removed from another_account_with_admin_privledges chances are they won’t really be removed all the way from the machine (no ownership privledges for example.) I’ve seen NT so messed up that the whole operating system had to be re-installed.

Page 108: vba11

Declaring Variables: Scope, Heap and the Stack• By a stack I mean a (simple) abstract data structure that in this case resides in some memory

that is set aside for VB(A) as a LIFO (Last In First Out). There are many “stacks” on every computer used for various things including the one that is used by your subroutines and functions when they are called from VB(A). If a function or sub is called with arguments, each one is pushed on the “call” stack. Other things may be pushed (and popped) on the stack as well including varous CPU registers automatically on your behalf, so calling a sub or function with 0 arguments almost always involves pushing and poping of some sort. On exit it pops them off the stack. On return additional “stack cleanup” may occur. The stack is in RAM and has a default size in C programs, Windows in general, and VB(A) of ~ 1Megabyte. For C this is a pretty hard limit though it can be changed with compile switches. For VBA they’ve fancied it up a little and allowed it to dynamically grow by managing it themselves and assigning additional memory to it via the “page fault” mechanism (VBA Developer’s Handbook, op cite, pg xxxiv.) More on this later. From the standpoint of performance this is NOT a good thing and can be avoided with a little thought. When they get a “page fault” they go get some more Memory (from some Heap somewhere) is basicaly added to the Stack. Thus the VB(A) stack apparently can grow and shrink dynamically as needed. As we all know growing (and shrinking) takes time.

• By “heap” I don’t me a jalopy in the parking lot. “The heap” is a bunch of memory assigned to a program to use as it desires. Programs that can use memory dynamically (Pascal, C, VB(A) all have some pool of memory that they use referred to as “the heap”. Typically in c and Pascal this is done with functions like new() and free() and using pointers. Though VB(A) has no explicit pointers it does facilitate a “heap” and it calls new() and free() for you on your behalf automagically “under the hood” for things like adding to the Stack as above. They are also called when you call the VB(A) “ReDim()” function to dynamically change the size of an array. Other things may get put on the heap for you such as variables declared in a function or subroutine. (Languages that support this automatic cleanup have some process running that does automatic “garbage collection”; when a variable goes out of scope “free()” is called for you!

• By scope I mean the visibility variable(s) have to other subroutines, functions and modules. A programmer controls the scope of a variable by where they declare it and how they declare it! Where and how a variable is declared determines in what part of memory it resides: the data segment, the stack, or the heap! It’s going to be in one of those 3 (abstract) places. All those three are in RAM but it’s which RAM it is in and how it is managed and the cost in time of the management of those data structures that I want to discuss!

Page 109: vba11

Declaring Variables: Scope, Heap and the Stack 2• Variables declared at the top of the module are available to any function or sub in the

entire module unless they are (members of a class and declared private.) They may be made global (public to all modules by declaring them as such with the “Global” qualifier. Things that are candidates for this are things that are used in almost every subroutine, a lot of subroutines, or finally enough subroutines so that passing them around as an argument becomes painful. Life is a tradeoff and this is no exception. If we were in school and this were an assignment then we would have to pass almost every thing into our routines as an argument or be graded down automatically. In at least once course our grade could be lowered for each variable we used in a subroutine that was not made into an argument! Around here we all flunked out years ago self included! But this is the real world and we have lines to write yesterday so sometimes we cut corners and don’t declare them as we should. But in general it is a good idea to use arguments as much as possible and is reasonable in subroutines. The reason to do it is because as the project grows one can run out of reasonable variable names. It then becomes very easy to get the variables mixed up and not type what you mean. So in general, if nothing else, passing things in as arguments does help make the code more manageable. This is a real good thing. (Until it becomes artificial or forced when you come into some old code where it’s already global and used all over the place one just gives up and goes with the flow (of less than perfect code).

• Another interesting thing is that if we are trying to write fast code, passing in things into a sub or function as an argument actually makes it run a little slower. Not a lot and it is truly hardly worth mentioning but it can be measured! Each argument on routine entry requires pushing it onto the stack on call and popping it off the stack on entry and then any stack cleanup on exit. So it’s probably ~2 instructions per argument per call. Also any variables in that subroutine must be created when the subroutine is running and destroyed when it exits. So if large temporary arrays are used in this subroutine and the subroutine is being called over and over in a loop then it would really be best to at a minimum make them “Static” (not created and destroyed each time and kept somewhere else: probably the “heap” or in the global data segment. The point is that not calling memory management routines saves time. Or one could make them global in scope or at least global to the module. The default heap in VB(A) is ~1Megabyte which is the same as other languages. However in VBA it will allocate (get) more memory from the heap dynamically if needed as “page faults” occur. You don’t want to be page faulting every loop!

Page 110: vba11

Declaring Variables: Scope, Heap and the Stack 3

• Which subroutine run’s faster and by how much? getsCalledaBillionTimes or getsCalledaBillionTimes2?:

“Option ExplicitDim dBigModuleAry(200000) As Double ‘module level global array. (could be made global)

Sub getsCalledaBillionTimes() Dim lIndex As Long Dim dBigAry(200000) As Double '8 bytes_per_double * 200,000=1,600,000 bytes! Stack is only 1Megabyte, 'page faults will occur so make static or move at least to module scope and/or make global! For lIndex = 1 To 200000 dBigAry(lIndex) = lIndex ‘local array Next lIndexEnd Sub

Sub getsCalledaBillionTimes2() Dim lIndex As Long For lIndex = 1 To 200000 dBigModuleAry(lIndex) = lIndex ‘module level global array. (could be global) Next lIndexEnd Sub “

Page 111: vba11

Declaring Variables: Scope, Heap and the Stack 4Using Precision modHiResTime the 2nd is ~2x faster!

(PIV running at 2.5 gigahertz on XP)

Sub aTest1() Dim dStart As Double Dim dEnd As Double Dim lSleepyTime As Long Dim dTimeCheck As Double Dim dPlace As Double Dim lnum2Ave As Long lnum2Ave = 1 lSleepyTime = 500 If dCPUfreq = 0# Then dPlace = getcpufreq(lnum2Ave, lSleepyTime,

dTicksPerMicroSec, dCPUfreq) End If dStart = getcputicks() Call getsCalledaBillionTimes dEnd = getcputicks() dTimeCheck = dEnd - dStart Cells(16, 16).Value = dTimeCheck Cells(16, 12).Value = " whole call took ticks=" dPlace = dTimeCheck / dTicksPerMicroSec Cells(17, 16).Value = dPlace Cells(17, 12).Value = " vb call + delay (microsec)="End Sub 'atest2

Sub aTest2() Dim dStart As Double Dim dEnd As Double Dim lSleepyTime As Long Dim dTimeCheck As Double Dim dPlace As Double Dim lnum2Ave As Long lnum2Ave = 1 lSleepyTime = 500 If dCPUfreq = 0# Then dPlace = getcpufreq(lnum2Ave, lSleepyTime,

dTicksPerMicroSec, dCPUfreq) End If dStart = getcputicks() Call getsCalledaBillionTimes2 dEnd = getcputicks() dTimeCheck = dEnd - dStart Cells(16, 16).Value = dTimeCheck Cells(16, 12).Value = " whole call took ticks=" dPlace = dTimeCheck / dTicksPerMicroSec Cells(17, 16).Value = dPlace Cells(17, 12).Value = " vb call + delay (microsec)="End Sub 'atest2

Page 112: vba11

Declaring Variables: Scope, Heap and the Stack 5

• Same goes for Redim(). Just don’t! (Especially in a loop!) You want to declare your big data arrays at the top of a module (possibly global if used across modules) bigger than the max biggest you will ever need. (Use Constants so you can change the max easily. Use all caps (or all except for the type if you want to use Hungarian notation) to remind yourself and others at a glance that it is a constant. (This is the C standard and it’s a good one!)

• I was at Si Det one day using an logic analyzer to trouble shoot some of my driver code and I kept seeing this ~40 millisecond (huge) delay in the trace. (Most things were happening in the microsecond range or perhaps a few milliseconds at most!) Eventually it was traced to a Redim() call in a loop every time! Around 40 milliseconds wasted every trigger (on a PII @ 400mhz on Win9x)! Yes it works but only as an example of bad design.

• Thank you for your attention. Hope this helped! Good luck & happy programming!

Page 113: vba11

Conclusion• Excel and Visual Basic for Applications are enabling technologies

providing excellent tools for rapid application development. Besides being relatively easy to use they are programmable and extensible. With present day fast processors present VME access speeds are approaching the hardware speed of ~2-3 microseconds per VME access from VBA with a 620 using my drivers under NT. If VBA is too slow to process data directly then function(s) can usually be moved to C and/or inline assembler. Together they can provide a complete solution in days or months instead of person years.

• Increased throughput has been obtained by using DMA, Direct Memory Access or if one prefers VME speak, Block Transfers. Other Windows schemes including Dynamic Data Exchange, DDE, and Object Linking and Embedding, OLE, are expected to be slower then the typical ~1 microsecond VBA DLL Call but were not directly investigated.

• This also makes available precision timing routines to make your own run time performance evaluations.