October 1996
The Visual Programmer
Joshua Trupin Joshua Trupin is technical editor of Microsoft Interactive Developer magazine (MIND). He was formerly a software developer at Hyperion Software. It's been a couple of months since the last Visual Programmer column, and there's a good reason-I've recently assumed the role of technical editor at Microsoft Interactive Developer magazine (MIND), the sister magazine to MSJ. So if you've written to me and I haven't sent you a reply, it's not because I'm ignoring you. It's because I'm busy and I'm ignoring you. But I have been reading the mail that's come in. One of the things that people are quickly becoming more interested in is the level of difficulty it will take to get an existing Visual Basic¨ program to work as an applet in VBScript. It sounds like it should be pretty easy, and yet there's a nagging feeling that it's going to be dreadfully hard. For this month's column, I took a sample program I wrote several years ago-a talking clock I coded in Visual Basic 2.0-and converted it to VBScript. As it turns out, the experience landed squarely between facile and onerous. Most of VBScript performed just like I expected, but a few gotchas I encountered were very informative, and could help you get past them quickly. Originally, the MSJ talking clock consisted of two control components, a hidden timer, and a button. The timer was set to trigger an event every 1000 milliseconds (1 second). The Timer event code simply set the button caption to the current time. That was the easy part. Making it talk was somewhat more complex. Through the Windows¨ 3.1 sndPlaySound API call you can tell a machine to play a WAV file, so this was added to the project as a Declare. I then recorded the sound files needed for every possible time combination. This list turned out to be the numbers 1-19, 20, 30, 40, and 50. (The program didn't need a spoken zero, for instance, because you don't say, "It's five-zero-zero" in common usage, you say, "It's five o'clock." The program did need an "oh" for times like "It's five-oh-three.") A few extra sounds were also recorded for the clock: "it's," "and," "o'clock," "second," and "seconds." Other numbers, such as 43, were made by combining two WAV files. The resulting files provide rich monaural sound for the application. Whenever a user clicked the time button, the program got the current time, parsed out hours, minutes, and seconds to determine what sounds had to be played, then played them. As a whole, the program is relatively simple. Figure 1 shows what the project looks like converted to Visual Basic 4.0; its code appears in Figure 2. (A very small side note-Visual Basic 1.0 programs can't be read into the Visual Basic 4.0 IDE. If you want to convert them, you have to open them in either version 2.0 or 3.0, save them, then reopen them in 4.0. This is only a problem if you have really old programs or download old samples from somewhere.) Figure 1 Under no stretch of the imagination is converting a Visual Basic 4.0 application to VBScript as easy as copying, pasting, and running. For one, you have to do a bit of work to get your controls onto a form correctly. The Microsoft ActiveXª Layout Pad will make this a great deal easier, but I did it by hand. This is a tedious process that involves finding each control's CLSID and adding it to an <OBJECT> tag in HTML. I used the OLE2VW32 program included with Microsoft Visual C++¨. None of the controls that you're used to in Visual Basic are intrinsically available to VBScript in HTML. Of course, the program needs a timer control and a button on it. Microsoft Forms 2.0 contains a number of controls matching the ones you get with Visual Basic, but there's no timer to be found. Eventually, I found one called IETimer (more on this later). The second roadblock is the lack of Declares in VBScript. This is a necessary bow to the idea of "sandboxing"-containing a browser script in a virtual machine so that it can't do any malicious damage to the operating system. Declares, obviously, allow you to call almost any procedure contained in any DLL on a system, including ones in the Win32¨ API. Since scripts aren't syntax-checked until they're actually run, a browser has no way of knowing whether a Declare is going to be used in any particular script. ActiveX controls, however, are supported by VBScript (and by any language following the ActiveX Scripting mechanism). Just as I've demonstrated in the past how to wrap system functions with OLE controls to make them accessible to Visual Basic, so you can also wrap functions in ActiveX controls for the sake of VBScript. You're probably saying to yourself, "wait-doesn't that make the script potentially unsafe again?" In theory it does, but there isn't much you can do without getting to the system at some point, including playing sounds. ActiveX controls can be digitally signed by their creator to ensure security, and a compliant browser will notify a user when a component is about to be installed. These steps make it far more difficult to sneak something onto a user's machine with ill intent. If you wrap safe APIs, the control can still be safe. Why am I telling you about all this? The talking clock needs a mechanism to play the WAV files that provide its compelling functionality. Since the sndPlaySound (or better, the Win32 API PlaySound) API call can't be used directly in VBScript, I wrote a simple ActiveX control that, when installed, plays a WAV file that exists in the user's Windows default directory. This process was extremely easy. I opened Visual C++ 4.1, created a new ActiveX control, and added a single method to it: PlaySound. Ctrl.PlaySound takes a WAV filename as an argument and calls the Win32 API PlaySound to play it synchronously (otherwise, the sounds might not be played in order). I also changed the OnDraw member function of the control so that it always makes sure that it's hidden on the form (using ShowWindow(FALSE)). I compiled to generate the control, noted its CLSID from the REG file Visual C++ created, and got out. The source for PLAYSND.OCX is shown in Figure 3. To create the Web page, I manually entered three <OBJECT> tags, one each for the Timer, Button, and Playsnd controls. Let's look at the Timer control tag as an example. In a couple of years you'll look back at this and laugh that you had to do any of it, but let me explain anyway. The first argument, CLASSID, contains the unique CLSID identifier for the IETimer control. If a browser is able to match up this value with the list in the registry and it can find the control installed on the user's machine, it doesn't need to retrieve it. The second argument, CODEBASE, indicates where this component can be found if it's not properly installed. I found the IETimer while poking around on www.microsoft.com. Use this only as an example; the timer control has now been added to the IE 3.0 distribution package. (You don't need to supply a component locally if it's available elsewhere-the ActiveX component download mechanism lets you use parts distributed anywhere that's reachable by URL.) A version number is also added so that a browser can update controls if necessary. The third argument, ID, indicates this control's name as referenced in any VBScript code on the page. ALIGN indicates where the control should be placed on the page; it's not important for a hidden control like this one. Two <param> tags follow this <OBJECT> tag. They provide persistent startup values for properties of the control. In this case, the IETimer control fires a Time event every 100 milliseconds (1/10 second), and it's enabled-the Time events will be generated normally. This is all closed with the </OBJECT> tag. I've also added my WavCtrl control to the page as WavCtl1; any VBScript code that says will play the BITEME.WAV file if it's installed in the user's Windows directory. Actually getting the proper WAV files downloaded is possible, again, through the ActiveX component download specification. Without explaining this in too much depth (Mary Kirtland wrote an entire article about the process in the July 1996 MSJ), the WAV files can all be compressed into a single CAB file, then a CODEBASE tag can reference where this CAB can be found. If they're not on the user's machine already, the CAB is automatically downloaded and its contents are installed to the proper places. If a file has multiple dependencies (for instance, a control that needs an associated DLL to function properly) an INF file can be referenced within a CODEBASE tag. INF files let you set up dependencies in a convenient INI-file-type format, and they can in turn reference CAB files where the files can be found. Now that I have the HTML page with the controls I need on it, it's time to activate them. VBScript can, for the most part, handle Visual Basic functions you've designed elsewhere. However, there are a few important differences in addition to the lack of Declares. The most important is that VBScript is a typeless language-everything's a variant. Variables like Strg$ aren't allowed because there are no format specifiers in the language. Strg is used as a variant, and it'll be coerced to a string type when needed. A big problem I found was the lack of a few commands like Format. In my original code, I used Format(n, "0") when I parsed a time string. This converted the minute text in "7:09" to a single nine so I could then play 9.WAV instead of 09.WAV. Fortunately, the talking clock only used Format to convert down (from "09" to "9") and not up ("9" to "09"). Since variants rule in VBScript, a statement like called when Mins="09", converts Mins to 9, adds 0, converts it back to "9", and appends ".wav" to it. This obviated the need for Format in my code. The result is a shorter, if somewhat more annoying, script. (It's annoying for people like me who are also versed in a language like C++ where, if you try to pass an LPSTR to a function expecting a LPCSTR, the compiler will reject it as unconvertable. Come to think of it, that's even more annoying.) VBScript procedures (as well as those written in any language) are wrapped as a block with the <SCRIPT> tag. A browser can tell what code interpreter to use by checking the language argument; ActiveX scripting can find the proper DLL to interpret any supported language. (For an explanation of how this happens, see my piece on Microsoft Internet Explorer 3.0 in the September 1996 issue of MIND.) Event procedures are named the same way as in Visual Basic. Here's the main code for creating a working (if mute) clock on a page containing an IETimer named Timer1 and a button named cmdbtn: The rest of the code exists in a subroutine named cmdbtn_Click. Triggered whenever the button is clicked on the form, it gets the current time with the aptly named Time intrinsic function. It then parses the string and plays the appropriate WAV files. Cmdbtn_Click was copied almost exactly from the original Visual Basic form, although the changes above were made to it. Looking back at it, I also realized I had been an idiot to waste my time parsing a time string by searching for colons within it. Instead, I simplified the code greatly by using the built-in Hour, Minute, and Second VBScript functions. After adding a bit of sparkle to the form, the complete HTML listing for the page is shown in Figure 4. And, if you act right now, I'll throw in-for free-the attractive screen shot of the application working in Figure 5! Figure 5 Talking Clock Obviously, this is a rough guide to the Visual Basic-to-VBScript conversion process. There's a lot more you can do with VBScript when combined judiciously with ActiveX controls. You can pretty much do anything from stupid little talking clocks to full-blown conferencing apps built on controls that provide WinSock support. In previous columns, I've shown how you can make Visual Basic 4.0 do anything you want if you're willing to put in just a bit of work to create a purpose-built OLE control. After converting my first program to VBScript, I've found that the same thing holds true. There's almost nothing you can't do with VBScript-provided you're willing to invest a couple of minutes creating an ActiveX control that will plug in the minor functional holes you might encounter along the way. Have a question about programming in Visual Basic, Visual FoxPro, Access, Office, or stuff like that? Send your questions via email to Joshua Trupin: joshuat@microsoft.com. VBScripting Your Existing App
The Original Program
VBScript Considerations
Creating the HTML
<OBJECT
CLASSID="clsid:59ccb4a0-727d-11cf-ac36-00aa00a47dd2"
CODEBASE="http://www.microsoft.com/ie/download/activex/ietimer.ocx#version=4,70,0,1086"
id=Timer1
align=middle
>
<param name="TimeOut" value="100">
<param name="Enabled" value="True">
</OBJECT>
WavCtl1.PlaySound "BITEME.WAV"
Language Differences
WavCtl1.Play (Mins + 0) & ".wav"
<SCRIPT language="VBScript">
Sub Timer1_Timer
' This is all you need to create a working clock
' with VBScript
cmdbtn.caption = Time
End Sub
</SCRIPT>