4
votes

I use Word automation from an Delphi application, and it is very slow. I have stripped my code down to the bare minimum, and was hoping that someone with some experience can tell me where I've gone wrong (and I'm actually hoping i have gone wrong, so that I can speed it up)

The essence of the automation in my application deals with bookmarks. The application opens a document with some special bookmarks, runs through these and change them based on their names. The real version also deals heavily with document variables and fieldcodes. A typical document has 50-80 bookmarks, some of which are nested. I also use some temporary documents to build blocks of text and images, that are placed consecutively in the document to be generated. The attached code is a VERY stripped down version without this functionality, but it displays the unwanted behaviour (i.e. the time to generate a document). In the attached sample it takes about 2,5 seconds to generate the document. For a typical real document it takes about 30-40 seconds, sometimes more.

What I'm hoping for is for someone to say "You're doing this all wrong. When doing Word Automation from Delphi, you must always remember to XXX!".

Since the full project, even when stripped down completely, is quite large, I have made this small application. If there is an obvious mistake in the way I do it, it will hopefully be apparent from this code.

Please create a new VCL Forms Application. Open Word, and create a new document. Enter some text on the first line, mark it and insert bookmark. Enter some text on the second line, and bookmark this too. Save the file as 'c:\temp\bm.doc' as a Word 97-2003 document. After running the application you should have a new document ('c:\temp\bm_generated.doc') with a random number on the first line, and no bookmarks.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, OleServer, WordXP, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  vWordApp    : TWordApplication;
  vDoc        : WordDocument;
  vFileName   : OleVariant;
  vIndex      : OleVariant;
  vBookmark   : Bookmark;
  vSave       : OleVariant;
begin
  vWordApp := TWordApplication.Create(nil);
  try
    vWordApp.ConnectKind := ckNewInstance;
    vWordApp.Connect;
    vFileName := 'c:\temp\bm.doc';
    vDoc := vWordApp.Documents.Open(vFileName, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam);

    //Replace bookmark text with random string:
    vIndex := 1;
    vBookmark := vDoc.Bookmarks.Item(vIndex);
    vBookmark.Range.Text := inttostr(random(10000)); //Will also delete the bookmark!

    //Delete bookmark content and bookmark
    vIndex := 1; //This will be the bookmark that was originally the first, since that was deleted when we sat the text
    vBookmark := vDoc.Bookmarks.Item(vIndex);
    vWordApp.Selection.SetRange(vBookmark.Range.Start, vBookmark.Range.End_);
    vWordApp.Selection.Text := '';

    vFileName := 'c:\temp\bm_generated.doc';
    vDoc.SaveAs2000(vFileName, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam);

    vWordApp.NormalTemplate.Saved := true; //For å slippe spørsmål om "normal.dot" skal lagres
    vSave := wdDoNotSaveChanges;
    vWordApp.Quit(vSave);
    vWordApp.Disconnect;
  finally
    vWordApp.Free;
  end;
end;

end.
3
Just a silly note, do you keep the TWordApplication instance or create the new one each time you work with a document ? I'm sure you keep it, that's why it's marked as a silly note :-)TLama
Not so silly. I use the same TWordApplication if i generate several documents at once, but most of the time this isn't the case. This is part of the print routines of an ERP application, so I create TWordApplication when the print-dialog indicates a word-document should be generated, and free it when finished.Svein Bringsli
That code looks good to me. Perhaps that's just how long it takes.David Heffernan
@David: That's excactly what I didn't want to hear :)Svein Bringsli
~ 300 - 400 ms here. Word2010 (think it took about a sec the first time though).Sertac Akyuz

3 Answers

2
votes

You could try:

vWordApp.ScreenUpdating := False;

and maybe also

vWordApp.Visible := False;

(remember to set back to previous values when done).

0
votes

Have you tried supplying your parameters to VBA and doing your replacements inside word? I did a few hundred macros in word docs for a client a few years back. That was a much quicker implementation from what I remember. That was from a Java code base.

Word spends a lot of time loading and parsing the document in the first place. That might be where the bulk of the time is spent. I would base line the timing test by doing no bookmark replacements. The other thing is that it is probably doing a full text scan for each replacement. That might be why the VBA worked better.

0
votes

If you do just basic stuff with bookmarks and fieldcodes (mind you NO IF constructions) you might think about converting the document(s) to RTF and change everything in there. I did that can run code inside 0.005 of a second per document. Saving the document takes about 0.2 - 2 seconds, depending on the speed of the diskdrive.