Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
Member Area
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Become a Member
-Why sign up!
-Chat Online!
-Indexes NEW!!
-Build your resume
-Find a job
-Post a job
-Resume Search
-Link to us
Visit Embarcadero
Embarcadero Community
Delphi / MSWord Automation FAQ Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
Delphi 3.x
User Rating
No Votes
# Votes
DSP, Administrator
Reference URL:

This document provides answers to some basic OLE Automation questions regarding 
Delphi (3 or 4) and Microsoft Word (8.0). The concepts outlined here can also be 
applied to many other MS applications (Excel, Internet Explorer etc) as well as any 
other application that supports OLE Automation. 


Setting up Delphi to work with Word 

In order for Delphi to access methods and properties exposed by Word (using OLE 
Automation early binding) the Word type library must be installed. Type libraries 
provide the definitions for all exposed methods and properties of an Automation 
Server in a standardized format that can be used by any compliant programming 
application including Delphi. To use Word's type library in Delphi select the 
"Import Type Library" from the "Project" menu and choose the file msword8.olb 
located in Microsoft Office's "Office" directory. This will create the file 
"Word_TLB.pas" which is the object pascal translation of the type library. The 
files Office_TLB.pas and VBIDE_TLB.pas will also be created since the Word type 
library references these type libraries. These files should be saved in Delphi's 
"Imports" directory. Now simply include Word_TLB in the uses list of any unit that 
will be accessing Word properties or methods. 

Finding help on Word's interfaces and methods 

All exposed functionality for Office applications is documented in the vba*.hlp 
files located in Microsoft Office's "Office" directory. For help on Word objects 
refer to the help file vbawrd8.hlp. This file is not installed by default during 
Office installation so you may have to get it from the Office installation program. 

How to open Word using OLE Automation 

The CoApplication class defined in the type library represents the implementation 
of the Word Application interface. Call CoApplication.Create to create an instance 
of Word. This method will return a pointer to an interface of type _Application. 
The _Application interface provides a "Documents" interface which provides 2 
methods to access documents: Add and Open. 

Both these methods return a pointer to a _Document interface. As well these methods 
take parameters that are of type OLEVariant. Many parameters passed to Word methods 
are defined as "optional". Optional parameters must be included in calls to methods 
but can be defined as Unassigned to indicate that they are not being used. Delphi 4 
provides a variable which can be used for optional parameters that are not being 
used called EmptyParam. 

Sample Code
1   uses
2     Word_TLB;
4   procedure StartWord(var WordApp: _Application; var WordDoc: _Document);
5   var
6     SaveChanges: OleVariant;
7   begin
8     try
9       WordApp := CoApplication.Create;
10      WordDoc := WordApp.Documents.Add(EmptyParam, EmptyParam);
11      WordApp.Visible := True;
12    except
13      if Assigned(WordApp) then
14      begin
15        SaveChanges := wdDoNotSaveChanges;
16        WordApp.Quit(SaveChanges, EmptyParam, EmptyParam);
17      end;
18    end;
20  //How to connect to a running copy of Word 
22  //To connect to a running instance of Word use the Delphi command 
23  GetActiveOleObject. This will return an IDispatch variable which points to the Word 
24  Application. You can then query the return object using QueryInterface to get the 
25  pointer to the _Application object. GetActiveOleObject will raise an exception if 
26  an instance of the object does not exist in the Running object Table (ROT) so make 
27  sure to wrap the call in a try..except block. 
29  Sample Code
31  uses
32    Word_TLB;
34  procedure StartWord(var WordApp: _Application);
35  var
36    SaveChanges: OleVariant;
37  begin
38    try
39      GetActiveOleObject('Word.Application').QueryInterface(_Application, WordApp);
40    except
41      WordApp := nil;
42    end;
44    if Unassigned(WordApp) then
45    begin
46      try
47        WordApp := CoApplication.Create;
48        WordApp.Visible := True;
49      except
50        if Assigned(WordApp) then
51        begin
52          SaveChanges := wdDoNotSaveChanges;
53          WordApp.Quit(SaveChanges, EmptyParam, EmptyParam);
54        end;
55      end;
56    end;
57  end;

Getting data from Word 

The Word Document object supports the IDataObject Interface. To get data from Word 
(RTF, text, structured storage etc) the IDataObject must be used. To get a pointer 
to the IDataObject Interface use QueryInterface. Word documents support the 
standard formats CF_TEXT and CF_METAFILEPICT as well as a number of other specific 
formats including RTF and structured storage. For the standard formats the constant 
values can be used for the value of cfFormat, but for the other formats the 
Document must be queried using the function EnumFormatEtc. This function will 
return a list of supported formats. The required format from this list is then 
passed to the GetData function of the IDataObject interface. It is important to 
note that the value of cfFormat for the proprietary formats (RTF etc.) is not 
constant between machines so it must always be found using EnumFormatEtc and not 
hard coded. For more information on IDataObject and its methods refer to the Win32 
programming help files (included with Delphi 4, C++Builder, Visual C++ etc.). 

Sample Code
58  uses
59    Word_TLB;
61  function GetRTFFormat(DataObject: IDataObject; var RTFFormat: TFormatEtc): Boolean;
62  var
63    Formats: IEnumFORMATETC;
64    TempFormat: TFormatEtc;
65    cfRTF: LongWord;
66    Found: Boolean;
67  begin
68    try
69      OleCheck(DataObject.EnumFormatEtc(DATADIR_GET, Formats));
70      cfRTF := RegisterClipboardFormat('Rich Text Format');
71      Found := False;
72      while (not Found) and (Formats.Next(1, TempFormat, nil) = S_OK) do
73        if (TempFormat.cfFormat = cfRTF) then
74        begin
75          RTFFormat := TempFormat;
76          Found := True;
77        end;
78      Result := Found;
79    except
80      Result := False;
81    end;
82  end;
84  procedure GetRTF(WordDoc: _Document);
85  var
86    DataObject: IDataObject;
87    RTFFormat: TFormatEtc;
88    ReturnData: TStgMedium;
89    Buffer: PChar;
90  begin
91    if Assigned(WordDoc) then
92    begin
93      try
94        WordDoc.QueryInterface(IDataObject, DataObject);
95        if GetRTFFormat(DataObject, RTFFormat) then
96        begin
97          OleCheck(DataObject.GetData(RTFFormat, ReturnData));
98          // RTF is passed through global memory
99          Buffer := GlobalLock(ReturnData.hglobal);
101         { Buffer is a pointer to the RTF text
102           Insert code here to handle the RTF text (ie. save it, display it etc.) }
103         GlobalUnlock(ReturnData.hglobal);
104       end;
105     except
106       ShowMessage('Error while getting RTF');
107     end;
108   end;
109 end;

Event Sinking with Word 

There are 2 ways that event sinking can be performed on Word: 

1.   Using the IAdviseSink interface 

To use the IAdviseSink interface you must first write an object that implements 
this standard interface. This object is then passed to the DAdvise method of a Word 
Document's IDataObject interface or to the Advise method of a Word Document's 
IOleObject interface. Refer to the help MS help on IAdviseSink for more information 
on this interface. 

2.   Using ConnectionPoints 

Word provides the following event sources that can be sinked to: 


procedure Startup; dispid 1; 
procedure Quit; dispid 2; 
procedure DocumentChange; dispid 3;


procedure New; dispid 4; 
procedure Open; dispid 5; 
procedure Close; dispid 6; 


procedure GotFocus; dispid -2147417888; 
procedure LostFocus; dispid -2147417887; 

To start a connection with Word you must get the IConnectionPointContainer for the 
Word application or document (depending what events you want to sink to). Next 
query the IConnectionPointContainer for the IConnectionPoint that you wish to use 
(ApplicationEvents, DocumentEvents or OCXEvents in this case). Once you have the 
IConnectionPoint use the Advise method to establish the connection. 

There appears to be some limitations with Word's implementation of connection 
points. When a document is closed in Word, without closing Word itself, Word sends 
a DocumentEvents.Close message and then an ApplicationEvents.DocumentChange 
message. Then when Word is closed nothing is sent. On the other hand if Word is 
closed with an open document then it sends a DocumentEvents.Close message and an 
ApplicationEvents.Quit message. Another problem is that Word will send the 
DocumentEvents.Close message when the user "closes" the document but before the "Do 
you wish to save changes?" dialog is shown. So if the user then selects cancel the 
document is never closed but the DocumentEvents.Close message was sent. 

Sample Code (StartingConnection)
110 uses
111   Word_TLB, activex, comobj, ConnectionObject
113   // ConnectionObject is the unit containing TWordConnection
115 procedure StartWordConnection(WordApp: _Application;
116   WordDoc: _Document;
117   var WordSink: TWordConnection);
118 var
119   PointContainer: IConnectionPointContainer;
120   Point: IConnectionPoint;
121 begin
122   try
123     { TWordConnection is the COM object which receives the
124       notifications from Word. Make sure to free WordSink when
125       you are done with it. }
126     WordSink := TWordConnection.Create;
127     WordSink.WordApp := WordApp;
128     WordSink.WordDoc := WordDoc;
130     // Sink with a Word application
131     OleCheck(WordApp.QueryInterface(IConnectionPointContainer, PointContainer));
132     if Assigned(PointContainer) then
133     begin
134       OleCheck(PointContainer.FindConnectionPoint(ApplicationEvents, Point));
135       if Assigned(Point) then
136         Point.Advise((WordSink as IUnknown), WordSink.AppCookie);
137     end;
139     // Sink with a Word document
140     OleCheck(WordDoc.QueryInterface(IConnectionPointContainer, PointContainer));
141     if Assigned(PointContainer) then
142     begin
143       OleCheck(PointContainer.FindConnectionPoint(DocumentEvents, Point));
144       if Assigned(Point) then
145         Point.Advise((WordSink as IUnknown), WordSink.DocCookie);
146     end;
147   except
148     on E: Exception do
149       ShowMessage(E.message);
150   end;
151 end;

Sample Code (Connection Object)
152 unit ConnectionObject;
154 interface
156 uses
157   Word_TLB;
159 type
160   TWordConnection = class(TObject, IUnknown, IDispatch)
161   protected
163     { IUnknown }
164     function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
165     function _AddRef: Integer; stdcall;
166     function _Release: Integer; stdcall;
168     { IDispatch }
169     function GetIDsOfNames(const IID: TGUID; Names: Pointer;
170       NameCount, LocaleID: Integer;
171       DispIDs: Pointer): HResult; stdcall;
172     function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
173     function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
174     function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
175       Flags: Word; var Params;
176       VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
178   public
179     WordApp: _Application;
180     WordDoc: _Document;
181     AppCookie, DocCookie: Integer;
182   end;
184 implementation
186 uses
187   Windows, ActiveX, Main;
189 procedure LogComment(comment: string);
190 begin
191   Form1.Memo1.Lines.Add(comment);
192 end;
194 { IUnknown Methods }
196 function TWordConnection._AddRef: Integer;
197 begin
198   Result := 2;
199 end;
201 function TWordConnection._Release: Integer;
202 begin
203   Result := 1;
204 end;
206 function TWordConnection.QueryInterface(const IID: TGUID; out Obj): HResult;
207 begin
208   Result := E_NOINTERFACE;
209   Pointer(Obj) := nil;
210   if GetInterface(IID, Obj) then
211     Result := S_OK;
212   if (not Succeeded(Result)) then
213     if IsEqualIID(IID, DocumentEvents) or IsEqualIID(IID, ApplicationEvents) then
214       if GetInterface(IDispatch, Obj) then
215         Result := S_OK;
216 end;
218 { IDispatch Methods }
220 function TWordConnection.GetIDsOfNames(const IID: TGUID; Names: Pointer;
221   NameCount, LocaleID: Integer;
222   DispIDs: Pointer): HResult;
223 begin
224   Result := E_NOTIMPL;
225 end;
227 function TWordConnection.GetTypeInfo(Index, LocaleID: Integer;
228   out TypeInfo): HResult;
229 begin
230   Pointer(TypeInfo) := nil;
231   Result := E_NOTIMPL;
232 end;
234 function TWordConnection.GetTypeInfoCount(out Count: Integer): HResult;
235 begin
236   Count := 0;
237   Result := E_NOTIMPL;
238 end;
240 function TWordConnection.Invoke(DispID: Integer; const IID: TGUID;
241   LocaleID: Integer; Flags: Word;
242   var Params; VarResult, ExcepInfo,
243   ArgErr: Pointer): HResult;
244 begin
245   // This is the entry point for Word event sinking
246   Result := S_OK;
247   case DispID of
248     1: ; // Startup
249     2: ; // Quit
250     3: ; // Document change
251     4: ; // New document
252     5: ; // Open document
253     6: ; // Close document
254   else
255     Result := E_INVALIDARG;
256   end;
257 end;
259 end.

Call Delphi from Word (VBA) 

Make your Delphi application an OLE Automation server (TAutoObject). 
File..New..ActiveX..Automation Object. Define your interface(s) and write the 
methods that you wish to call from Word. 
In VBA add your Delphi exe to the project. Tools..References. You should now be 
able to use the VBA Object Browser to (F2) to browse your Delphi functions. 
Code a VBA procedure to call Delphi. 

Sample Code
Sub foo
	' AutoServer is the name of the class
	' in the object browser
	Dim MyServer as AutoServer

	System.Cursor = wdCursorWait

	set MyServer = new AutoServer
	Call MyServer.DelphiFoo(p1, p2)

	System.Cursor = wdCursorNormal
end Sub

Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10


Share this page
Download from Google

Copyright © Mendozi Enterprises LLC