Microsoft makes development tools for several languages, including C and BASIC, and also makes the Windows operating system containing many functions that can be useful to any program. Microsoft operating system functions traditionally required a program to have quite a bit of control over memory. They were typically designed to be used with programs written in the C language, and required a program to be able to pass the address of memory (ie, a C pointer), and allocate blocks of specially formatted memory (ie, a C structure), and when passed a string, that string was usually required to be in the format that a C compiler internally creates (but which may not be used for strings in other languages. For example, REXX's strings are not internally formatted the way that a C string is).
It became very troublesome for Microsoft to support the numerous languages, each with its own limitations and way of doing things. It especially became troublesome to support Visual BASIC, since VB couldn't call the multitude of Windows operating system functions without a lot of extra support created for VB. So, instead of continuing to write operating system functions that needed structures and pointers and C-style strings and other similiar things supported by only certain languages, Microsoft decided to come up with a new interface to its operating system functions.
Microsoft called this new interface Common Object Model (COM). Then Microsoft began creating new operating system DLLs that used this new interface, for example, DirectX has numerous COM interfaces inside of it. Microsoft decided to say that these new DLLs contained "ActiveX objects". So, ActiveX objects use COM as their programming interface. And when your script uses them, your script therefore uses COM too. Now, a Visual BASIC script could use these new operating system functions just like a C program.
Then Microsoft got thinking that it would be good to update its software such as Word, Excel, etc, so that these programs could use COM for their "macro interface". In this way, a Word or Excel macro can be written in any of those COM capable languages. Typically, you write a macro to automate some repetitive task. So Microsoft called it Automation when you write a script that controls some other program's operation. (Gotta love these programmers and their time-wasting hobby of inventing new words to describe things already in existence).
Automation allows one application (such as a REXX interpreter running a REXX script) to work with another application's objects (internal data and functions).
If your REXX interpreter is itself a COM object (specifically an ActiveX script engine), then it's possible for software such as Excel/Word/etc to start up the REXX interpreter and launch your script that, in turn, works with Excel/Word/etc objects. So, to the enduser, it would look like REXX script support was built right into Excel/Word/etc. After all, the enduser would be able to write a REXX script that modifies Excel/Word/etc data/documents, and launch that script right from Excel/Word/etc.
Note: At this time, Reginald is not an ActiveX Script Engine. This means that, although you can use Reginald to run scripts that work with Excel/Word/etc documents, you must start up your script via Reginald's Script Launcher, or Rexx Programmer Center.
To create an instance of some COM object, you must call either CreateComObject, or GetObject. Use CreateComObject if you wish to create a COM object that isn't yet running, for example, if you want to start up a new instance of Microsoft Excel. Use GetObject if you want to utilize a COM object that is already running, for example, if you wish to attach to an already open Excel application.
CreateComObject is passed a Product ID (ProdID). This is simply some string that uniquely identifies the COM object. You must know the ProdID of the COM object you wish to use. (This is usually supplied with the documentation for the COM object). For example, Microsoft Excel has a ProdID of "Excel.Application".
You must assign the return value of CreateComObject or GetObject to some variable of your choice. If all goes well, this variable will hold an instance of that COM object. If there's an error, a SYNTAX condition will be raised.
For example, here we open up Excel, and assign its COM object to our variable named "objExcel":
objExcel = CreateComObject("Excel.Application")
For GetObject, the second arg (not the first arg - like with CreateComObject) is the ProdID. For example, here we open up Excel, and assign its COM object to our variable named "objExcel":
objExcel = GetObject(, "Excel.Application")After you have created a COM object, you use the variable just like you would any other object variable to call its functions. For example, assume that Excel has a function called MyFunction, which is passed one argument, and returns a value. Here we call MyFunction using our objExcel variable, passing the string "My argument", and storing the return in a variable named MyVar.
MyVar = objExcel~MyFunction("My argument")If a COM object has a "property" (ie, some internal variable), then you can set its value, or retrieve its value with a simple assignment. For example, assume the Excel object has a property called "Visible". Here we get the current value of the Visible property, and then set it to the value 1:
origValue = objExcel~Visible objExcel~Visible = 1When you're done with a COM object and do not need it anymore, simply DROP the variable holding the object:
DROP objExcel
Reginald's COM support is rather similiar to VBscript, and therefore, somewhat similiar to Visual BASIC.
Note: Reginald REXX (like VBscript) does not support "early-binding". If you have a Visual BASIC (ie, not VBscript) example that uses early-binding, it should first be rewritten to use "late binding".
Let's take the following Visual BASIC example:
Dim objExcel as Object Set objExcel = CreateObject("Excel.Application") objExcel.Workbooks.AddThe first line declares a variable named objExcel, and says its datatype is a COM object. With REXX, you do not need to declare a variable's type before you use it, so we can eliminate this line. All we need to know is that the variable objExcel will indeed be used to hold a COM object.
The second line contains a call to CreateObject. This is the same as Reginald's CreateComObject function. It creates an instance of the COM object whose ProdID you pass to CreateObject. It then stores this object in the variable you assign to. (Above, that's the variable "objExcel"). In Visual BASIC, this line begins with a SET keyword. But for Reginald, simply delete the SET keyword, and change the function name to CreateComObject.
In Visual BASIC, you use dots to separate the names of objects (form a function name or property). In Reginald, you use a ~ character. Note the line:
objExcel.Workbooks.AddFirst of all, Add is a function, not a property. So, there should be parentheses after that function name. Visual BASIC allows you to omit the parentheses after a function name. It's similiar to using Reginald's CALL keyword:
CALL objExcel.Workbooks.AddWe could use the above, but I think that a more explicit way to indicate this is a function call is by showing the parentheses (and you can omit the CALL keyword with Reginald Lite):
objExcel.Workbooks.Add()Secondly, remember when using a COM object, you must replace the dots with a ~ character. You may be tempted to change it to:
objExcel~Workbooks~Add()What the above is saying is I want to call the Add function in the Workbooks object. And by the way, the Workbooks object is gotten from my Excel object". But Reginald doesn't support using more than one ~ to reference a subobject. So what you need to do is first get the Workbooks object from the Excel object and assign it to a variable. And then you can use this new object variable to call its Add function. So here's our complete script:
/* Get an Excel object from a new instance of the Excel * application, and store it in the variable named "objExcel". */ objExcel = CreateComObject("Excel.Application") /* Get a Workbooks object from my Excel object, and store * it in the variable named "objWorkbook". */ objWorkbook = objExcel~Workbooks /* Call the Workbooks object's Add() function. */ objWorkbook~Add() /* If we no longer need the Workbooks object, DROP it. */ DROP objWorkbook /* If we no longer need the Excel object, DROP it. */ DROP objExcelNote: Reginald automatically DROPs all objects when your script terminates, so the above DROP instructions aren't needed if the script is ending anyway.
Let's take another VBscript example. Here's one that gets the Windows shell object (ie, the part of windows that runs programs), and tells it to run Notepad.exe.
Set WshShell = CreateObject("Wscript.Shell") WshShell.Run("NotePad.exe")Whereas Visual BASIC needed a DIM statement to declare a variable to hold the object, VBscript (like Reginald) doesn't need that. So there is no DIM statement above. The first statement passes the ProdID for the Windows shell (ie, "Wscript.shell") to CreateObject. This runs a new shell instance. The above statement assigns the returned object to the variable WshShell.
In the second line, the Shell object's Run function is called, and passed the name of an executable to run.
To adapt this VBscript for Reginald, we first get rid of the SET keyword, and replace CreateObject with CreateComObject. Then we replace the dot after the object variable name with a ~ character:
/* Get an Shell object from a new instance of the Windows * shell, and store it in the variable named "WshShell". */ WshShell = CreateComObject("Wscript.Shell") /* Call the Shell object's Run() function to run Notepad. */ WshShell~Run("NotePad.exe") /* Since this script is ending, WshShell will be automaticall DROP'ed. */Sometimes, an object will have something known as a "Collection". This is simply a group of items, such as a group of values. For example, a REXX stem variable could be thought of as a collection. And all of the items in that collection would be all of the tail names that use that stem. For example, if we have a stem named MyStem., we would use the DO OVER loop to enumerate all of the items (tail names) in this "collection":
DO i OVER MyStem. SAY i ENDVBscript and Visual BASIC use a FOR EACH statement to do something similiar to Reginald's DO OVER. Let's assume that we have some object we've stored in a variable named "MyVar". And we want to enumerate (and display) each item in the collection. In VBscript, it would look like this:
for each MyItem in MyVar MessageBox MyItem NextEach time through the above loop, the variable named MyItem is set to the next item enumerated (and it is displayed). Here's the same thing with Reginald's DO OVER:
DO MyItem OVER MyVar SAY MyItem ENDIn conclusion, you can replace a FOR EACH statement with a DO OVER statement. (And replace the NEXT statement with an END statement).
Let's take the following VBscript example:
set locator = CreateObject("WBEMScripting.SWBEMLocator") set wmi = locator.ConnectServer() set Properties = wmi.Get("Win32_VideoController") for each oProp in Properties.Instances_ WScript.Echo(oProp.Name) nextThe first line passes the ProdID for the WBEMScripting component to CreateObject to create a running instance of its object. This is assigned to the variable named "locator".
Next, the WBEMScripting object's ConnectServer() function is called to get another object. This object is stored in the variable named "wmi". (Why don't I call CreateObject to get this wmi object too?" you may ask. Because CreateObject gets only a "master object", and typically sub-objects are gotten by calling some function in the master object. That's just the way most COM objects work).
Next, the wmi object's Get() function is called. It is passed a string for some type of object to get. Here, it's an object that gives information about the video cards in the system. This object is stored in the variable "Properties".
So far, we have 3 COM objects (which we stored in 3 variables).
It just so happens that there can be more than 1 video card in a system. In other words, there can be a "collection" of video cards. And the Properties object lets you enumerate all the video cards in this collection. So a FOR EACH loop is done on the Properties object. Actually the FOR EACH loop is done on another object gotten by calling the Properties object's Instances_() function. So there's really 4 objects at work here.
And what is each item returned? Why, it just so happens to be another (fifth) object. And this fifth object contains information about one video card. It just so happens to have a Name property that is the name of the video card.
Let's translate this VBscript to Reginald. The first line is easy. Remove the SET keyword, and replace CreateObject with CreateComObject.
The next two lines are also easy. Remove SET, and replace any dot after an object variable name with a '~'.
Now we have the FOR EACH, which will be replaced with a DO OVER. And we have to replace the Properties.Instances_ with Properties~Instances_(). But there's one problem here. The DO OVER instruction can deal only with an object variable name, not a call to an object's function. So we'll break this up into two lines. The first line will call Properties~Instances_() to stuff this fourth object into a variable we'll name Enum. Then, we'll use this Enum object variable in our DO OVER statement.
The next line simply displays the oProp object's Name property. We'll use a SAY instruction to do the same.
The last line (Next) is replaced with END.
So here's our final script:
locator = CreateComObject("WBEMScripting.SWBEMLocator") wmi = locator~ConnectServer() Properties = wmi~Get("Win32_VideoController") Enum = Properties~Instances_() DO oProp OVER Enum SAY oProp~Name END