WordBasic Migration to Visual Basic

Microsoft Corporation

November 4, 1996

Abstract

Microsoft has integrated WordBasic migration to Microsoft® Visual Basic® as transparently as possible. When a template file is opened, either directly or indirectly (attached templates are an example of indirect) the WordBasic code is converted to Visual Basic automatically. In most cases the user doesn't even know that a conversion process happened. Templates, wizards, and add-ins: load, convert and run without prompts. This paper explains the conversion process, many of the known conversion issues, and solutions to resolve conversion issues.

Changes in Terminology

Macro: Any valid entry-point for code. In WordBasic, a macro is a module with a Sub Main entry point. In Microsoft Word 97, a macro is any public subroutine that does not take parameters. Word 97 still uses the Sub Main entry point for backward compatibility. However, using public subroutines is the preferred entry point. Only valid entry points will show up in Word's Macros dialog and can be assigned to command bars.

Add-in: To be consistent with other Office applications, global templates are now called add-ins. An add-in is any template that is global in scope. Word add-in libraries (.wll files) are still considered add-ins as well. To see which add-ins are currently loaded, click Templates and Add-Ins on the Tools menu.

Procedure: Either a subroutine or function. Used to simplify documentation when the distinction between subroutine and function does not matter.

Conversion Process

When a template that contains WordBasic code is opened in Word 97, the WordBasic code is automatically converted into Visual Basic code. This is done as soon as the template is opened. If the template is saved in Word 97 format, the WordBasic code is removed. This process cannot be undone by saving the file back as Word versions 6.0 or 7.0 format. Saving the file in a previous file format removes the Visual Basic code.

Work spaces that use both Word 97 and Word 6.0/7.0 must use duplicate templates or Word 6.0/7.0 version templates. Using the duplicate templates option means using two templates, one saved in Word 97 format and the other in Word 6.0/7.0 format. Keeping templates in the old file format will require users of Word 97 to wait for the conversion process to complete every time a template file is opened (strongly discouraged).

There is no WordBasic interpreter in Word 97. All WordBasic statements are methods of the WordBasic object in the Word object model. For those who are familiar with the WordBasic Automation object that was used to drive Word 6.0 and 7.0, this is basically the same concept, except that the code is executed within Word.

First let's examine successful translation of WordBasic to Visual Basic. The following code shows WordBasic syntax prior to conversion:

Dim Shared MySharedVariable$
Sub MAIN
   'Simple variable assignment.
   MyVariable$ = "Hello World!"
   'Display a message box using the variable.
   MsgBox MyVariable$
   'Illustrate using some simple WordBasic statements.
   'Create a new file
   FileNewDefault
   'Call the below function (very simple)
   MySharedVariable$ = MyFunction$
   'Select the whole document.
   EditSelectAll
   'Delete the contents of the document.
   EditClear
   'Insert new text using the module variable.
   Insert MySharedVariable$
   'Demonstrate naming collisions. Debug is a keyword in VBA.
   If Debug = 0 Then
      FileClose 2
   End If
End Sub
Function MyFunction$
   MyFunction$ = "This is a test of the national broadcasting system ..."
End Function

The following is the same code after conversion.

Dim MySharedVariable$
Public Sub MAIN()
    Dim MyVariable$
    Dim Debug_
    MySharedVariable$ = ""
    'Simple variable assignment.
    MyVariable$ = "Hello World!"
    'Display a message box using the variable.
    WordBasic.MsgBox MyVariable$
    'Illustrate using some simple WordBasic statements.
    'Create a new file
    WordBasic.FileNewDefault
    'Call the below function (very simple).
    MySharedVariable$ = MyFunction$
    'Select the whole document.
    WordBasic.EditSelectAll
    'Delete the contents of the document.
    WordBasic.EditClear
    'Insert new text using the module variable.
    WordBasic.Insert MySharedVariable$
    'Demonstrate naming collisions. Debug is a keyword in VBA.
    If Debug_ = 0 Then
        WordBasic.FileClose 2
    End If
End Sub
Private Function MyFunction$()
    MyFunction$ = "This is a test of the national broadcasting system ..."
End Function

A few things to note about the converted code:

The following conversion sample is a macro that calls a function within another macro (the other macro is called Test1 and the function is MyFunction$):

Sub MAIN
    Insert Test1.MyFunction$
End Sub

The WordBasic object implemented a new method named Call to handle this situation, as seen in the following code:

Public Sub MAIN()
    WordBasic.Insert WordBasic.Call("Test1.MyFunction$")
End Sub

Visual Basic cannot call the MyFunction$ function from any module external to Test1 because it was converted to private in scope. (Only procedures within the same Visual Basic module can call a private procedure using the Visual Basic calling convention.) Adding private scope was done to preserve the WordBasic calling functionality.

Using WordBasic.Call is significantly slower than using the Visual Basic Call statement. To improve performance in cross-module calls, change the declaration of the procedure you're calling from private to public. In the calling statement, remove WordBasic.Call and use the Visual Basic Call statement, as follows:

Public Sub MAIN()
    'The Call keyword is not required, but used here to emphasis it.
    WordBasic.Insert Call Test1.MyFunction$
End Sub

For more details, see "Word Calling Conventions".

Note   If the scope of a subroutine is changed to public, it will be listed as a valid entry point. If this is an undesired side effect, verify that the users can perceive the performance gains.

If the subroutine or function to be called exists in another template, the calling template must reference the source template project. This can be done by adding a reference to the project in the References dialog box in the Visual Basic Editor. For more details, see "Check or Add an Object Library Reference" in Help.

Long-Term Conversion Process

The Word 97 process of converting WordBasic code to Visual Basic code is a short-term solution. The preferred long term solution is to rewrite the converted WordBasic code using Visual Basic objects other than the WordBasic object. One of the primary reasons for the WordBasic object is to extend the life span of old WordBasic code. This enables IS departments to upgrade the install base to Word 97 before a rewrite of the old code base is complete. The code base can be replaced at a managed pace.

Why rewrite the converted code using the hierarchical Word object model? Performance, efficiency, and maintenance are all dramatically improved with well written object-model code.

More information

For more information about converting WordBasic macros to Visual Basic, refer to the following Help topics:

WordBasic Migration Issues

Most migration issues are a result of the functional differences between WordBasic and Visual Basic for Applications. Migrating to Visual Basic for Applications as the development platform for Word also means that Word must abide by an existing standard. In most cases, the conversion is seamless. This section describes eight significant issues that may halt converted macros from executing. Frequently, minor edits to the source code can quickly resolve a conversion issue.

Finding Old Syntax Errors

WordBasic is a true interpreted language and Visual Basic is not. One side effect of true interpreted languages is that statements are not validated until they are executed. In the case of WordBasic, if a statement is never executed, it is never parsed or validated. The following WordBasic code contains a statement that is never executed.

If 0 <> 0 Then
   1 = SyntaxErrorSpokenHere
End If
MsgBox "Worked"

This code functions correctly in the WordBasic environment but causes a compile error in Visual Basic. Visual Basic is a quasi-interpreted language, meaning that Visual Basic compiles code and also executes statements through an interpreter. When Visual Basic compiles code, it parses every statement into excodes. Excodes are compacted statements that the interpreter executes. This compile process improves execution performance. The side effect of this compile process is that every line of code must successfully compile. In the previous example, even though the line that contains the syntax error will never execute, it is syntactically incorrect; in Visual Basic, the parser raises an error. This error causes the parser to halt, ending the compilation process and any chance of the code executing.

The only solution here is to edit the source code, correct the errors, and recompile to test for other errors. Repeat the process until the project compiles successfully.

Adding Option Explicit to the top of every module is highly recommended. Option Explicit forces variable declaration, raising a compile time error for undeclared variables. These are commonly just misspelled variables. To have Option Explicit automatically inserted for you, click Options on the Tools menu in the Visual Basic Editor, and then check Require Variable Declaration in the Options dialog box.

Converting Templates that Contain Execute-Only Macros

In Visual Basic, the project represents the solution. The components of the project represent a logical division of the project, but cannot function alone. In WordBasic, the macro represents the solution. To be consistent with other Office applications, the macro maps as a project component (module) and the document or template maps as the project.

This design change affects how source code is protected. Visual Basic only provides project-level protection. To protect one macro, the whole project must be protected. In the case of an add-in or template, this is generally a non-issue.

In the case of the Normal template, however, this is an issue. It is very common for Word developers to copy project hooks into the Normal template. Solutions that are one or two macros would copy the protected macros into the Normal template.

This creates a problem for the conversion process. Should Word allow protected macros to convert? If it does, should it protect the whole template or expose the code? After careful study, most users preferred to have access to their converted macros instead of a protected template. Therefore, the conversion process removes execute-only macros from the Normal template. Word does make a back-up copy of the Normal template, called Normal.wbk.

There is a working solution, but it must be implemented prior to installing Word 97. Move all execute-only macros from the Normal template into a global template called 2Normal.dot (Templates and add-ins are loaded alphabetically). This way 2Normal.dot will be a protected template after conversion, but won't affect the users personal macros stored in Normal. Most solutions will correctly function after the conversion.

When Word starts, it loads the Normal template. If the Normal template has not been converted into the new Word 97 format, Word converts it at this time. This conversion is silent (no prompts). For all other templates, Word displays a prompt asking the user to replace the existing template with the converted template, save the template with a different name, or cancel. If the user chooses to replace a template and the template contains protected macros, the protection is irreversible. In most cases this is the desired functionality.

Copying Macros from One Template to Another

The macro protection change had a rippling effect on the WordBasic MacroCopy command and the Word Organizer. Below is a summary of the changes.

Why was this done?

If it were possible to copy a macro from a protected project to a public project, the macro would be editable. If it were possible to copy a macro to a public module as execute-only, it would encrypt the whole project.

These changes significantly affect installation routines. The solution provider should refrain from using the Normal template as a code repository. All code stored in Normal must be public (visible to all). Utility or vertical solutions that enhance the application environment should be installed as global templates. Workflow and task oriented solutions should be implemented as attach templates or wizards.

Solutions that are distributed as products should be distributed using professional software distribution tools.

Editing and Creating Macros

There is no WordBasic editor in Word 97. As a result, WordBasic commands that pertain to the WordBasic editor will not work (such as REM, ShowVars, and ToolsMacro .Edit). These routines must be written in Visual Basic to work with the Visual Basic Editor, which is represented by the VBE object. For more information about using the methods and properties of the VBE object see Help.

The following code is a simple demonstration of automating the Visual Basic Editor. The code adds a module to the Normal template, renames the module to NewCode, inserts a procedure named Sub Main to the module, and then executes the procedure. To test the following code, insert the subroutine into a module in the Visual Basic Editor. Make sure there is a reference to the Microsoft Visual Basic for Applications Extensibility object before executing the code.

Sub AddProcToNormal()
    Const MODULE_NAME As String = "NewCode"
    Const PROJECT_NAME As String = "Normal"
    
    Dim vbModule As VBIDE.VBComponent
    Dim szCode As String
    
    'Add a new module and set a reference to it.
    Set vbModule = Application.VBE.VBProjects(PROJECT_NAME). _
                   VBComponents.Add(vbext_ct_StdModule)
    'Name the module NewCode.
    vbModule.Name = MODULE_NAME
    
    'Define the code to be inserted.
    szCode = "Sub Main()" & vbCrLf & _
             vbTab & "Msgbox ""This is a test.""" & vbCrLf & _
             "End Sub" & vbCrLf
             
    'Add the code to the module.
    vbModule.CodeModule.AddFromString szCode
    'Execute the macro.
    Application.Run MODULE_NAME & ".Main"
    
End Sub

Note   Previously, a common Automation hack used to improve performance was to copy WordBasic code into the WordBasic editor using Automation and then execute the macro. This technique does not work in Word 97.

Updating 16-Bit API Calls

16-bit application programming interface (API) declarations must be converted to 32-bit declarations. This limitation also existed in 32-bit WordBasic (in Windows NT® and Windows® 95). For people who have yet to migrate to the 32-bit world, there is some relief. Bruce McKinney, author of Hardcore Visual Basic (Microsoft Press, 1996), has made his Visual Basic 4.0 Windows type library available here.

To download the type library, click here to view a list of sample files. From the list, select bin\win32.tlb and click Copy

Why use the type library? The type library will save you the time of looking up the declaration for the 32-bit version of the API. The type library also provides easy access to the whole Windows library. Using the type library reduces memory consumption of unused API declarations and reduces the maintenance cost of duplicating API declarations across multiple projects.

To use the Windows type library (Win32.tlb) file

  1. Copy the type library to the Windows system directory.

  2. In every project where there are Windows API calls, make a reference to the Windows type library. References are added in the References dialog box (use the References command on the Tools menu in the Visual Basic Editor).

  3. Remark out all 16-bit API declarations. Word automatically remarks out standard Windows API calls.

  4. Force Visual Basic to compile the project (use the Compile Project command on the Debug menu in the Visual Basic Editor).

Visual Basic will halt compilation at the first error it finds. If it halts at an API function, check to see that the alias (function name) is the same name as in the type library. Use the Object Browser to look up functions in the type library. Repeat this process until the project successfully compiles and runs.

For more detailed information on porting 16-bit solutions to 32-bit Word, see Porting Your 16-Bit Microsoft Office-Based Solutions to 32-Bit Microsoft Office.

Updating API Calls that Return LPSTR

WordBasic supported LPSTRs (long pointer to a string) returned from external function calls. This feature was unique to WordBasic. Visual Basic requires that BSTR (basic string) be returned from external calls.

Note   Many of the functions in the Wbodbc.wll that was released with the Word Development Kit for Word 95 and Word 6.0 return LPSTRs.

Opening Documents with Long Macro Names

WordBasic allowed macro names (module names) to be 40 characters. Visual Basic module names are limited to 31 characters. If a template contains 2 macros with the same first 31 characters, Word cannot correctly modify the names of both macros. This will cause Word to fail on opening the template. The only work around is to open the template in Word 6.0 or 7.0 and rename the macros to shorter names.

Converting Localized WordBasic Code to Word 97

WordBasic statements are stored in a tokenized format. When Word 97 converts WordBasic code, it converts the tokens into English statements. However if the developer used localized names in strings, these strings won't be converted. For example, the following WordBasic statement:

ToolsMacro .Name="FichierImprimerDéfaut", .Run 

Successfully converts to the following Visual Basic statement:

WordBasic.ToolsMacro Name:="FichierImprimerDéfaut", Run:=1

On execution an error will occur ("Unable to run specified macro", Error 4644).

In the past, developing WordBasic solutions for international markets has been difficult. Word 97 addresses international development issues by using enumerated constants in place of literal strings. For example, to change the style to Heading 1, you use the wdStyleHeading1 constant instead of the literal string "Heading 1."

'The following statement works in English Word.
Selection.Style = ActiveDocument.Styles("Heading 1")
'The following statement works for all versions of Word 97.
Selection.Style = ActiveDocument.Styles(wdStyleHeading1)

There are other features that are part of the Visual Basic language that dramatically improve the ability to write international macros. For more information on international development, see the MSDN Library on developing for international markets, including Chapter 29, "International Issues," in the Programmer's Guide in the Visual Basic documentation; "Developing for International Markets," in the C++ documentation; and the Globalization Resource Kit, in Books and Periodicals.

Word Calling Conventions

The methodology that Word uses to find and execute a macro is termed a calling convention. In Word 97 there are three distinct and different calling conventions. These are Visual Basic Call, WordBasic.Call and Application.Run.

Visual Basic Calling Convention

The Visual Basic calling convention is the most natural and efficient calling method. Visual Basic permits scope descriptors (public and private) to define the visibility of procedures to other modules. Private procedures are visible only within the module. Public procedures can be called from any other procedure.

The period (.) is termed the navigation operator in Visual Basic. In the same way, objects, methods, and properties are navigated with the navigation operator, so are modules and procedures. For example, to call the MySub subroutine in a module called MyModule in the Normal template, the following syntax can be used. This is termed an explicit call. The Call keyword is optional. It is in brackets to accentuate that the Visual Basic Call statement is being executed with or without the call keyword prefixing the statement.

[Call] Normal.MyModule.MySub

MySub could be called without any navigation operators. This is an implicit call. In this case, the first MySub procedure or method is executed. Visual Basic first looks in the calling module, and then all other modules in the project. If the procedure isn't found, Visual Basic enumerates each reference for MySub. If no procedure or method exists, a compile-time error is generated. Visual Basic inserts the explicit reference in the excodes.

So what does this really mean? First, to call procedures that exist in other templates (Normal, add-ins, or global templates), a reference to that project (template) must be made to the project. This can be added manually or programmatically. Second, when the solution is distributed, all of the references must be resolved before the solution can execute.

The following code illustrates how to programmatically add a reference:

Sub AddReference()
    On Error GoTo ErrorHandler
    ThisDocument.VBProject.References.AddFromFile "Normal.dot"
    Exit Sub
ErrorHandler:
    Select Case Err.Number
        Case Else
            MsgBox Err.Description & "; " & Err.Number & _
                   "; " & Err.Source
            Err.Clear
    End Select
End Sub

For more information, see "Understanding Scope and Visibility" in Help.

Word Calling Conventions

Word 97 has two calling conventions (external to Visual Basic). These are WordBasic.Call and Application.Run. Both are methods of the respective parent object. Both are similar in overall behavior. Both are significantly slower in execution than using the standard Visual Basic Call technique. However, the Word conventions have other uses.

Word implements a calling convention that is based on the active environment. That is, the macros, command bars, and key assignments that are available depend on the active document and its attached template. So, when either WordBasic.Call or Application.Run are used, the call enumerates each level, in the following order, looking for the specified macro.

  1. Active document

  2. Attached template

  3. Normal template

  4. Add-ins and global templates

  5. Built-in commands

Because the attached template can be dynamically removed or changed, the developer can implement polymorphic programming techniques to Word.

Note   Word documents can now contain code and be attached to a template. This new functionality enables the Word developer to use new programming techniques never before possible.

As an example, assume there is a macro called FileOpen in the Normal template. When the users selects Open on the File menu, Word will search the above levels, starting at the first, for FileOpen. In this example, when the Normal template is reached, the FileOpen macro will execute. In most cases, Word's built-in commands are not over-ridden, so the built-in commands execute.

The two differences between WordBasic.Call and Application.Run are the search order in which Word searches for a procedure and the way parameters are passed.

In Word 97, there are two types of macros. One is a subroutine (call this new style) and the other is MacroName.Main (call this old style). WordBasic.Call first searches for a new style macro then for an old style macro. If this fails for the current level, Word moves to the next level, and so on, until the macro is found or the search is exhausted. Application.Run searches all levels first for new style then repeats the search for old style macros.

Note   It is possible to have a converted macro enter at the wrong entry point. For example, if a WordBasic macro contained a subroutine whose name was the same as the macro (module name), the entry point under Word 97 would be at the subroutine instead of Sub Main.

Parameters can be passed through WordBasic.Call but not Application.Run.

Conclusion

In most cases, the conversion process from WordBasic to Visual Basic is seamless. With preplanning, migration issues can be minimized. Other issues can be resolved with minor edits to the converted source code. In Word 97, the converted code can be modified to add features never before possible in WordBasic. In time, all of the code that executes through the WordBasic object can be replaced with Visual Basic code that uses the Word object model.