How close Winword.exe in my application - c#

When i started my app and opened word file i see 3 process winword.exe in taskmanager.
After i called 'close' function 1 process winword.exe closed.
When i called worddoc.close() or wordapp.quit() in destructor, i got exception "COM object that has been separated from its underlying RCW cannot be used."
public class WordHelper
{
private object nullobj = System.Reflection.Missing.Value;
public string context = "";
Microsoft.Office.Interop.Word.Document doc = new Document();
Microsoft.Office.Interop.Word.Application wordApp = new Application();
public WordHelper(string FileName)
{
//Open word file
}
//somefunction fo work with file
public void CloseWord()
{
doc.Close();
wordApp.Quit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(wordApp);
}
~WordHelper()
{
//i got exception
doc.Close();
wordApp.Quit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(wordApp);
}
}
How i call my class
WordHelper wddoc = new WordHelper("C:\\Test Word\\Test.docx");
wddoc.CloseWord(); //this line i use and can close 1 process not 3
//One process close after i close application
At the end i want close all winword.exe which was opened by my application, and i want to close them in destructor. At the end, i need to close all 'winword.exe' which was opened by my application, and i need to close them in destructor.

You are doing this in a finalizer (different than a destructor). Finalizers are non-deterministic, meaning by the time they run class members may have already been finalized and thus would no longer be valid.
I would implement the Dispose pattern and explicitly control the lifetime of your word COM objects. This answer has a lot of good links that may help you.

//To find out the WINWORD.exe process and set RealTime Priority to WINWORD.exe
//This is used to quick process
Process[] proces = Process.GetProcessesByName("WINWORD");
foreach (Process proc in proces)
{
proc.PriorityClass = ProcessPriorityClass.RealTime;
}

Related

C# : Close Excel file without popup

So basically my app triggers an excel macro, from a file, that updates the file and then closes it.
When I open the file I set the "DisplayAlerts = false" variable in order to ignore all popups and it works as expected in my computer... however, a colleague of mine tried to use it and for every file, he gets the popup asking if he wants to save all changes...
Checked other questions about the popups in excel but all suggested solutions use "oBook.Saved = true;" or "oBook.Close(false);", but these did not work for me.
my code is as follows:
using Microsoft.Office.Interop.Excel;
public static bool Trigger_Macro_From_File(string path)
{
ApplicationClass oExcel = null;
Workbook oBook = null;
try
{
string filename = Path.GetFileName(path);
string macro_name = "!some_macro";
string macro = #"'" + filename + #"'" + macro_name;
// Create an instance of Microsoft Excel
oExcel = new ApplicationClass
{
DisplayAlerts = false,
Visible = false
};
oBook = oExcel.Workbooks.Open(path);
RunMacro(oExcel, new Object[] { macro });
oBook.Save();
oBook.Saved = true;
return true;
}
catch (Exception ex)
{
return false;
}
finally
{
oBook?.Close(false);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oBook);
oBook = null;
oExcel?.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcel);
oExcel = null;
GC.Collect();
}
}
Does anyone know anything about this?
Thanks in advance.
You could double-check that no other "Microsoft Excel" process is running in the Task Manager.
Let's say at some point in your development process you started your program and open the workbook with something like
xlWorkbook = xlApp.Workbooks.Open(filePath);
Then you encountered an exception for some reason, and killed the program without closing the file properly (workbook.Close(..), app.Quit(..) and so on).
The Microsoft Excel process is still running in the background, and has a handle on the file you want to edit. So you cannot execute an instruction that saves the file under the same name. This is why the popup is appearing.
This scenario is taken from the point of view of the developer, but the same behavior could have happened on your coworker's computer if your app crashed without quitting properly, and gets re-started.
Also, be careful that finally statement might not always be executed, so double-check which scenario could cause your app to close without releasing the COM object.

The process cannot access the file 'C:\file.txt' because it is being used by another process

I am trying to log each method on my program, I have the application deployed on IIS Server and the user just called me and said the email functionality is not working so I need to basically run the application but log each step into a txt file.
I am declaring the below as a global value:
StreamWriter writer = new StreamWriter("C:\\file.txt");
Then I use it like below in my code:
Method 1
{
if (file1.HasFile)
{
writer.WriteLine("Has File");
}
}
Method 2
private Boolean InsertUpdateData(SqlCommand cmd)
{
writer.WriteLine("Insert Started" + DateTime.Now.ToString());
}
So in my case method one runs fine and it writes Has File but when it goes into the second method I get the file is already open which is correct how can I work around this?
Thanks
Global Value - declared at top
namespace WorkOrderManagement
{
public partial class CreateWorkOrder : System.Web.UI.Page
{
bool successfull;
string path;
string name;
string content;
string datas;
string ext;
bool affectedrows;
string seasonalsupervisor;
private string sLogFormat;
private string sErrorTime;
StreamWriter writer = new StreamWriter("C:\\file.txt");
I really suggest you to discard the idea to have a global variable to represent a stream and then try to use it in different methods. This is simple in a desktop application, but a lot more complex in an ASP.NET application.
There are simple alternatives that could atomically write your log text and leave the file unlocked.
For example you could have a method like this
public static class Log
{
public static string _file = "log.txt";
public static object _locked = new object();
public static void AppendToLog(string text)
{
lock(_locked)
{
string path = Server.MapPath("~/APP_DATA");
File.AppendAllText(Path.Combine(path, _file), text + Environment.NewLine);
}
}
}
Now you can call the log write with
Log.AppendToLog("My message");
I want to underline two important things here. First I don't write in the root drive of the server. This is a bad practice and always a source of problems when you deploy your ASP.NET application in a server where you have not permissions to use anything outside your site root. Thus the ASP.NET system defines a particular folder called APP_DATA under your site root where your application should have read/write permissions.
Second point to notice is the use of the lock keyword. This is necessary in an environment like ASP.NET where two users could reach a point of the code where you need to write to the common log file. As MSDN explains it
The lock keyword ensures that one thread does not enter a critical
section of code while another thread is in the critical section. If
another thread tries to enter a locked code, it will wait, block,
until the object is released.
you can also do this to close the file stream
using (StreamWriter writer = new StreamWriter("C:\\file.txt"))
{
//your code here
}
//this automatically closes the stream, and it is more recommended.
Close the stream after writing the file
Method 1
{
if (file1.HasFile)
{
writer.WriteLine("Has File");
writer.Close();
}
}

Does matter COM object not assigned to var is not released?

Will be all unmanaged COM objects released in case if I use code like this
var worksheet = new Application().Workbooks.Add().Worksheets.Add();
Marshal.ReleaseComObject(worksheet);
instead of code like this
var excel = new Application();
var workbook = excel.Workbooks.Add();
var worksheet = workbook.Worksheets.Add();
Marshal.ReleaseComObject(excel);
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(worksheet);
?
If there is some documentation please send a link in answer.
Actually, both code samples will leave an Excel process running in the background. You need to call Application.Quit() on the application object, for example. The following works:
private static void DoExcel()
{
var application = new Application();
var workbook = application.Workbooks.Add();
var worksheet = workbook.Worksheets.Add();
// Name that this will be saved as
string name = workbook.FullName + ".xlsx";
string fullPath = Path.Combine(Directory.GetCurrentDirectory(), name);
// If a file of the same name exists, delete it so that we won't be prompted if
// we want to overwrite it when we save
if (File.Exists(fullPath))
File.Delete(fullPath);
// Save the workbook - otherwise we may be prompted as to whether we want to save when we go to quit
workbook.Save();
// Quit the application
application.Quit();
// Release the references
Marshal.ReleaseComObject(worksheet);
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(application);
// Release the .NET reference and run the garbage collector now to make sure the application is closed immediately
worksheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
A few other good things to remember: I didn't use it here, but there's a Marshal.FinalReleaseComObject method that's very useful in these cases. Also, again I didn't use this in my code sample, but the Marshal.ReleaseComObject method returns the current count, so you could always do the release in a loop if you wanted to make sure the count reached zero:
while (Marshal.ReleaseComObject(comObject) > 0) { }
You can also use this for debugging purposes - e.g.
int count = Marshal.ReleaseComObject(comObject);
Trace.TraceInformation("Current COM object reference count: " + count.ToString());

Identify process id of word instance using System.Diagnostics.Process object

I have code which starts Word application instance as follows
Microsoft.Office.Interop.Word.Application app = new Microsoft.Office.Interop.Word.Application();
app.Caption = "abcd_" + DateTime.Now.Ticks.ToString();
I now need to know process id of the word instance that was started.
I cannot use FindWindow to get window handle and GetWindowThreadProcessId to get process id from handle as the code does not work on Windows server 2008.
I get all word processes by using Process.GetProcessesByName("WINWORD").
Is there any property of Process which can give me value that we set in app.Caption ?
If not, is there any other property of Word.Application which I can set and later on read from Process array to identify correct instance of Word ?
You Could use the Process.MainWindowTitle property to judge if the process is by your code.
But there's some limitation:
When you using new Microsoft.Office.Interop.Word.Application(), the word windows is not visible by default. When it's hidden, the Process.MainWindowTitle is empty. So you show set it visible before you get the Pid.
After you open a document, the MainWindowTitle will change to the document's file name.
Here's my code:
static void Main(string[] args)
{
string uniCaption = Guid.NewGuid().ToString();
Word.Application oWordApp = new Word.Application();
oWordApp.Caption = uniCaption;
oWordApp.Visible = true;
Process pWord = getWordProcess(uniCaption);
//If you don't want to show the Word windows
oWordApp.Visible = false;
//Do other things like open document etc.
}
static Process getWordProcess(string pCaption)
{
Process[] pWords = Process.GetProcessesByName("WINWORD");
foreach (Process pWord in pWords)
{
if (pWord.MainWindowTitle == pCaption)
{
return pWord;
}
}
return null;
}
What about that (untestet):
Updated
Word.Application wdapp;
try
{
Process wordProcess = System.Diagnostics.Process.Start("Path to WINWORD.EXE");
wdApp = (Word.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Word.Application");
}
catch (COMException)
{
Type type = Type.GetTypeFromProgID("Word.Application");
wdapp = System.Activator.CreateInstance(type);
}
The wdApp should be the started word.
And you get the process id through wordProcess instance.

Excel Interop instances won't close

I am using Excel Interop. In the beginning of a method I got, I'm allocating a new instance of the application and at the end of it I'm trying to free it, but when I look at the TaskManager I can still see Excel open.
This is the code:
A class member: private Excel.Application _app;
The usage:
public void MethodApp()
{
_app = new Excel.Application();
....
....
FreeApplicationResources();
}
private void FreeApplicationResources()
{
_app.Quit();
Marshal.ReleaseComObject(_app);
}
MethodApp can run several times and it opens instances at the same quantity as the number of times it's called.
Why won't Excel close?
Try releasing any worksheets and workbooks used also in the order below:
Marshal.ReleaseComObject(_worksheet);
Marshal.ReleaseComObject(_workbook);
Marshal.ReleaseComObject(_app);

Categories