Word 97 Frequently Asked Questions

Last updated 22 August 1999

Delphi 5 comes with the Word objects as components - surely your classes are no longer needed?

Project Demo.exe raised exception class EOleSysError with message 'Class not registered'. Process stopped. Use Step or Run to continue.

[Fatal Error] Word97.pas(29): File not found: 'Word_TLB.dcu' or 'Word97.pas'

[Error] Word97.pas(267): Undeclared identifier: 'Application_'
[Error] Word97.pas(271): Undeclared identifier: 'CoApplication_'

Where is the Borland demo version of Word97?

Which versions of Delphi can use these classes?

Is there a C++ Builder version of these classes?

Why use early binding?

I'm odd - I would like to use late binding. How to I get a handle on Word?

Delphi 3 has no EmptyParam, how can I survive?

Why is Word slow to load and eats vast amounts of memory?

Automating Word is faster if I drop down a Word menu — How can I get this speed advantage in code?

I want to use Word to produce xxx, what is the best way to do this?

How do I use Word inside the TOleContainer object?

I want to re-define what Word does for x menu item (e.g., FileNew)

I want to convert my Word macros/VBA code to Delphi as Delphi is compiled and therefor fast

Where can I get more information on Word automation and Delphi COM in general?

Delphi 5 comes with the Word objects as components - surely your classes are no longer needed?

Delphi 5 wraps the automation classes up into a set of 5 components (TWordApplication, TWordDocument, TWordFont, TWordLetterContent, TWordParagraphFormat). These components publish a standard set of OleServer properties and expose specific properties and methods as a public interface. The events are also specific to the object. The advantages of these new components over the existing classes are:

  1. They expose the component's events as easily as any other component - this has to be the main advantage
  2. They make it easy to connect to an existing server or create a new one
  3. They connect using early binding
  4. By using method overloading, they provide a way to make OLE default parameters easy to code

The components also have a number of down sides:

  1. There is no point that I can see to having any object other than the TWordApplication as a component as almost all users create the document object at runtime. I cannot see any use for the other three components. They seem to be have been created because the code generator found them
  2. By using method overloading, they have made the import libraries huge - they are now about 180% of what they were in Delphi 4. Although this is an attractive way to make optional parameters easy to code for, the alternative way (to actually use Delphi's optional parameters) is neater. However, you cannot make OleVariants optional (see next problem)
  3. They continue to use OleVariant for almost all parameters. This is a difficult to use type as you cannot substitute its value with a constant and you cannot give it a default value for optional parameters. My classes get round this by using standard Delphi types for the method parameters and converting them in code to OleVariants. This makes the code insight features more intuitive and makes optional parameters simpler to code.
  4. The events exposed by Word are inconsistent and incomplete and the classes do nothing to help with this. My classes go some way toward making the events useable.

Project Demo.exe raised exception class EOleSysError with message 'Class not registered'. Process stopped. Use Step or Run to continue.

class function CoGlobal.Create: _Global;
begin
  Result := CreateComObject(CLASS_Global) as _Global;
end;


This is not a bug in the demo. The Global class is registered when any instance of Word is present. If an exception occurs when trying to refer to the class, this implies that there is no active Word present and the creator method will have to create a new one. While an exception is raised when you run the code within Delphi, the exception will be silent in a free-standing program.

I have updated the constructor to use a different means of getting the active application object. This uses GetActiveObject and checks its return value to see if it succeeds (most examples of using this function wrap it up in OleCheck—all this procedure does is raise an appropriate exception if the function result indicates failure). I check for a specific error (no application object present) and deal with that by creating a new instance. Note that I still use OleCheck to cope with any other errors that I have not coded for. My demo should now run without any exceptions. Note that I now get the application object from "GetActiveObject" rather than from the Global object. In a senario where several instance of Word are in existance, this may not return the same instance of Word as before (haven't checked as it is of no relevance to me - may be important to you). Let me know if this is a problem.

 

[Fatal Error] Word97.pas(29): File not found: 'Word_TLB.dcu' or Word97.pas'

I don't provide Word_TLB.pas for two reasons:

1. It is huge - 859k when created by Delphi 4 and 699k when created by Delphi 3
2. It is Delphi version specific
3. You can easily roll-your-own:
    In Delphi choose File_Open
    Select Files of Type "Type Library" and open the file "C:\Program Files\Microsoft Office\Office\Msword8.olb"
    Make sure "$(DELPHI)\Imports" is in your Library path.
    If you own Delphi 3, you will need to make a small fix to the pascal file - see the FAQ on "Application_"

NB Delphi 5 imports the library as C:\Program Files\Borland\Delphi5\Ocx\Servers\Word97.pas
This is rather annoying and means I have had to change my unit to AHDelphi97.pas

 

[Error] Word97.pas(267): Undeclared identifier: 'Application_'
[Error] Word97.pas(271): Undeclared identifier: 'CoApplication_'

This occurs with Delphi 3 and is due to the way it creates the Word_TLB.pas file.

With COM objects, when you refer to an object, property or method, you are just refering to an GUID class ID (i.e., a big number). The actual name used is not important. You can change the names of entities in the type library pascal file and still use it (provided you refer to the new name in your code). This is similar to aliases for DLL imports. When Delphi imports a type library and writes out the pascal file, it alters quite a few of the objects names because of conflicts with its "reserved names". You will see this at the start of the file:

{ Conversion log:
  Warning: 'Object' is a reserved word. Parameter 'Object' in _Application.IsObjectValid changed to 'Object_'
  ...

Reserved words have been part of pascal from the beginning. They are fundamental names used in pascal that should not be used as identifiers to avoid confusion with the original pascal meaning. Since pascal is strictly typed, it won't allow you to reuse these special words (unlike C++). Delphi 3 does a fairly good job at removing these conflicts when it imports type libraries (Microsoft's type libraries are full of "reserved words"). However there is one object that was not a renamed because it was not a reserved word in Delphi 3. The object is of course "Application". Application is an object in the Forms.pas file. VCL object names were not considered important enough for reserving/renaming, even one as fundamental as Application - if you think you have never used it, you have not looked - you will find it in all your project files (*.dpr):

begin
  Application.Initialize;
  Application.CreateForm(TfrmDemo, frmDemo);
  Application.Run;
end.

Now, if you were to compile a program that "used" Word_TLB.pas, when the compiler gets to Application it may look in Word_TLB.pas first and complain at the lack of an Initialize method.The application object is also used elsewhere in the VCL and can be of use in your own code (e.g. to get the EXEname). You can specify which file to look in for an object by prefixing it with the unit name (e.g., Forms.Application or Word_TLB.Application). This is fine for your own code, but a hassle for the autocreated code in the dpr and you shouldn't change code in the VCL.

When Delphi 4 came along, Borland realised that this was a problem and made "Application" a reserved word in the eyes of the type library. Thus the Delphi 4 version of Word_TLB.pas uses "Application_" instead of "Application". NB do not confuse either with "_Application".

The solution to all this is to edit Word_TLB.pas. Now Delphi warns you that any edits to a type library pascal file will be overwritten if you re-import the type library. However, since the Word 97 type library will never change, you will never need to re-import it. The changes you should make are:

Line number Original line New line
3059 Application = _Application; Application_ = _Application;
many - do a search function Get_Application: Application; safecall; function Get_Application_: Application_; safecall;
many - do a search property Application: Application read Get_Application; property Application_: Application_ read Get_Application_;
many - do a search property Application: Application readonly dispid 1000; property Application_: Application_ readonly dispid 1000;
14663 CoApplication = class CoApplication_ = class
14709 class function CoApplication.Create: _Application; class function CoApplication_.Create: _Application;
14714 class function CoApplication.CreateRemote(const MachineName: string): _Application; class function CoApplication_.CreateRemote(const MachineName: string): _Application;

Note, the line numbers are for my copy of Word_TLB.pas produced by Delphi 3. They will probably be the same for you (but MS/Delphi service packs may alter this).

NB Delphi 5 changes these again and refers to WordApplication, WordDocument, etc..

 

Where is the Borland demo version of Word97?

If you installed Delphi in the default directory, you should find it at:

C:\Program Files\Borland\Delphi4\Demos\Activex\Oleauto\Word8\Word97.pas

or

C:\Program Files\Borland\Delphi5\Demos\Activex\Oleauto\Word8\AutoImpl.pas

 

Is there a C++ Builder version of these classes?

No. I don't own C++ Builder and have little C++ experience. I presume you could use the compiled unit in a version compatible with Delphi 4. I could send this unit to anyone who want to try it and doesn't have Delphi 4. I know there are some compatibility issued with sharing units but have no time/desire to find out. Jacques Mainville asked this question but the return e-mail address bounced -this is why you have not received a reply!

 

Which versions of Delphi can use these classes?

The classes were developed in Delphi 4. Quamar Ali has converted the original Word97 for Delphi 3. As new features have been added, I have tried to keep the Delphi 3 up-to-date. However, I no longer have Delphi 3 installed. All I can do is check that the code compiles in Delphi 4, using the Delphi 3 type library. Delphi 4 has lots of nice extras that make working with COM objects easier - especially default/optional parameters. Quamar Ali removed the event sink from the Delphi 3 version. I have no way of knowing if you can use event sinking in Delphi 3 - I suspect not. If someone wants to copy the code over and try - be my guest.

Delphi 1 and 2 are not able to use these classes.

I have just made the classes Delphi 5 compatible.

 

Why use early binding?

There are several reasons why early binding has not taken off with Word:

  1. Many are still put off by the difficulties they had with type libraries from Delphi 3
  2. Word's objects make great use of optional parameters. Early binding forbids their use. Borland were not very forthcoming with a method for passing 'null' optional parameters in Delphi 3 (see Appendix 1) and didn't exactly advertise the existence of the EmptyParam 'constant' in Delphi 4.
  3. Many arguments are enumerate types (TOleEnum). You need to constantly look these up in Word VBA help.
  4. Far too many arguments are of type OleVariant. These require that you pass a variable of OleVarient type, i.e., you cannot do something like Selection.MoveLeft(wdWord, 1, wdMove) as these arguments are constants, not OleVariants.
  5. Code for getting the current word application using late binding was frequently give in the OleAutomation newsgroups (see Appendix 2). This is useful; otherwise you end up with loads of copies of word.

Here are a few reasons why early binding is better:

  1. We all know it is faster (although Word is d*** slow so you may not notice - see Why is Word slow to load and eats vast amounts of memory?).
  2. It is language independent (more of a problem with Word versions before '97).
  3. You get tool tips to show you all 11 parameters required to do ActiveDocument.SaveAs (…)
  4. You are guaranteed to get a Word 97 object. If you use CreateOLEObject('Word.Application') then you may get an error if an older version of word is present as there was no Application object then (you would have used Word.WordBasic). You may still get this error if you have both Word 97 and a previous version installed on the same machine. This is very important if you application is distributed to unknown machines (with goodness knows what on them).
  5. You can interface with the events generated from Word (for what it's worth).
  6. Late binding is for script interpreters - you bought Delphi because you're a grown-up and can handle the raw speed, why use crayons to write your masterpiece?

 

I'm odd - I would like to use late binding. How to I get a handle on Word?

Use the following code:

var
  wrdApp : OleVariant;
begin
  try
    wrdApp := GetActiveOLEObject('Word.Application');
  except on EOleSysError do
    wrdApp := CreateOLEObject('Word.Application');
  end;

 

Delphi 3 has no EmptyParam, how can I survive?

To roll your own EmptyParam for Delphi 3, declare the following as a global variable in a unit:

var
 EmptyParam: OleVariant;
At the end of the unit, put these lines in the initialization section
initialization
  TVarData (EmptyParam).VType  := varError;
  TVarData (EmptyParam).VError := DISP_E_PARAMNOTFOUND;
end.

 

Why is Word slow to load and eats vast amounts of memory?

Short answer - because of the bloatware wars with WordPerfect.

Better answer -

- start Word (opens new document for you)
- in the new document, type the word Blue
- select this word and use Format->Font to change it to be bold and blue
- select OK
- enter a space after the word Blue
- choose Help->About
- on the Word icon, press Control-Shift-(Left)Click

After a bit of thinking, a full screen pinball game appears!

 

Automating Word is faster if I drop down a Word menu — How can I get this speed advantage in code?

This is a FAQ of the OleAutomation newsgroups. If you have a lot of automation to perform and you switch to Word, then click on the File menu, the automation process will be much faster. This is probably because Word suspends any background tasks (like spell-checking) when you do this. You may get a speed advantage if you turn off the intellisense, spelling and grammar features of Word (but preserve them when finished or you will upset the user). However, it appears that you still don't get the full advantage that you get when you drop a menu. If you have read this far in the hope that I have the answer — sorry! I have put this FAQ in so that anyone who finds the solution may feel charitable enough to share it with me (and the rest of the Delphi community). So far I have not seen a solution on the newsgroups.

 

I want to use Word to produce xxx, what is the best way to do this?

My Word97 classes were developed for writing one or two letters or a few pages of a report. Data is read from an Access file and heavily processed to generate natural English text for inserting inside Word. The processing is the main reason I use Delphi — I would have great difficulty coding it in VBA.

Having the letters in Word format is important so that the secretaries can save/edit them just like all their other letters.

When you are considering Word to generate letters consider the following:

  1. Are you sure  you want to use Word?
  1. Are you sure you want to use Delphi?

I know this is heresy, but there are good reasons for considering coding within Word VBA (see converting Word macros/VBA code to Delphi).
You can go half way and use my classes as well as call Word macros for part of the work.

You may also want to consider mail-merging if you are producing lots of similar letters.

Using Visual Basic itself (as opposed to Word VBA) has no advantages to my mind.

  1. Are you sure you want to write the letter/report by automating each step.

There are other methods to consider:

  1. Generate ASCII or RTF output and then import it into Word. You can do this by using Word's filters. You can also directly read/write the RTF of a Word document using OLE - see Joel Milne's Delphi / Word pages.
  2. Use QuickReport's Word filter. I have not studies this in depth, but I suspect this just exports RTF. I believe that little formatting or placement is kept during the transfer so it is probably of limited use. It is certainly not up to the quality of Access' Report to Word export.

 

How do I use Word inside the TOleContainer object?

There has been quite a lot written about TOleContainer in the OleAutomation newsgroup. Basically, it seems to be a partly implemented container class, unlike the Visual Basic version. It has been suggested that since the Borland team don't use it, it has not been developed to its full potential. I have no idea whether this is true — what is clear is that you are largely on your own here. I have read that some very brave/clever programmers have rewritten it to do what they need. However, these are in-house rewrites and I have yet to see TSuperOleContainer on Torry's.

*** I have managed the impossible - I have got the TOleContainer to work and have converted the demo to show it off (Demo2.dpr). Nothing fancy as I don't use it - perhaps someone who does could add menu merging and thus answer that other FAQ - how do I redefine the menu File...

 

I want to re-define what Word does for x menu item (e.g., FileNew)

It is quite easy to get Word to perform a different task when the user chooses a standard menu item. Just use the standard action's name as the name of your macro, e.g.,

Sub FileNew
  ' This proc alters the "view" of the templates
  SendKeys "%2"
  Dialogs(wdDialogFileNew).Show
End Sub

If you cannot do what you want to do using VBA, you could always call a Delphi DLL. I am interested in ways to communicate back to the controlling Delphi application (rather than an in-process DLL). If anyone has done this or has a good idea how this could be done easily and reliably, I would be very interested to hear from you.

 

I want to convert my Word macros/VBA code to Delphi as Delphi is compiled and therefor fast

In general, don't.

Prototyping how to automate Word using macros is a great way to learn how to control Word from a Delphi program. However if you have existing macros in Word, particularly if they are big, keep them there. Just call the macro and let it run inside Word.

When Delphi automates Word it has to pass a fair amount across process boundaries. COM marshalls this so you don't have to think about it but you should be aware that this is a costly exercise, even when you use early binding. When Word executes VBA code, all the action happens inside Word's process space. Despite the fact that VBA is interpreted, automation commands run faster inside Word.

You may wish to convert to Delphi if you want to hide the code (although you can hide code in VBA). You will get a speed advantage if you have to do a fair amount of non-automation tasks (e.g, calculations/database lookups). Programming that kind of stuff is more enjoyable in Delphi too.

It is easier to amend VBA code inside a Word template that it is to recompile and re-install your program. Bear in mind that you can use the DAO and ADO within the VBA quite easily to read databases. If all you want to do is read names and addresses from a database and fill in the headings for a letter, this is almost certainly easier from inside Word that it is from Delphi.

If you were put off using Word basic and Word forms in the past, have a look at VBA for Word 97 now. I was pleasantly suprised at how easy it was to make up a simple form and put code behind it. Before I get shot as a traitor, I should say that the VBA environment and language is still miles behind Delphi. I can just about bear it for very simple forms (a bit like Access).

 

Where can I get more information on Word automation and Delphi COM in general?


MS Word Articles - http://www.microsoft.com./WordDev/w-a&sa.htm
Microsoft Office 97 Automation Help File Available on MSL - http://support.microsoft.com/support/kb/articles/Q167/2/23.asp

Other COM programmers are Graham Marshall, Joel Milne, Binh Ly and Deborah Pate
They all know far more about Delphi and COM than I do and frequent the delphi OleAutomation newsgroup.

Graham Marshall's Delphi /Word / Excel page is http://vzone.virgin.net/graham.marshall/default.htm

Joel Milne's Delphi / Word page is http://www.softmosis.ca/WordFAQ.html

Binh Ly's Delphi / COM page is http://www.castle.net/~bly/Programming/Delphi/index.html

Deborah Pate's Delphi Automation page is http://www.djpate.freeserve.co.uk/Automation.htm