Tutorial: Using Reserved Words as VB6 Class Member Names
Title
VB6 Polymorphism as a Means of Using Reserved Words as Class Member Names in VB6
Description
On rare occasions you'll want to create a VB6 Class, UserControl, Form, etc. that has methods or properties that duplicate VB6 keywords that the IDE and compiler just won't allow. Some examples might be Open, Close, Stop, and many others.
The VB6 manuals (and the versions contained in the MSDN Library articles) cover the mechanism required in the sections on creating and using objects under inheritance and polymorphism. However they do not directly address what you need to do regarding these reserved words.
I haven't seen a decent writeup anywhere on this topic so I thought I might give it a go myself.
This tutorial is an attempt to provide an example of how you might do this, embedded in a working demo Project.
This is a 300 level tutorial, not for beginners. It assumes a good deal of knowledge, requires a Windows SDK of recent vintage, and touches your dev system's registry (though we'll try to clean that up too). I will try to keep things as simple as I can nonetheless.
Everything described may work just as well for VB5.
Author name
Bob Riemersma
System Requirements
Windows 2000 or newer (though even Win95 may be fine).
VB6 Professional or better installed.
A recent vintage Windows SDK installed (6.0 was used here).
Administrative rights on the system (which you really need for VB6 development anyway).
License info
Free for anyone to use or copy with attribution. No warranties or guarantees, etc. As-is information, use at your own risk.
Introduction
So why might you want to do this?
Well, sometimes it can just be annoying to have to name your "open" method OpenFile rather than Open but that generally isn't important enough for you to go to all this trouble.
But you might be creating a DLL for use in lots of projects, by others, etc. and it has a kind of raggedy look to have to decorate your class member names just so they'll compile.
Or in the case at hand (our guinea pig Project) I'm writing a small script host (using the MS Script Control) where I want to offer a few standard WSH objects and methods on them. In particular I want to implement a stripped down version of WScript.StdOut that has a Write method on it. "Write" is a VB reserved word so you can't normally use it as a method name.
Step 1: First Attempt
The Project is named RunScript. First Attempt is the name of a folder in the attached archive that attempts to limp along using xWrite instead of Write as we'd prefer to.
This means my StdOutSim.cls looks like this:
Code:
Option Explicit
'
'A partial WScript.StdOut object simulation.
'
Public Sub WriteLine(Optional ByVal Text As String = "")
xWrite Text
xWrite vbNewLine
End Sub
Public Sub xWrite(ByVal Text As String)
UIForm.txtOut.SelText = Text
End Sub
But it also means my test script GraphSin.vbs must look like:
Code:
Sub Plot(ByVal X, ByVal Y)
Dim Line
Dim Pos
Line = Spaces & "|" & Spaces
Pos = 1 + (Y + 1) * Scale
Line = Left(Line, Pos - 1) & "*" & Mid(Line, Pos + 1)
With WScript.StdOut
.xWrite FormatSingle(X)
.xWrite " -"
.xWrite Line
.xWrite "+"
.WriteLine FormatSingle(Y)
End With
End Sub
Bleh. Can we do better?
Step 2: Make a DLL
Make a DLL is the name of the next folder we have. It is used to create a DLL containing the classes we want to use reserved words in, prefixed "I-" (for interface). In my case I only need to tweak StdOutSim.cls so that's all I'll include.
The steps involved here are:
Make a new VB6 ActiveX DLL Project.
Change the Project name to RunScriptInterfaces.
Remove the unneeded auto-created Class1.cls that we don't want.
Copy StdOutSim.cls into this folder as IStdOutSim.cls.
Use "Add file..." to add this class to our DLL Project.
Rename the class from StdOutSim to IStdOutSim.
Change the class Instancing from Private to MultiUse.
Edit StdOutSim as shown below:
Code:
Option Explicit
'
'A partial WScript.StdOut object simulation interface template.
'
Public Sub WriteLine(Optional ByVal Text As String = "")
'Code not needed or wanted here:
' xWrite Text
' xWrite vbNewLine
End Sub
Public Sub xWrite(ByVal Text As String)
'Code not needed or wanted here:
' UIForm.txtOut.SelText = Text
End Sub
The attached archive has this folder and these Project files already made.
Then we can go ahead and compile our DLL. After that go ahead and delete leftovers like .LIB and .EXP files.
Finally, drag and drop the new RunScriptInterfaces.dll's icon onto the icon of the provided DLLUnreg.vbs script. This will unregister our DLL since we don't need it - just a little cleanup.
Step 3: Extract the IDL
No archive folder for this step, but we'll use the one from the next step.
Now we'll need to get the type library info from our DLL in a useful form so we can edit it and ultimately compile it into a standalone typelib (TLB) file.
One easy way is to use the OLE View utility, either from your VS 6 Tools Start Menu shortcut or you can use the version from your Windows SDK's Bin folder. Run this and then open your DLL via View TypeLib...:
This should open a second window with the generated IDL code in the right pane.
Don't bother trying to use the Save menu there, it creates a broken Unicode text file that is useless. It still does this even using the SDK 7.0 version of OLE View so you can see it doesn't get much love from Microsoft.
Instead open Notepad, then click in the right pane where the generated IDL is, press Ctrl-A to select all text, Ctrl-C to copy it, and then paste that into Notepad.
Finally close both OLE View windows (we're done) and save the Notepad file as a Orig RunScriptInterfaces.odl with ANSI encoding into the next folder...
Step 4: Modify the IDL
This folder (from the attached archive) comes with these files in it:
Annotated ODL.rtf - An RTF file showing my original ODL (IDL) with replaced and deleted lines struck out in red and new/changed lines in blue. A helpful "how to" guide for editing your DLL's ODL file to make a new interface ODL source.
Orig RunScriptInterfaces.odl - This was my original ODL before editing. Save yours over it.
RunMidl.cmd - A batch file we'll use to compile your edited ODL source.
RunScriptInterfaces.odl - My final ODL after editing.
SDK Files Needed.txt - A short text document listing the files you will want to copy from the Windows SDK into a handy Make TLib folder for easy running of the MIDL compiler.
So you overwrote Orig RunScriptInterfaces.odl here in the previous step. Now you need to edit it and save the edited version as RunScriptInterfaces.odl (reaplcing the one I provided) and you can just use Notepad for this.
The .RTF document shows what must change, but there are three GUID values you need to recreate with new values yourself. If your interfaces TLB will have more classes in it (we just have the one in this simple tutorial) you'll have more GUIDs to replace.
In theory we can use the ones created originally by the VB6 compiler since we've unregisrtered them. But to be thorough go ahead and use a GUID generator to make fresh ones. I tend to just use GuidGen.exe from the SDK's Bin folder.
Step 5: Make TLib
Create this folder yourself. It can be handy for future projects, so maybe put it someplace among your stored VB6 project folders or somewhere.
Follow the directions in SDK Files Needed.txt about the files to copy here from your Windows SDK.
Copy the RunMidl.cmd file into here from the archive's Modify the IDL folder. Then move your edited and saved RunScriptInterfaces.odl file here as well.
Drag and dropRunScriptInterfaces.odl's icon onto RunMidl.cmd's icon in an Explorer window. This is an easy way to accomplish the MIDL compiler run.
This should produce two new files if all goes well:
log.txt - The MIDL compiler messages, errors if any, etc.
RunScriptInterfaces.tlb - Your new interfaces type library, ready for use.
Step 6: Second Attempt
Here we'll take our first approximation and create a new and improved (Now with Write!) version of RunScript.
Copy the original Project files into a new folder. Second Attempt from the archive already has this done.
Make a few changes...
First we need to reference our new TLB. We can do this via "Project|References..." in the IDE, where we'll use the Browse button:
Then we need to modify our StdOutSim class:
Code:
Option Explicit
'
'A partial WScript.StdOut object simulation.
'
'We'll implement the "abstract class" IStdOutSim here:
Implements RunScriptInterfaces.IStdOutSim
Private Sub IStdOutSim_Write(ByVal Text As String)
UIForm.txtOut.SelText = Text
End Sub
Private Sub IStdOutSim_WriteLine(Optional ByVal Text As String = "")
IStdOutSim_Write Text
IStdOutSim_Write vbNewLine
End Sub
Getting those "event handler like" interface proxy method signatures is easy, and much like getting them for more usual event handlers once we've added the Implements... line to the class:
Then we'll need to modify the WScriptSim class to use our new interface on our class:
Code:
Option Explicit
'
'Global Object class, an instance of which is added to the
'Script Control as global object "WScript". This is how our
'scripts interact with the host.
'
'A partial WScript object simulation.
'
'Private WshStdOut As StdOutSim becomes:
Private WshStdOut As RunScriptInterfaces.IStdOutSim
'Public Property Get StdOut() As StdOutSim becomes:
Public Property Get StdOut() As RunScriptInterfaces.IStdOutSim
Set StdOut = WshStdOut
End Property
Public Function CreateObject(ByVal ProgId As String, Optional ByVal Prefix As String) As Object
'We ignore Prefix in this implementation.
Set CreateObject = VBA.CreateObject(ProgId)
End Function
Public Sub Echo(ParamArray Text())
WshStdOut.WriteLine Join$(Text, vbNewLine)
End Sub
Public Sub Quit(Optional ByVal Status As Long)
Err.Raise gINTERNAL_ERROR 'Internal error, catch this in Script Control Error event!
End Sub
Private Sub Class_Initialize()
Set WshStdOut = New StdOutSim 'But we create an instance of the "concrete class."
End Sub
Now we can go ahead and compile our new program.
But before running it we'll need to update our script:
Code:
Sub Plot(ByVal X, ByVal Y)
Dim Line
Dim Pos
Line = Spaces & "|" & Spaces
Pos = 1 + (Y + 1) * Scale
Line = Left(Line, Pos - 1) & "*" & Mid(Line, Pos + 1)
With WScript.StdOut
.Write FormatSingle(X)
.Write " -"
.Write Line
.Write "+"
.WriteLine FormatSingle(Y)
End With
End Sub
Cleanup
By adding the reference to our TLB we caused VB6 to register it. We don't need the crud left in the registry, and it seems that the VB6 compiler is quite happy to continue using it even after unregistering it! Perhaps this only works if the TLB is "next to" the VBP file or something, but it seems to work fine.
To unregister the typelib you can drag and dropRunScriptInterfaces.tlb's icon onto the icon of the provided TLBUnreg.vbs (provided in the archive's Second Attempt folder).
Last Word
This works great for many VB6 reserved words. However one that is particularly stubborn is the Print pseudo-method. The compiler handles this and a few others as special cases.
That doesn't mean you can't do this with Print, but you won't be able to invoke it from within a VB6 program. It still works for a script via Script Control (VBScript doesn't care about "Print" as a reserved word), or it works compiled into a DLL that some other language will use. But for VB6 it just isn't going to happen as far as I can tell.
At best you might get away with syntax such as this:
Code:
fudd.[Print] arg1, arg2, arg3
... assuming an object fudd of a class you have created a Print method on via this polymorphism trick.
I hope this information helps someone. As I said, I haven't seen it written up before even though a lot of us know about it.
Last edited by dilettante; Nov 29th, 2019 at 04:00 PM.