I have the following snippet of code. It works (opens all the Word documents in a directory and then closes them down)...but it doesn't clean up after itself when I totally exit the program.
By this I mean that if I look at the TaskManager once I exit the VB.NET application I see the WINWORD.EXE even though it did not exist before I opened the application.
Here's the declarations I have:
Dim WordApp As Microsoft.Office.Interop.Word.Application
Dim aDoc As Microsoft.Office.Interop.Word.Document
Dim missing As Object = System.Reflection.Missing.Value
Dim nullobj As Object = System.Reflection.Missing.Value
Dim MYreadOnly As Object = False
Dim isVisible As Object = False
And here's the code:
Private Sub cmdGenerate_Click(sender As System.Object, e As System.EventArgs) Handles cmdGenerateKeywords.Click
Dim xmldoc As New XmlDataDocument()
Dim xmlnode As XmlNodeList
Dim i As Integer
Dim fs As FileStream
WordApp = New Microsoft.Office.Interop.Word.Application
WordApp.Visible = False
For Each f As FileInfo In New DirectoryInfo(txtFolderName.Text).GetFiles("*.docx")
' Open the document that was chosen by the dialog
aDoc = WordApp.Documents.Open(f.FullName, missing, [MYreadOnly], _
missing, missing, missing, missing, missing, missing, missing, _
missing, isVisible)
'aDoc.Close()
aDoc = Nothing
Next
'Close the Word Document
'aDoc.Close(nullobj, nullobj, nullobj)
WordApp.Application.Quit()
WordApp = Nothing
End Sub
As you can tell I've commented and uncommented various statements in regards to closing down Word documents and the Word Application itself. Nothing I have tried seems to be able to get rid of that pesky WINWORD.EXE
Something seems to have a lock and will not let it close down? Is that it?
Run explicitly Garbage Collector, as is shown in this article:
// Clean up the unmanaged Word COM resources by forcing a garbage
// collection as soon as the calling function is off the stack (at
// which point these objects are no longer rooted).
GC.Collect();
GC.WaitForPendingFinalizers();
// GC needs to be called twice in order to get the Finalizers called
// - the first time in, it simply makes a list of what is to be
// finalized, the second time in, it actually is finalizing. Only
// then will the object do its automatic ReleaseComObject.
GC.Collect();
GC.WaitForPendingFinalizers();
Despite link above, my experience is that run once is enough. But second call doesn't throw an error, so do it this way.
fs.Close()
fs.Dispose()
http://msdn.microsoft.com/en-us/library/system.io.filestream.dispose.aspx
This releases your resources. Might allow other close methods to work. I read through the code twice and do not see you using "fs" anywhere like I would expect.
Related
I want to access a Lotus Notes Database and get attachments from documents in it.
I can open the DB and doc and loop through all items.
The problem is, I can not use the items as NotesRichTextItem and therefore not check if there are any item.EmbeddedObject.
I guess it is a problem with declaration of the items.
In general: If I debug using VS2010, doc and the database and NotesSession have the "value" System.__ComObject and "type" is the Domino.Notes Object it should be.
e.g. doc in WATCH:
Name VALUE TYPE
doc {System.__ComObject} Domino.NotesDocument
but if I use the doc.GetType() command the result is
doc.GetType() = {Name = "__ComObject" FullName = "System.__ComObject"}
Since I do not know if my doc.item is a NotesRichTextItem, I define it as an object and want to check afterwards is type. Which I can't since the return value of the functions is as above for doc, too.
Here is the complete code I use currently, I loaded the Lotus Domino reference from the COM section.
Public Sub OpenDocumentLN()
Try
Dim ns As New Domino.NotesSession
Dim db As Domino.NotesDatabase
Dim doc As Domino.NotesDocument
Dim view As Domino.NotesView
If Not (ns Is Nothing) Then
ns.Initialize()
db = ns.GetDatabase("", sLotusNotesPath & sLotusNotesDB, False)
If Not (db Is Nothing) Then
view = db.GetView(sLotusView)
doc = view.GetFirstDocument
While Not doc Is Nothing
Dim lnNextDoc As Domino.NotesDocument = view.GetNextDocument(doc)
For Each item As Domino.NotesItem In doc.Items
Dim rtItem As Object = doc.GetFirstItem(item.Name)
If rtItem Is Nothing Then Continue For
If Not rtItem.GetType() = GetType(Domino.NotesRichTextItem) Then Continue For
' NEVER reach this part of the code since the IF clause prevents it due to the type problem
If rtItem.EmbeddedObjects Is Nothing Then Continue For
For Each o As Domino.NotesEmbeddedObject In rtItem.EmbeddedObjects
o.ExtractFile(sLotusExportPath & o.Source)
Next
Next
doc = lnNextDoc
End While
End If
db = Nothing
ns = Nothing
End If
Catch ex As Exception
End Try
End Sub
How can I use my rtitem as a NotesRichTextItem so I can handle it appropiate? And why are all objects are treated als ComObjects?
I cannot access .Count property of Sheets. I'm using Excel Interop. I'm in debug mode and I'm trying this:
?xlSheets.Count
This results in:
(1) : error BC30456: 'Count' is not a member of 'Sheets'.
I have no clue on what's wrong, as I see in MSDN that there is such property!
This works well: ?xlSheets(1).Name. But Count fails... Is it possible to get the count of sheets?
These guys had a similar problem - they wanted to .Worksheets.Add(.Worksheets.Sheets.Count). Finally they did not get the count, they went for .Worksheets.Add(After:=.Worksheets(3))...
UPDATE:
To my great delight, after further trying / experimentations, it became clear that in debug modeSheets.Count does not work only when there is no such line in the code.
While debugging this code, I can access Sheets.Count, because this line exists in the code.
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Form1
Dim xlApp As Excel.Application
Dim xlWorkBook As Excel.Workbook
Dim xlWorkbooks As Excel.Workbooks
Dim xlSheets As Excel.Sheets
Private Sub btnCreate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCreate.Click
xlApp = New Excel.Application
xlWorkbooks = xlApp.Workbooks
xlWorkBook = xlWorkbooks.Open("C:\Temp\Template.xlsm")
xlSheets = xlWorkBook.Sheets
MessageBox.Show(xlSheets.Count)
xlWorkBook.Close()
xlApp.Quit()
'Clean Up
releaseObject(xlSheets)
releaseObject(xlWorkBook)
releaseObject(xlWorkbooks)
releaseObject(xlApp)
End Sub
Private Sub releaseObject(ByVal obj As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
GC.WaitForPendingFinalizers()
End Try
End Sub
End Class
But when I replace MessageBox.Show(xlSheets.Count) with MessageBox.Show(xlSheets.Creator), the error appears when trying to ?xlSheets.Count. I don't yet know the reason of such behaviour (I come from VBA environment where debug mode seems to be more flexible), but at least that works during run time...
If someone knows how to fix this, please let me know, as I feel restricted while testing small things in debug mode!
Use Project > Properties > References. Locate and select the "Microsoft Excel xx.x Object Library" entry. In the Properties window, set the Embed Interop Types property to False. Use Build > Rebuild to rebuild your app. It will now work the way you expected.
Briefly, this option is a strong optimization for COM interop libraries, like Microsoft.Office.Interop.Excel, you no longer have a runtime dependency on the library. The compiler copies the interop types from the library into your program's executable, only the ones you actually need to run your program. Explains your discovery, the Count property is in fact missing when you don't use it in your program.
You don't want to leave it this way, set the property back to True after you're done testing.
Your code works fine for me. I notice the file type is a macro-enabled workbook. Have you set your macro settings properly on your dev PC? By default Excel will disable macros.
Edit: I think I get your problem now. You are getting the error when trying to print the property in debug mode. Probably you have stopped the code at a point where the variable is not set (.Count is only available while the button code is actually running). Put a breakpoint on the message box line, click the button, and try again.
I am hoping you can help me here, in the past you all have been great. I have tried every variation of the kill script for killing excel from vb.net, to no avail.
First I can't post explicit code on here because it is my company's proprietary software, but I can tell you a few things. Also there are over 28,000 lines of code.
I am not using
Imports Excel = Microsoft.Office.Interop.Excel
due to the fact that we have to accommodate different variations of clients software. I am creating the new excel as an object as such
Dim XLObj As Object = CreateObject("Excel.Application")
I have seen this used on several other sites but the kill function they are using is when you save and then close it, which I'm not doing.
The error message I am getting says that "Com object that has been separated from its underlying RCW cannot be used". I'm not sure where this com object is because I have released the sheets, workbook and then the application.
Oh and I don't want to use the excel.kill() because if a client already has the excel open I don't want to kill it without saving it. I only want to kill the newly generated excel process that doesn't have a window open associated with it.
My questions are as follows
I need to be able to close the Excel application when/if the open fails. So say I am click a link and it opens the dialog box to select an Excel template to load but either the data from the database is corrupt or the sql statement is broken. The program throws and error and then Excel should close in the Task Manager. Unfortunately it doesn't close hence the problem.
is there a way to close only the newly created process id? I have tried to use the directions here but it doesn't work either. When I do that it gives me a different error "Value cannot be null Parameter name: o". The line that is throwing the error is on (from the link)
Marshal.FinalReleaseComObject(tempVar)
I only tried this because we are using the With on the XLObj. The With is in reference to the workbook itself so shouldn't it be released when I close the workbook? And being as I'm causing it to error on purpose at the moment it shouldn't reach the With statement anyway.
Is there a way to tell which com object is not closing?
Things I have tried:
This releaseObject that I found on the internet. (don't ask me where I've been through about 75 pages)
Private Sub releaseObject(ByRef obj As Object)
Try
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj)
If obj Is Nothing Then
Else
obj = Nothing
End If
Catch ex As Exception
If obj Is Nothing Then
Else
obj = Nothing
End If
Finally
GC.Collect()
GC.WaitForPendingFinalizers()
End Try
End Sub
This is used in conjunction with this function (which was pieced together from the many sites I have been on)
Public Sub CloseExcel(ByRef WorkBook As Object, ByRef Application As Object)
Dim xLSheet As Object = WorkBook.Sheets
For Each xLSheet In WorkBook.Sheets
If xLSheet IsNot Nothing Then
releaseObject(xLSheet)
End If
If xLSheet IsNot Nothing Then
Kill(xLSheet)
End If
Next
If WorkBook IsNot Nothing Then
WorkBook.Close(False)
End If
If WorkBook IsNot Nothing Then
Kill(WorkBook)
End If
releaseObject(WorkBook)
If Application IsNot Nothing Then
Application.Quit()
End If
If Application IsNot Nothing Then
Kill(Application)
End If
releaseObject(Application)
GC.Collect()
GC.WaitForPendingFinalizers()
Application.Quit()
End Sub
and because it is also referenced the Kill function
Public Sub Kill(ByRef obj As Object)
Try
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj)
Catch ex As Exception
MessageBox.Show("moduleExcel.Kill " & ex.Message)
Finally
obj = Nothing
End Try
End Sub
any help would be greatly appreciated.
Ok so for those of you having this exact same issue. I do have a solution for you. Yes the above code does work but for a few minor adjustments.
you need to take out all the code in the CloseExcel sub and place it EXACTLY where you want it to close. So if you want it to close if the program errors out, put after the catch statement. You cannot call a Sub and pass in your objects and expect it to kill the process.
you need a few bits above the opening of the new Excel process. and they are as follows.
'declare process for excel
Dim XLProc As Process
'loads the financials excel bookmarks
'this will be where you declare your new excel opbject
Dim XLObj As Object = CreateObject("Excel.Application")
'get window handle
Dim xlHWND As Integer = XLObj.hwnd
Dim ProcIDXL As Integer = 0
'get the process ID
GetWindowThreadProcessId(xlHWND, ProcIDXL)
XLProc = Process.GetProcessById(ProcIDXL)
and of course you will need the GetWindowThreadProcessId which I got from the link I included in the original question. I am posting it here so you don't have to search for it.
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Private Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer
End Function
This code will only close the single process you have it associated with, it will not close other open Excel files. Our clients sometimes will have multiple files open and we don't want to close them without telling them. This KILLS the Excel process that was created at run time when the system Errors out.
I have a launcher utility I wrote that uses Directory.GetFiles() on a Timer to keep track of shortcuts in the start menu.
It has a memory leak, however. I'm not doing anything strange, so I don't understand why it's leaking... I leave the program open and after a few days, it's at 300mb. I used the CLR Profiler to try to locate the leak and it says the memory leakage is coming from String instances allocated by Directory.GetFiles and Directory.GetFileNameWithoutExtension Here's the code I'm using:
Private Sub tmr_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmr.Tick
IndexStartMenu()
GC.Collect()
End Sub
Private Sub IndexStartMenu()
Dim startMenu As IO.DirectoryInfo
Dim shortcuts() As IO.FileInfo
'Enumerate current user's start menu
startMenu = New IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu))
shortcuts = startMenu.GetFiles("*.lnk", IO.SearchOption.AllDirectories)
For Each lnk As IO.FileInfo In shortcuts
Dim newRow As DataRow = dtApps.NewRow
newRow("Application") = IO.Path.GetFileNameWithoutExtension(lnk.FullName)
newRow("Window") = "Launch"
newRow("Hwnd") = ""
newRow("IsShortcut") = True
newRow("ShortcutPath") = lnk.FullName
dtApps.LoadDataRow(newRow.ItemArray, LoadOption.Upsert)
newRow = Nothing
Next
'Enumerate all users' start menu
startMenu = New IO.DirectoryInfo(allUsersStartMenuPath)
shortcuts = startMenu.GetFiles("*.lnk", IO.SearchOption.AllDirectories)
For Each lnk As IO.FileInfo In shortcuts
Dim newRow As DataRow = dtApps.NewRow
newRow("Application") = IO.Path.GetFileNameWithoutExtension(lnk.FullName)
newRow("Window") = "Launch"
newRow("Hwnd") = ""
newRow("IsShortcut") = True
newRow("ShortcutPath") = lnk.FullName
dtApps.LoadDataRow(newRow.ItemArray, LoadOption.Upsert)
newRow = Nothing
Next
'Trying to fix memory usage
startMenu = Nothing
Array.Clear(shortcuts, 0, shortcuts.Length)
shortcuts = Nothing
End Sub
Based on the method you posted, wouldn't the timer just fire every interval and add the contents of those directories repeatedly? If dtApps is a DataTable field scoped to the class which persists for the duration of the application, you are just repeatedly adding the rows to the DataTable causing it to grow. It is not a memory leak, but a natural event. Check the row count of your dtApps. My guess is that you are intending to only add new rows.
Also, you could improve the solution above and eliminate the need to poll the two directories based on a timer by employing a FileSystemWatcher. The FileSystemWatcher will notify you by firing an event when there is a change to the file system.
I am trying to compile and run code at runtime. I am using the below code to achieve this. However, when i trying to invoke the method, simply a "Find Source" file browser dialog opens and the code is not run. Can anyone please help me here.
Dim VBP As New VBCodeProvider
Dim CVB As System.CodeDom.Compiler.ICodeCompiler
CVB = VBP.CreateCompiler
Dim PM As New System.CodeDom.Compiler.CompilerParameters
PM.GenerateInMemory = True
PM.GenerateExecutable = True
PM.OutputAssembly = "RunCode.dll"
PM.MainClass = "MainClass"
PM.IncludeDebugInformation = True
Dim ASM As System.Reflection.Assembly
For Each ASM In AppDomain.CurrentDomain.GetAssemblies
PM.ReferencedAssemblies.Add(ASM.Location)
Next
Dim CompileResults As System.CodeDom.Compiler.CompilerResults
CompileResults = CVB.CompileAssemblyFromSource(PM, sCode)
Dim CompileErrors As System.CodeDom.Compiler.CompilerError
For Each CompileErrors In CompileResults.Errors
RTMainScript.AppendText(vbCrLf & CompileErrors.ErrorNumber & ": " & CompileErrors.ErrorText & ", " & CompileErrors.Line)
Next
Dim objRun As New Object
Dim vArgs() As Object
objRun = CompileResults.CompiledAssembly.CreateInstance("RunCode.MainClass", False, BindingFlags.CreateInstance, Nothing, vArgs, Nothing, Nothing)
If Not objRun Is Nothing Then
Dim oMethodInfo As MethodInfo = objRun.GetType().GetMethod("Main")
Dim oRetObj As Object = oMethodInfo.Invoke(objRun, BindingFlags.Static Or BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic, Nothing, Nothing, Nothing) 'Find source dialog appears here
Else
MsgBox("Compile Error")
End If
The code you provided is incomplete. You are using this method to compile the code:
CompileResults = CVB.CompileAssemblyFromSource(PM, sCode)
But you actually never specified what sCode is. If you are getting an open file browser dialog, then I am quite sure that your sCode is the cause of it. It must have been set somewhere while calculating the variable value to open a file.
If you are trying to change a piece of code that was used to compile from a file then changing the method from CompileAssemblyFromFile() to CompileAssemblyFromSource() is not enough. You need to dig more into the code and change all related methods.
Ensure your threading model is STA.
OpenFileDialog and similar objects will not operate correctly if the threading model is set to MTA.
If you must use MTA for some other reason then you can create your own custom OpenFileDialog class; sort of sucks.