4

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.
Svein Bringsli
  • 5,640
  • 7
  • 41
  • 73
  • 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 Apr 25 '12 at 11:27
  • 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 Apr 25 '12 at 11:34
  • 1
    That code looks good to me. Perhaps that's just how long it takes. – David Heffernan Apr 25 '12 at 11:36
  • @David: That's excactly what I _didn't_ want to hear :) – Svein Bringsli Apr 25 '12 at 11:38
  • ~ 300 - 400 ms here. Word2010 (think it took about a sec the first time though). – Sertac Akyuz Apr 25 '12 at 11:45
  • @Svein - Sure. What I was thinking was the version of the application could matter. – Sertac Akyuz Apr 25 '12 at 11:54
  • @Sertac: All versions from Word 97 and up. Depends what our customers have installed. If there were significant differences between versions, we could raise the requirements, but as far as I can tell there isn't. – Svein Bringsli Apr 25 '12 at 12:06

3 Answers3

2

You could try:

vWordApp.ScreenUpdating := False;

and maybe also

vWordApp.Visible := False;

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

Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
  • In the real version word is invisible. But in the stripped version I didn't include that. (But I didn't know about ScreenUpdating, I'll test that too) – Svein Bringsli Apr 25 '12 at 12:06
  • It would be good to see some benchmarks to be able to consider time needed to generate some reference MS Word document. – truthseeker Jan 27 '15 at 06:51
0

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.

Michael
  • 587
  • 3
  • 14
  • Doing it without bookmarks is not an option. And AFAIK VBA uses the object model, just as I do from Delphi, so I don't think that would affect the speed. – Svein Bringsli Apr 26 '12 at 06:15
  • Yeah it shouldnt but it seems it does. Perhaps Word is instantiating the whole environment every time; I am not sure, I just remember it was a dramatic answer to the problem. For sure you are flipping threads each time so it might improve things if you do your call and then call Application.HandleMessage possibly a few times. It is considered a hack by many purists and I wont argue that, it is just useful sometimes. The other part of this answer is to limit the number of round trips you do between applications. Every one of them will induce lag. – Michael Apr 27 '12 at 19:19
0

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.