Microsoft Word Interop performance issue - c#

I have a winform program which loads Microsoft Word and performs some basic editing (find & replace), as well as some autosaving.
It loads an existing word template, which again is basic text.
The code for the interop is;
try
{
// Is Word running?
WordApp = System.Runtime.InteropServices.Marshal.GetActiveObject("Word.Application") as Microsoft.Office.Interop.Word.Application;
WordApp.Visible = true;
return WordApp;
}
catch (COMException ce)
{
WordApp = null;
if (ce.ErrorCode == unchecked((int)0x800401E3))
WordApp = new Microsoft.Office.Interop.Word.Application();
WordApp.Visible = true;
return WordApp;
}
Once the document is open, the user types what they need to but it has been noted that after a page or so of text, the performance really slows. There is an increasing lag on the users typing.
I thought initially it was due to some issues with the find/replace code so I have commented out everything apart from the code for loading the template;
WordApp = WordEdit.GetWord(); //Class & Method calling interop code
WordApp.Documents.Add(AppDomain.CurrentDomain.BaseDirectory + "\\Templates\\" + DocType + ".dot");
//set Active Document
WordDoc = WordApp.ActiveDocument;
The performance is still as poor.
I then thought to release the COM and set things to null, but again this didn't have any effect.
System.Runtime.InteropServices.Marshal.ReleaseComObject(WordApp);
WordApp = null;
WordDoc = null;
I then thought maybe my app is causing the system in general to slow down. If I quit the application but continue to use the Word application it load, the performance is still slow. If I start a fresh Word application (manually) this works perfectly. So it has something to do with the way my application is loading word. My application does not have any impact on system resources, and is currently set to do nothing other than load the template.
Is there either a different way get hold of Word (a different way to use interop) or a way to improve performance?

Although I haven't been able to pin point the cause of the slow performance, I have found a workaround.
Instead of having the app create a new Word session via the marshal, I instead use this code;
Process.Start(AppDomain.CurrentDomain.BaseDirectory + "\\Templates\\" + DocType + ".dot");
System.Threading.Thread.Sleep(2000);
This opens the required template (DocType+ extension) so the system creates a Word session.
I then use
WordApp = System.Runtime.InteropServices.Marshal.GetActiveObject("Word.Application") as Microsoft.Office.Interop.Word.Application;
WordApp.Visible = true;
return WordApp;
To take control of the now active session.
The ` System.Threading.Thread.Sleep(2000); is there because if it wasn't, things run too quickly and then my app can't get hold of the Active Document in the WordApp object. Giving it a few seconds wait gives it chance to catch up.
It's not ideal - but it has removed all performance issues,
`

Related

Getting 'Compile error in hidden module: SmartTagsMod' while opening a word document in MS Word 2010

I am converting a large number of MS Word documents to PDFs using the Interop library in a multi-threaded WPF application (.NET Framework 4). I get the following error on some word documents:
It blocks the current thread until I click OK on the dialog and then continues with the conversion and the converted PDF comes out to be fine as well.
This only happens on certain documents. I am running the application on multiple computers and this has occured on other computers too.
Below is my code for conversion:
var wordApplication = new Microsoft.Office.Interop.Word.Application();
// Opening the word document
var wordDocument = wordApplication.Documents.Open(tempFile, false, true, false, NoEncodingDialog: false);
// Exporting the document to the PDF
wordDocument.ExportAsFixedFormat(pdfPath, Microsoft.Office.Interop.Word.WdExportFormat.wdExportFormatPDF);
// Closing the document and the application.
((Microsoft.Office.Interop.Word._Document)wordDocument).Close(false);
((Microsoft.Office.Interop.Word._Application)wordApplication).Quit(false);
Marshal.ReleaseComObject(wordDocument);
Marshal.ReleaseComObject(wordApplication);
wordDocument = null;
wordApplication = null;
Does anyone know what could be causing this? Or whether I can close this dialog box from my application?
Thanks
wordApplication.AutomationSecurity = Microsoft.Office.Core.MsoAutomationSecurity.msoAutomationSecurityForceDisable;
This seem to have fixed the issue.
Thanks bibadia for providing the link to the question that had the fix.

How to open a word document file(.doc) in Editable mode in C#/WPF

When I run this code it opens file in Readonly mode but I want to open my document file in Editable mode.I have already readonly mode set to false.This is the code which i have using:-
Microsoft.Office.Interop.Word.Application winword = new Microsoft.Office.Interop.Word.Application();
Microsoft.Office.Interop.Word.Document document = new Microsoft.Office.Interop.Word.Document();
try
{
//Set status for word application is to be visible or not.
//Create a missing variable for missing value
object readOnly = false;
object missing = System.Reflection.Missing.Value;
object isVisible = true;
document = winword.Documents.Open(pathToFile, ReadOnly: false, Visible: true);
document.Activate();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(winword);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(document);
}
catch (Exception ex)
{
// WB.Close(false, Type.Missing, Type.Missing);
throw;
}
My guess is that you can't do it. Readonly mode is provided in part as a security measure to stop some macros from executing in Word. Thus, to provide an SDK overriding it would be a risk to end-users. You might be able to get around it by running the exe directly with a parameter of the filename, since then it would be like a person double-clicking the icon, but I'm not familiar with what libraries are or are not available in WPF. In good ol'-fashioned consoles or Windows Forms, you'd be looking at
System.Diagnostics.Process.Start([the path of Word.exe], pathToFile);
but you'd have to be careful about finding the path to Word.exe, since that could vary based on the version of word and what installation instructions were given. It's conceivable that those COM objects will expose the path somehow, since there's a good chance they'd need to know it, but yet again I'm not familiar with them.

System.AccessViolationException when using Excel.Worksheet.Copy

We have a VSTO addin for Excel. The main functionality creates reports that are used to generate workbooks. When I run a batch of reports, I get a System.AccessViolationException when using Excel.Worksheet.Copy, which also crashes Excel. Here's how I recreate it:
1) Open and run report #1 with a single parameter which creates one workbook. We close the workbook.
2) Open and run the same report with several parameters. This create 5 workbooks but crashes when creating the second, but ONLY if we have run the first single output report (see step 1). If we remove the report from step 1 from the batch, this creates all 5 workbooks without error.
I've checked to make sure that the sheet we are copying is from the workbook is open, and is not referencing the first report. In fact, we close the first one so I know that it's not. Again, this ONLY happens if we have the report in step one, which it does not access at all, so how could that be affecting a sheet from a completely different workbook?
This doesn't even finish out my try/catch so that I can get more info. It simply blows up Excel and I have to restart.
UPDATE:
Here's the basic code:
function void ReplaceSheets(Dictionary<Excel.Worksheet, IReportSheet> sheetReports)
{
List<string> oldNames = new List<string>(sheetReports.Count);
foreach (Excel.Worksheet oldSheet in sheetReports.Keys)
{
Excel.Worksheet veryHiddenSheet = null;
Excel.Worksheet newSheet = null;
try
{
string sheetName = oldSheet.Name;
veryHiddenSheet = WorkbookHelper.FindSheet(this.DocumentView, MakeHiddenSheetName(sheetName, "--VH--"));
veryHiddenSheet.Visible = Excel.XlSheetVisibility.xlSheetVisible; //Sheet has to be visible to get the copy to work correctly.
veryHiddenSheet.Copy(this.DocumentView.Sheets[1], Type.Missing);//This is where it crashes
newSheet = (Excel.Worksheet)this.DocumentView.Sheets[1]; //Get Copied sheet
/* do other stuff here*/
}
finally
{
veryHiddenSheet = null;
newSheet = null;
}
}
}
I never found a way in VSTO to "fix" this. I switched code to NetOffice, and I was able to get some better error message. Excel/Com was not releasing the memory attached to the spreadsheets. I rebuilt the reports from blank 2010 spreadsheets and it took care of it. I think it was a corrupted 2007 spreadsheet that may have occured on converting to 2010 or something like that. I recommend NetOffice over VSTO because the exception handling is far superior, and you have access to the source code, but it does have it's quirks. (You'll need to pay attention to loading order for taskpanes.)

Powerpoint could not open file

I am developping a C# programm which creates a PowerPoint presentation. However, I am running into a problem with the following instruction:
Presentation pres = pres_set.Open(path,
Microsoft.Office.Core.MsoTriState.msoFalse,
Microsoft.Office.Core.MsoTriState.msoTrue,
Microsoft.Office.Core.MsoTriState.msoTrue);
This instruction works only sometimes. If it doesn't, it throws a exception with the message "PowerPoint could not open file". When I then manually open the template file, close it and execute the function again, most of the time it will execute correctly.
I have used the Microsoft Powerpoint 14.0 and the Microsoft Powerpoint 12.0 libraries, but both have the same problem.
Is there any way to avoid this strange problem?
The answer is much simpler: The Powerpoint-application expects an existing file.
I just had the same exception because I used a relative path, and PowerPoint tried to open the file relative to PowerPoint.exe instead of your Program.exe.
This problem can quickly be fixed by adding something like this before calling the open-method:
// Start Powerpoint
var powerpointApp = new Microsoft.Office.Interop.PowerPoint.Application();
// Get a fileInfo object to generate the full path
FileInfo fileInfo = new FileInfo(#"RelativeDir\YourPresentation.pptx");
// Use the full path
powerpointApp.Presentations.Open(fileInfo.FullName, MsoTriState.msoTrue, WithWindow: MsoTriState.msoFalse);
Have you tried not setting the TriState, like this?
Object oMiss = System.Reflection.Missing.Value;
Presentation pres = pres_set.Open(ref path, ref oMiss, ref oMiss, ref oMiss);
I had the same problem while trying to open an existing PowerPoint presentation until I found out that Visual Basic for Applications (VBA) was not installed on my Office installation options as described here:
http://www.debugging.com/bug/22261
Apparently, this problem only happens when working with PowerPoint as I have no problems manipulating Excel and Word files.
The problemĀ“s gone away as soon as I repaired my Office, including the VBA to the installation.
Hope it helps!
I had a similar problem with PowerPoint. I found my Presentations.Open method was failing unless I had PowerPoint open.
One potential solution is to set PowerPointApplication.Visible = MsoTriState.msoTrue However, this will cause PowerPoint to physically open which is likely to be undesirable.
I resolved my issue by setting the final argument of the Open method to msoFalse, which specifies that the PowerPoint window should not open on the server which is more desirable.
Presentations.Open(inputFileName,
MsoTriState.msoFalse,
MsoTriState.msoTrue,
MsoTriState.msoFalse);
Take a look at this MSDN KB article for more information on the different parameters of the Open method.
ProcessStartInfo ten_ct = new ProcessStartInfo();
ten_ct.FileName = "POWERPNT.EXE";
ten_ct.Arguments = #"D:\project\GiaoAn\GiaoAn\MyPpt.pptx";
Process.Start(ten_ct);
I've seen problems like this with Excel, even when I try to launch it as a user with a command-line file to open. So it may not be anything wrong with your program. When I do this as a user, usually it works the second time.
So my advice would be either
1) For your program to try opening Power Point first without the file, wait (start with 5 seconds), and then have it try to load the file.
or
2) Your program can catch the exception, and simply try opening the file again if it fails (and if you find this works, you should add a maximum number of tries, so the program doesn't loop trying to do this all day). You could optionally also check to see if the file exists (if that's a possibility in your scenario - but it doesn't sound like this is currently the problem you are facing).
Try this:
Presentation pres = pres_set.Open(path,
Microsoft.Office.Core.MsoTriState.msoFalse,
Microsoft.Office.Core.MsoTriState.msoTrue,
Microsoft.Office.Core.MsoTriState.msoFalse);
I solve this problem using:
var app = new PP.Application();
PP.Presentation pres = null;
try
{
Process.Start(inputFile);
var presCol = app.Presentations;
// Waiting for loading
Thread.Sleep(2000);
pres = presCol[inputFile];
// Your code there
// ...............
}
catch (System.Exception ex)
{
Log.Error(ex.Message);
throw;
}
finally
{
if (pres != null)
{
pres.Close();
}
if (app != null)
{
app.Quit();
}
pres = null;
app = null;
}

Why am I getting an Out of Memory Error doing ASP .NET Excel Interop?

This was working..and I moved the disposal code to the finally block, and now it fails every time.
I have a test spreadsheet with 4 records, 6 columns long. Here is the code I'm using to bring it in. This is ASP .Net 3.5 on IIS 5 (my pc) and on IIS 6 (web server).
It blows up on the line right before the catch: "values = (object[,])range.Value2;" with the following error:
11/2/2009 8:47:43 AM :: Not enough storage is available to complete this operation. (Exception from HRESULT: 0x8007000E (E_OUTOFMEMORY))
Any ideas? Suggestions? I got most of this code off codeproject, so I have no idea if this is the correct way to work with Excel. Thanks for any help you can provide.
Here is my code:
Excel.ApplicationClass app = null;
Excel.Workbook book = null;
Excel.Worksheet sheet = null;
Excel.Range range = null;
object[,] values = null;
try
{
// Configure Excel
app = new Excel.ApplicationClass();
app.Visible = false;
app.ScreenUpdating = false;
app.DisplayAlerts = false;
// Open a new instance of excel with the uploaded file
book = app.Workbooks.Open(path);
// Get first worksheet in book
sheet = (Excel.Worksheet)book.Worksheets[1];
// Start with first cell on second row
range = sheet.get_Range("A2", Missing.Value);
// Get all cells to the right
range = range.get_End(Excel.XlDirection.xlToRight);
// Get all cells downwards
range = range.get_End(Excel.XlDirection.xlDown);
// Get address of bottom rightmost cell
string downAddress = range.get_Address(false, false, Excel.XlReferenceStyle.xlA1, Type.Missing, Type.Missing);
// Get complete range of data
range = sheet.get_Range("A2", downAddress);
// get 2d array of all data
values = (object[,])range.Value2;
}
catch (Exception e)
{
LoggingService.log(e.Message);
}
finally
{
// Clean up
range = null;
sheet = null;
if (book != null)
book.Close(false, Missing.Value, Missing.Value);
book = null;
if (app != null)
app.Quit();
app = null;
}
return values;
I'm not sure if this is your issue or not, but it very well may be. You are not cleaning up your excel objects properly. They are unmanaged code and can be tricky to clean up. Finally should look something like this: And as the comments have noted working with excel from asp.net is not a good idea. This cleanup code is from a winform app:
GC.Collect();
GC.WaitForPendingFinalizers();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(range);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sheet);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(book);
WB.Close(false, Type.Missing, Type.Missing);
Excel.Quit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(Excel);
EDIT
An alternative would be to use ado.net to open the workbook.
DataTable dt = new DataTable();
string connectionString;
System.Data.OleDb.OleDbConnection excelConnection;
System.Data.OleDb.OleDbDataAdapter da;
DataTable dbSchema;
string firstSheetName;
string strSQL;
connectionString = #"provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filename + #";Extended Properties=""Excel 12.0;HDR=YES;IMEX=1""";
excelConnection = new System.Data.OleDb.OleDbConnection(connectionString);
excelConnection.Open();
dbSchema = excelConnection.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null);
firstSheetName = dbSchema.Rows[0]["TABLE_NAME"].ToString();
strSQL = "SELECT * FROM [" + firstSheetName + "]";
da = new OleDbDataAdapter(strSQL, excelConnection);
da.Fill(dt);
da.Dispose();
excelConnection.Close();
excelConnection.Dispose();
Creating/destroying excel on every request will have absolutely terrible performance to matter what you do. In general running any Office app using automation is a nasty business for a lot of reasons (see here). The only way I got it to work is to have a single instance of the app (in my case Word) which is initialized once, and then requests are queued to this instance for processing
If you can stay away from the apps and parse the file yourself (using MS libraries, of just XML)
You're going to run into a lot of trouble using interop from ASP.NET. Unless this is meant for some tiny in house application it would be advisable not to go forward with it.
Office Interop is not a programming API in the traditional sense - it's the Office macro system taken to its maximum, with the ability to work interprocess - for example an Excel macro could interact with Outlook.
Some consequences of using interop are:
You are actually opening a full copy of the office application.
Your actions are being executed by the application as if a user initiated them - that means instead of error messages being returned in code, they are displayed in the GUI.
Your copy of the application only closes if you explicitly command it - and even then errors can prevent that from actually happening (the application may present a "do you want to save" dialog if you did not programmably tell Excel that the changes do not need to be saved). This usually results in many hidden copies of Excel being left running on the system - open task manager and see how many excel.exe processes are running.
All of this makes interop something to avoid for regular desktop application, and something that should only be used as a last resort for server applications, since a GUI popup requiring action or script that leaks process is murder on a server environment.
Some alternatives include:
Using Microsoft Office 2007 XML based formats, so that you can write the XML files yourself.
Using SpreadsheetGear.Net, which is a .NET binary Excel file reader/writer (you don't need Excel installed, as it is completely stand alone). SpreadsheetGear models itself after the Intertop interfaces to make conversion of older code easier.
The error is probably exactly what it says, you are getting an out of memory error. Try to split the loading of the values array into several smaller chunks instead of getting the entire Range at one time. I tried your code out in C# and had no issues, but my spreadsheet I was loading was mostly empty.
I noticed that the Range was the entire spreadsheet though (from A2 to IV65536 or something). I'm not sure if that is intended.
One thing you could try using is sheet.UsedRange, that will cut down on the number of cells you are loading.
A couple additional small things that I have learned which you may find useful:
Use Application instead of ApplicationClass
Use Marshal.FinalReleaseComObject(range) (and also for sheet, book, app) otherwise you will have your EXCEL.EXE process sticking around.

Categories