views:

853

answers:

9

I'm running into the classic scenario where, when creating Word COM objects in .NET (via the Microsoft.Office.Interop.Word assembly), the WinWord process won't exit even though I'm properly closing and releasing the objects.

I've narrowed it down to the use of the Word.Documents.Add() method. I can work with Word in other ways without a problem (opening documents, modifying contents, etc) and WinWord.exe quits when I tell it to. It's once I use the Add() method (and only when adding a template) that the process is left running.

Here is a simple example which reproduces the problem:

Dim oWord As New Word.Application()
oWord.Visible = False

Dim oDocuments As Word.Documents = oWord.Documents

Dim oDoc As Word.Document = oDocuments.Add(Template:=CObj(sTemplatePath), NewTemplate:=False, DocumentType:=Word.WdNewDocumentType.wdNewBlankDocument, Visible:=False)


' dispose objects
oDoc.Close()
While (Marshal.ReleaseComObject(oDoc) <> 0)
End While
oDoc = Nothing

While (Marshal.ReleaseComObject(oDocuments) <> 0)
End While
oDocuments = Nothing

oWord.Quit()
While (Marshal.ReleaseComObject(oWord) <> 0)
End While
oWord = Nothing

GC.Collect()

As you can see I'm creating and disposing the objects properly, even taking the extra step to loop Marsha.ReleaseComObject until it returns the proper code. Working with the Word objects is fine in other regards, it's just that pesky Documents.Add that is causing me grief. Is there another object that gets created in this process that I need to reference and dispose of? Is there another disposal step I need to follow? Something else? Your help is much appreciated :)

Update: I tried GC.Collect at the end of the disposal step but still no luck.

Update 2: I've narrowed the problem down to the use of custom templates. When I invoke Documents.Add(...) I specify a custom template for the new document. If I don't do this and instead invoke Add() with no parameters, then the problem does not happen.

A: 

does "oDocuments" have a .Dispose() or .Close() method? you're disposing of the other 2, but not this one.

marduk
Thanks for pointing that out. I am disposing the oDocuments object, I had just forgotten to put it in my example. The example is fixed now but the problem remains...
Keith
Tried to use Try / Finally ? Keith ?
MadBoy
+1  A: 

I've only done Excel automation, but have run into similar problems. Referencing some old code, the final step in closing has the line GC.Collect()

This article mentions it too: http://support.microsoft.com/kb/317109

OG
If you don't use try / finally indeed it's a must to use GC.Collect as i had multiple excels stay open after tasks are done. However when i was using try / finally excel always closed properly. Of course in finally i always had 3x Marshal.ReleaseComObject(...);
MadBoy
Thanks. I added GC.Collect() to the end of my disposal step but still no luck.
Keith
A: 

Although this is C# but maybe it will help you out. I'm using this method to merge multiple documents into one. I pass all documents in Arraylist, and Word seems to close properly when done.

 public static void documentsMerge(object fileName, ArrayList arrayList) {
        // object fileName = Path.Combine(Environment.CurrentDirectory, @"NewDocument.doc");
        File.Delete(fileName.ToString());
        try {
            wordApplication = new ApplicationClass();
            var doc = wordApplication.Documents.Add(ref missing, ref missing, ref missing, ref missing);
            try {
                doc.Activate();
                int count = 0;
                foreach (var alItem in arrayList) {
                    addDocument(alItem, doc, count == 0);
                    count++;
                }
               // addDocument(@"D:\Projects\WordTests\ConsoleApplication1\Documents\Doc1.doc", doc ) ; //, false);
               // addDocument(@"D:\Projects\WordTests\ConsoleApplication1\Documents\Doc2.doc", doc ) ; //, true);
                doc.SaveAs(ref fileName, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
            } finally {
                doc.Close(ref missing, ref missing, ref missing);
            }
        } finally {
            wordApplication.Quit(ref missing, ref missing, ref missing);
        }
    }

The finally block is useful for cleaning up any resources allocated in the try block as well as running any code that must execute even if there is an exception. Control is always passed to the finally block regardless of how the try block exits.

So try to put your code into try / finally block and see how it behaves then?

For VB.NET

Try
' Statement which can cause an exception.
Catch x As Type
' Statements for handling the exception
Finally
End Try 'Any cleanup code
MadBoy
Thanks but adding the try..catch..finally block doesn't help. I've narrowed it down further to the fact that I'm using a custom template...please see the updates in my original post.
Keith
+1  A: 

Try calling GC.WaitForPendingFinalizers() and using Marshal.FinalReleaseComObject instead of Marshal.ReleaseComObject. This gets rid of the need to loop it.

Update your code to this and try it (the GC calls are in the beginning on purpose):

GC.Collect()
GC.WaitForPendingFinalizers()

oDoc.Close()
Marshal.FinalReleaseComObject(oDoc)

Marshal.FinalReleaseComObject(oDocuments)

oWord.Quit()
Marshal.FinalReleaseComObject(oWord)

You might also want to check out this related question discussing the issue for Excel.

Ahmad Mageed
Thanks for the tip. It didn't solve my problem but this looks like a cleaner and more stable way of cleaning up COM objects.
Keith
+2  A: 

Have you tried changing

oWord.Visible = False

to

oWord.Visible = True

?

I ask because Word may be asking you to do something that's related to this template you are trying to use. If it thinks there's a dialog showing, it will normally not shut down. IIRC, there's a way to do Quit so that it forces Quit and won't wait on any dialogs. But, it's been a while.

Jim Leonardo
Usually it is a good idea to set `Application.DisplayAlerts = wdAlertsNone`. This prevents most of Word dialog boxes to pop up (unfortunately some rare dialogs could still be blocking, e.g. a message about uninstalled proofing tools) .
0xA3
A: 

You should not discard the document object created in Word.Documents.Add. Save and call Marshal.ReleaseComObject on every COM object you get from automation when you are done, that is, if you do not cache the objects anywhere.

Sheng Jiang 蒋晟
Microsoft recommends discarding every object that is referenced (in this case that includes the Document object) in the article that OG posted: http://support.microsoft.com/kb/317109
Keith
looks like we have different understand of discarding. Do you discard by clean up?
Sheng Jiang 蒋晟
A: 

I figured out that the use of Documents.Add() when using a custom template is to blame. I can't explain why this would leave WinWord.exe hanging. However there are other ways to create documents from templates that don't result in the same problem.

So I replaced:

Dim oDoc As Word.Document = oDocuments.Add(Template:=CObj(sTemplatePath))

with:

Dim oDoc As Word.Document = oDocuments.Add()
oDoc.AttachedTemplate = sTemplatePath
oDoc.UpdateStyles()

Using AttachedTemplate to specify the template works for me and doesn't leave WinWord.exe hanging.

(One new issue has arisen however... An image in the template's footer does not get copied to the document when using AttachedTemplate/UpdateStyles. I'm taking that up as a separate issue. But since this method solves my original problem, I'm satisfied. Thanks to everyone who offered answers!)

Keith
Please note that this is *not* a general problem with templates, but it is specific to the template that you are using. In general, you original code is the way to create documents from a template.
0xA3
As a follow up -- There is definitely a memory leakage issue when using the Word interop to create docs based on templates, and even when working with existing docs created from templates. I believe this happens because such documents cause a Template com object to be automatically created and added to the Application object's Templates collection, thus not giving the developer the chance to reference and properly dispose of it.Another work-around I found is to use OpenXml to open an existing document (not a template) and insert into content controls.
Keith
A: 

Hey,

I got the same problema when i was doing it:

object missing = System.Reflection.Missing.Value;
wordApplication.Quit(ref missing, ref missing, ref missing);

I solved this way:

object objFalse = false;
wordApplication.Quit(ref objFalse, ref objFalse, ref objFalse);

Don't ask me why, automating office is an adventure :)

heringer
A: 

(All of my advice is adapted from this answer about Excel interop.)

There are a few important things here:

1) Never use 2 dots on the same line. Also consider an indexer as a dot

Good

Word.Documents d = wordApp.Documents;
Word.Document aDoc = d.Open(/*...*/);

BAD

Word.Document aDoc = wordApp.Documents.Open(/*...*/);

2) Release all of your pointers.

3) No really, go back and release all of your pointers, you missed one somewhere (or at least I always do).

Here's a full example of what FINALLY worked for me on one project after much wailing and gnashing of teeth:

object m = Missing.Value;
// this must be an object, not a string. if you forget though,
// intellisense will remind you
object oFilename = @"C:\my sheet.doc";

object readOnly = false;
object isVisible = false;

Word.Application wordApp = new Word.ApplicationClass();
wordApp.Visible = false;
// remember: don't use 2 dots on 1 line
Word.Documents d = wordApp.Documents;
Word.Document aDoc = d.Open(ref oFilename, ref m, ref readOnly, ref m,
    ref m, ref m, ref m, ref m, ref m, ref m, ref m, ref isVisible,
    ref m, ref m, ref m, ref m);
aDoc.Activate();

object findText = "my old value";
object replaceText = "new and improved value";

object oTrue = true;
object oFalse = false;
object replace = 2;
object wrap = 1;

Word.Selection s = wordApp.Selection;
Word.Find f = s.Find;
f.Execute(ref findText, ref oTrue,
    ref oTrue, ref oFalse, ref oFalse,
    ref oFalse, ref oTrue, ref wrap, ref oFalse,
    ref replaceText, ref replace, ref oFalse, ref oFalse,
    ref oFalse, ref oFalse);

aDoc.SaveAs(ref oFilename, ref m, ref m, ref m, ref m, ref m, ref m,
    ref m, ref m, ref m, ref m, ref m, ref m, ref m, ref m, ref m);

object doNotSaveChanges = Word.WdSaveOptions.wdDoNotSaveChanges;
// casting here because intellisense complained of ambiguity
(aDoc as Word._Document).Close(ref doNotSaveChanges, ref m, ref m);

// release each in the reverse of the order in which it was first used
// ReleaseComObject might also work as well. I haven't tested yet
Marshal.FinalReleaseComObject(f);
Marshal.FinalReleaseComObject(s);
Marshal.FinalReleaseComObject(aDoc);
Marshal.FinalReleaseComObject(d);

// must quit app before releasing
// again: casting because intellisense complained of ambiguity
(wordApp as Word._Application).Quit(ref m, ref m, ref m);
Marshal.FinalReleaseComObject(wordApp);
Dinah