How does a windows.forms.form close itself

Answered How does a windows.forms.form close itself

  • Wednesday, May 30, 2012 3:15 PM
     
     

    This is a bit embarrasing but ...

    I created a new windows form application using VB 2010. I added no controls to the form. This is right "out of the box" app created by the VB windows form app wizard. I next added a FormClosed event handler. The handler gets called whenever I click the "x" on the form.

    Later I needed to close the form programatically in response to an event from an automation client. So I did something apparently farily dumb. I called "Me.Close()"

    Nothing happens. The form stays up on the screen. My FormClosed event handler is NOT called.

    The MSDN entry for "Close" doesn't give me a clue as to why this call doesn't work. Indeed, it seems to imply it should work.

    So I added a couple of lines of code after calling "Close()" since when I stepped across that line of code it appears that no other code would execute. Sure enough, as soon as I step over the call to Close(), I cannot step through any other lines of code.

    Then I found another piece of form code while searching around and I found the code had what appeared to be an odd keyword just dangling in the code. The keyword, colored by the IDE as a VB keyword?

    End

    So I typed "End" into my method and intellisense shows me "End Statement. Stops execution immediately". And it does. But is my FormClosed event handler called. No.

    I added "Inherits System.Windows.Forms.Form" to see if that had any effect. No change.

    So how does a form close itself programatically?


    R.D. Holland

All Replies

  • Wednesday, May 30, 2012 3:26 PM
     
     Proposed Answer

    End does not stop a form, it kills it with all kind of bad behaviour with that. For a form application made with the application framework Close is the only good way. 

    If you have created your own Sub Main and therefore start the application with Application.Run(theform) you can at the end of that sub main set Application.Exit

    Be aware that Me is the class you are in so you cannot call that from another class than the main form to close the program if it is windows forms.


    Success
    Cor

  • Wednesday, May 30, 2012 4:01 PM
     
     Proposed Answer Has Code

    Me.Close is correct.

    Create a new windows forms project and add a button to the form to prove it.

    Public Class Form1
    
      Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Me.Close()
      End Sub
    End Class
    

    May we all make money in the sequel.

  • Wednesday, May 30, 2012 6:21 PM
     
     Answered

    Me.Close() is not correct in my case. I have found that I am getting a .NET exception (once I turned on trapping all first chance exceptions).

    What I get is an exception that says I am trying to close my form in a different thread than the one in which the form was created.

    So how does this happen? .NET magic. I used the VS VB "WIndows Form application" wizard. When I build and run, my form is automatically created for me. In my form_Load event handler, I connect up to a COM application and I also connect up to an event source using the "withevents" keyword.

    When a certain event is raised, I need to close my form. But the event handler of my form cannot call Close since apparently the event is handled in a different thread.

    So I still need to close the form programatically, but Close() is not an option. If this was not .NET, I would just simply call PostMessage(m_hWnd, WM_CLOSE, 0, 0). But alas, there appears to be no PostMessage API. Heck, my .NET is so bad that I have not even figured out how to get a handle to my window, let alone call PostMessaage.

    Ah, I think I have it. I found system.windows.forms.application.exit(). When I call it, the form_closed event is raised.

    I have added a reference to a COM type lib and it has an application object. So I could not just call "application.exit" as exit is not a member of the COM application. Even ME.application.exit fails to be a good line of code too. I guess I need to bone up on scoping in VB .NET.


    R.D. Holland

    • Marked As Answer by RD Holland Wednesday, May 30, 2012 6:21 PM
    •  
  • Wednesday, May 30, 2012 6:31 PM
     
     Answered Has Code

    Cross thread call eh.  Now that is a different beast all together.  You need something like this

      ' This delegate enables asynchronous calls for closing
      ' the form.
      Private Delegate Sub CloseFormCallback()
      Public Sub CloseForm()
        ' InvokeRequired required compares the thread ID of the
        ' calling thread to the thread ID of the creating thread.
        ' If these threads are different, it returns true.
    
        If Me.IsHandleCreated AndAlso Me.InvokeRequired Then
          Dim d As New CloseFormCallback(AddressOf CloseForm)
          Me.Invoke(d)
        Else
          Me.Close()
        End If
      End Sub

    This way the caller of CloseForm could be on another thread and the CloseForm routine will check to see if InvokeRequired and if so then perform a callback on the form's thread.

    May we all make money in the sequel.

    • Marked As Answer by RD Holland Wednesday, May 30, 2012 10:47 PM
    •  
  • Wednesday, May 30, 2012 10:47 PM
     
     

    Thanks Dave, looks promising. But unless I need to not call appliction.exit, that seems to work fine for me since in my case, my exe is a windows form application. But if I didn't want the app to exit and just wanted to close a form, I think your solution looks promising.

    So I plagarized your code and tried it out. I commented out my call to application.Exit and called CloseForm(). I stepped thru the call (I have a breakpoint there) and got to Me.Invoke(d) and sure enough, CloseForm() is called and the second (recursive call) goes to the "Else" clause and calls Me.Close(). And to top it off, my breakpoint in form1_formclosed event handler is called. Oh so close. But that's it. The executable locks up. I never get back to the CloseForm subroutine. And the form stays on the desktop. When I move the mouse over the form, the "busy" cursor appears. I have to kill the app off myself.

    I thought I had exited form_closed but if I break into the process, I see that the "lockup" is due to me making a call back to the COM application. I'm guessing I have deadlock as I am now trying to call back to the COM app from a different thread!

    Oh, I can fix that in this particular case.  Your code works fine if I did not have to interop with a COM app. COM interop via .NET. What a joy!


    R.D. Holland

  • Wednesday, May 30, 2012 11:04 PM
     
     

    R.D.,

    I haven't read through all of this - but enough to see that you're confused on a few things.

    You don't need a FormClosed event - unless you want to do something special after the form has closed (which clearly you don't).

    Simply calling "Me.Close" (or just "Close()") is sufficient to close the form and assuming it's a program with just one form, turn off the program. Sometimes you may want to use the form's Closing event (not closed), but for what you're doing there, you don't even need that.

    End is not a solution and is dangerous. Beware of that.

    I'm assuming that maybe you're experimenting with a button to turn the program off or something? If so, "Close()" is sufficient to your needs.

    For what it's worth...


    Please call me Frank :)

  • Thursday, May 31, 2012 1:40 AM
     
      Has Code

    R.D.,

    I haven't read through all of this - but enough to see that you're confused on a few things.

    You don't need a FormClosed event - unless you want to do something special after the form has closed (which clearly you don't).

    Simply calling "Me.Close" (or just "Close()") is sufficient to close the form and assuming it's a program with just one form, turn off the program. Sometimes you may want to use the form's Closing event (not closed), but for what you're doing there, you don't even need that.

    End is not a solution and is dangerous. Beware of that.

    I'm assuming that maybe you're experimenting with a button to turn the program off or something? If so, "Close()" is sufficient to your needs.

    For what it's worth...


    Please call me Frank :)

    Frank,

    He is handling an event from a ComObject to call the close event.  These events are raised on a different thread, so just doing a "Me.Close" will cause a cross-thread operation not valid exception.  To demo the issue, try this code that uses Excel interop.  Run in the debugger.  Close the Excel app when it appears and observe the results.

    ' Add project reference to MS excel object library
    Imports Microsoft.Office.Interop
    
    
    Public Class Form1
    #If DEBUG Then
       Private Const Minimize As Int32 = 6
       Private Const Restore As Int32 = 9
       Private session As Process
    #End If
    
       Dim WithEvents app As New Excel.Application
       Delegate Sub CloseMe()
       Private MainThread As Threading.Thread = Threading.Thread.CurrentThread
    
       Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
          app.Visible = True
          app.Workbooks.Add()
    
    #If DEBUG Then
          session = Process.GetProcessesByName("devenv").First(Function(p As Process) p.MainWindowTitle Like (My.Application.Info.AssemblyName & "*"))
          ShowWindow(session.MainWindowHandle, Minimize)
    #End If
          Me.WindowState = FormWindowState.Minimized
       End Sub
    
       Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
          app.Quit()
          app = Nothing
       End Sub
    
    
       Private Sub app_WorkbookBeforeClose(ByVal Wb As Microsoft.Office.Interop.Excel.Workbook, ByRef Cancel As Boolean) Handles app.WorkbookBeforeClose
          Try
             Me.Close()
          Catch ex As Exception
             app.Visible = False
             MsgBox(String.Format("Main thread ID: {0}, Trigger Event Thread ID:  {1}, close using invoke", New Object() {MainThread.ManagedThreadId, Threading.Thread.CurrentThread.ManagedThreadId}))
             Me.Invoke(New CloseMe(AddressOf Me.Close))
          End Try
    
       End Sub
    
    #If DEBUG Then
       <Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True, CharSet:=Runtime.InteropServices.CharSet.Auto)> _
       Private Shared Function ShowWindow(ByVal hwnd As IntPtr, ByVal nCmdShow As Int32) As Boolean
       End Function
    #End If
    
    End Class


    • Edited by TnTinMN Thursday, May 31, 2012 1:42 AM typo
    •  
  • Thursday, May 31, 2012 1:50 AM
     
     

    I never got beyond the first paragraph - which will certainly teach me to read it all!

    *LOL*

    Point made ...


    Please call me Frank :)

  • Thursday, May 31, 2012 12:36 PM
     
     
    The application.exit is compensating for something not being right.  It sounds like your on the right track with the COM object.  Something about the use of that object is not being cleaned up and disposed of properly.

    May we all make money in the sequel.

  • Thursday, May 31, 2012 1:14 PM
     
     

    Dave,

    Apparently I just traded off one threading issue for another. When the COM app raises the event, I used your Invoke code and the form did close. But while I was cleaning up my COM references I hit another deadlock. The deadlock occurs when I call Marshal.ReleaseComObject.

    But not always. I have three COM objects I need to release. Two of the three had no problem when I called Marshal.ReleaseComObject. But I had a feeling I know why this happens. The object that caused the lockup is an object for which I have declared "withevents" (and it is the object to which I am responding by closing my form). So I declared another one of the three "withevents" since it too provides events. As soon as I click the object in the object dropdown list and then pick one of the events in the object's list of events, I lock up when I call ReleaseComObject (no issue until an event is implemented) when closing my form.

    I figured it was "withevents" that causes the lockup because in this same app I already found that after I call ReleaseComObject on the object "withevents" I MUST NOT do this:

    objXXX = nothing

    For when I execute that line, I get yet another .NET runtime exception telling me RCW has been disconnected from the underlying COM object. It appears I will have to do events the hard way so that I am in charge of connecting and especially disconnecting from the events.

    Is there is a way to force all of this into a single threaded application? I see there is an App.config file created for the project. Can I add an entry there to force the app to be STA based? I searched all the project settings but didn't see anything related to threads. I also looked at the "(ApplicationSettings) entry of the form's properties but saw nothing to use (I can have more than one form so I expected that). I have searched and I see there is an [STAThread] attribute that people are putting above their "Main" function. But my winform app has no "Main" that I can find.

    As you can see, my experience as a programmer is lacking when it comes to .NET. I'm trying to create project templates for VB 6 users (non CS engineers for the most part) who have been forced to move to .NET when working with our app's automation system. Even the simplest programs in VB6 that work with our COM app, when ported to .NET, give them fits. I'm beginning to understand why! It looks like they have to worry about COM lifetimes, the intricacies of events connecting and disconnecting and now threading too. I see why some engineers have told us that VB is devolving and why they have held onto VB 6 for so long.


    R.D. Holland

  • Thursday, May 31, 2012 1:34 PM
     
     

    Frank :),

    Actually I do want to clean up RCWs. I figured form_closed was a good place to do that. I don't have a button. Well actually I do. I have the "x" button. But there is no issue with closing the form via that button. What I am actually doing is interoping with a COM application by posing as a command in the application. The user can terminate a command in that app by hitting the escape key. At that point the command automation object in the COM app fires a "terminate" event to which I respond.

    VB 6 users code this stuff up in a matter of minutes using our API. And so did I with VB .NET. But whereas the VB 6 user is completely done in a matter of minutes, the .NET user has a lot of other things to work out that VB 6 handled automatically.

    Next up - trying IDispose to clean up the COM references.


    R.D. Holland


    • Edited by RD Holland Thursday, May 31, 2012 1:35 PM
    •  
  • Thursday, May 31, 2012 1:50 PM
     
      Has Code

    Based on what you have stated so far it sounds like the root problem is that the Form that creates the COM object is then attempting be destroyed by the COM object (i.e. the COM object's event triggers the close).  Either the Form or the COM object need to be the master, the other the slave.  There is more information that I don't know as to why this situation exists though.

    If the COM object needs to have a lifetime beyond that of the Form then its object reference and related resources should be at a higher level than the Form.  This has a little to do with your inquiry about Sub Main.  Here is how to layout your project to start with Sub Main.

    Add a new Module to the project and put the sub main routine in it.

    Module Module1
    
      Public Sub Main()
    
        Dim f As System.Windows.Forms.Form = New Form1
        Application.Run(f)
    
      End Sub
    
    End Module
    

    Open the Project's "My Project" and go to the Application tab. Uncheck "Enable application framework". Now the "Startup form" drop down will show a choice of Sub Main. Choose Sub Main.

    Just like in VB6 objects declared at the Module level have a higher scope that within the Form.

    It sounds like the COM objects need to be declared, instantiated, and ultimately destroyed at the Module level given the interaction described so far of the COM object event triggering the close of the form.


    May we all make money in the sequel.

  • Thursday, May 31, 2012 2:40 PM
     
     

    Frank :),

    Actually I do want to clean up RCWs. I figured form_closed was a good place to do that. I don't have a button. Well actually I do. I have the "x" button. But there is no issue with closing the form via that button. What I am actually doing is interoping with a COM application by posing as a command in the application. The user can terminate a command in that app by hitting the escape key. At that point the command automation object in the COM app fires a "terminate" event to which I respond.

    VB 6 users code this stuff up in a matter of minutes using our API. And so did I with VB .NET. But whereas the VB 6 user is completely done in a matter of minutes, the .NET user has a lot of other things to work out that VB 6 handled automatically.

    Next up - trying IDispose to clean up the COM references.


    R.D. Holland


    A better idea - not one that's easily done - would be to totally rebuild these objects as dot NET classes which support IDisposable. Get rid of those old COM objects.

    Please call me Frank :)

  • Thursday, May 31, 2012 5:29 PM
     
     

    Frank :),

    15 million.

    That's how many lines of c++ code and assembly code we have. And that doesn't count MFC and other third party toolkits we use.

    Besides, when .NET came out we did a bit of work to determine how it handles apps that have extremely large memory needs. .NET failed those tests miserably. Perhaps it is better now, but I doubt it. I know the memory management team did some work to differentiate big blocks of memory versus small blocks. That might mean we can easily open gigabyte design files now (lucky for us and our customers that there is that /3GB switch supported by 32 bit Windows and of course now there is 64 bit addressing).

    Perhaps when the OS is based on .NET, we will move too!


    R.D. Holland

  • Thursday, May 31, 2012 7:35 PM
     
     

    Frank :),

    15 million.

    That's how many lines of c++ code and assembly code we have. And that doesn't count MFC and other third party toolkits we use.

    Besides, when .NET came out we did a bit of work to determine how it handles apps that have extremely large memory needs. .NET failed those tests miserably. Perhaps it is better now, but I doubt it. I know the memory management team did some work to differentiate big blocks of memory versus small blocks. That might mean we can easily open gigabyte design files now (lucky for us and our customers that there is that /3GB switch supported by 32 bit Windows and of course now there is 64 bit addressing).

    Perhaps when the OS is based on .NET, we will move too!


    R.D. Holland

    YOW!

    Boy I've put my foot in my mouth twice in this thread - best I just shut up now!

    I hope you do get resolution on it though. Good luck with it!


    Please call me Frank :)

  • Friday, June 01, 2012 2:13 PM
     
     

    Frank :),

    I keep getting into deadlock situations. So now I am going to try Dispose. But I have no experience with Dispose on a VB Form (I have implemented it on my own base classes in other situations but all I got was errors when I tried to do a full implementation as I have in other classes I created). So I found a KB article and tried to implement Dispose based on the article. The article is generic, it was not for a Form. When I build I got error BC30269. It turns out there is a Form1.Designer.vb file that mysteriously shows up when I double click the error in the output window. This file is not in my solution window under the project (I only have Form1.vb, app.config and "My Project".

    I don't mind much the hiding of this extra code. But how am I supposed to implment my own code during the Dispose operation? Is the correct way to get into the Dispose pipeline to edit this auto-generated code? I'd hate to modify that code and find out that something I do later regerates that code!

    Don't worry about "sticking" your foot in your mouth :) I appreciate any replies and help I can get. Oh, by the way, I was told our line count is approaching 25 million. I think someone must have done a count that included the project I am currently working on! Once I have a good solution and toss out all the other things I have tried, the line count will go back down.


    R.D. Holland

  • Friday, June 01, 2012 2:20 PM
     
     

    R.D.,

    I can't help with the dispose part - not at the level you're at right now - but as for the "hidden file", it's just not being presently shown is all. In Solution Explorer, hover your mouse over the toolbare and one of them (I think second from the left) will be "Show All Files". When you toggle that then you'll see all of the designer files also.


    Please call me Frank :)

  • Friday, June 01, 2012 3:00 PM
     
     

    Frank :),

    I keep getting into deadlock situations. So now I am going to try Dispose. But I have no experience with Dispose on a VB Form (I have implemented it on my own base classes in other situations but all I got was errors when I tried to do a full implementation as I have in other classes I created). So I found a KB article and tried to implement Dispose based on the article. The article is generic, it was not for a Form. When I build I got error BC30269. It turns out there is a Form1.Designer.vb file that mysteriously shows up when I double click the error in the output window. This file is not in my solution window under the project (I only have Form1.vb, app.config and "My Project".

    I don't mind much the hiding of this extra code. But how am I supposed to implment my own code during the Dispose operation? Is the correct way to get into the Dispose pipeline to edit this auto-generated code? I'd hate to modify that code and find out that something I do later regerates that code!

    Don't worry about "sticking" your foot in your mouth :) I appreciate any replies and help I can get. Oh, by the way, I was told our line count is approaching 25 million. I think someone must have done a count that included the project I am currently working on! Once I have a good solution and toss out all the other things I have tried, the line count will go back down.


    R.D. Holland

    This might seem a bit trivial in all this talk of muti-threading and COM Interop, but in mind of keeping things simple, have you tryed using your forms disposed event?

    Programming is easy, understanding how is not.

  • Friday, June 01, 2012 3:50 PM
     
     

    PEng1,

    I just finished working in the Dispose method. I had to toss my Dispose code after I wrote it because it turns out the Form already has that method in the "designer" .vb file (revealed when I did a build and got an error). So I added my "cleanup" code there. And I still lockup. I simply cannot seem to disconnect from an event set unless the user specifically closes the form via the "x" button. Meanwhile I have C++ code that has absolutely no problem disconnecting from an event set. But forcing VB6 programmers to move the C++ is not a viable option. Oh, yes, there is absolutely no problem in VB 6.

    I have now resorted to a pure hack. When the COM app fires an event to terminate the automation client (my VB code), I set a flag. When Dispose is called, I check the flag. If it is set, I simply leave the connection point as is. If the user terminates via closing the form directly, the flag is not set and I disconnect from the COM events with no problem. What does that tell me?

    I am currently trying to determine exactly where execution is when the lockup occurs. I have two debuggers running, one for the COM app and one for my VB form. In the VB form code, when I call IConnectionPoint.Unadvise, the call never comes back. Debug break is of little use as there is little I can discern from the call stack and I can't seem to get the IDE to even let me go into disassembly mode so I can track exactly where I am at.

    Meanwhile while VB is sitting doing nothing, I can go to the other debugger and break into the app and I see I am waiting for the event I fired to the automation client to return (that's on my main thread). I have examined the other threads (in both apps) and in my COM app I have not yet found any call from managed code in any of the threads.

    I did have to implement an OleMessage filter and that is yet another variable the VB 6 user did not have to deal with but which the VB .NET programmer does. That code has been pulled off the MSDN (I didn't pull it but that appears to be the source).


    R.D. Holland

  • Friday, June 01, 2012 4:10 PM
     
     

    Dave,

    My latest attempt to avoid lockup by putting my code in Dispose doesn't work either. I still get a lockup when I close the form using your cool invoke methodology. I have a breakpoint set in my COM app on the Unadvise (MS ATL) function. When I hit the close button, dispose calls my cleanup code, which in turn disconnects from the event source and my breakpoint trips. But when I terminate via the event and call Invoke, Dispose is called (on the main thread :) ) but no call makes it to my COM app. Breaking into the VB process I find that the main thread is locked down:

      [In a sleep, wait, or join] 
      mscorlib.dll!System.Threading.WaitHandle.WaitOne + 0x2f bytes 
      mscorlib.dll!System.Threading.WaitHandle.WaitOne + 0x25 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle + 0x89 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke + 0x31c bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke + 0x50 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke + 0x7 bytes 
    > SEMacro.exe!SEMacro.Form1.CloseForm Line 156 + 0xd bytes Basic
      SEMacro.exe!SEMacro.Form1.Terminate Line 203 + 0xa bytes Basic
      [Native to Managed Transition] 

    I have no ideal what .NET is waiting on. Does the above stack give any clue to you? I have managed to deadlock both apps at once if I dare try to make any call other than ReleaseComObject. If I avoid the call to disconnect and any call to set a property on my COM object, no lockup in this workflow occurs. But attempt to make any call and I am hosed.


    R.D. Holland

  • Friday, June 01, 2012 4:11 PM
     
     

    R.D.,

    I wasn't meaning to write your own Dispose method, but rather to add a reference to the Form.Disposed Event handler.

    Since the COM app is attempting to Terminate the automation client, is it also instatiating it?

    Peng1


    Programming is easy, understanding how is not.

  • Friday, June 01, 2012 4:24 PM
     
     

    RD,

    My gut still says the problem is the event raised by the COM object is still in the call stack when the form is attempting to be closed.

    If that is the case you could try something to prove it.  Put a timer on the form  (disabled with a 50 ms interval) and have the COM object event enable the timer.  In the timer event make the call to close form.


    May we all make money in the sequel.

  • Friday, June 01, 2012 6:09 PM
     
     

    PEng1,

    The COM app is running and the VB form app is started by the user. So neither starts the other. Both are separate processes. The VB form app calls into my COM app and creates a command. The user interacts with both the VB form and the COM app (via the command the form app started). When the user terminates the command in the COM app, the command is notifying the form app that the user is finished.

    I simply want the form to go away when the user terminates the command in the COM app, and I want the command in the COM app to go away when the user closes the form. The latter is no problem, the former is spinning my head. I outlined a hack, which isn't really too bad, elsewhere in this thread.


    R.D. Holland

  • Friday, June 01, 2012 6:22 PM
     
     

    Dave,

    Yes the com event call is still active as all this happens then. Normally I would easily handle this in a normal windows program by having the form app post WM_CLOSE or some such message to itself. Perhaps I should PInvoke. I really liked your code for closing from a different thread using Invoke. But I may have to go back to the Application.Exit() call. I have tested both termination methods (click x, kill command in COM app) and when the event to terminate is received by the VB form, calling application.exit() results in Dispose being called and my cleanup code called from there can disconnect from the event set (and I break in the ATL Unadvise routine to verify). I am just looking for the "best" or most correct way to unwind all this complexity. Perhaps Exit() is the way to go. I see a number of other programmers have simly called "End" (which I personally dislike doing and think is wrong).

    By the way, I posted the wrong call stack earlier. That stack was from the worker thread created via your cool Invoke code. The main thread, which is what I am on when Dispose is called thanks to the Invoke code you posted is more like this:

      [Managed to Native Transition] 
      SEMacro.exe!SEMacro.Form1.ConnectToEvents Line 57 + 0x10 bytes Basic
      SEMacro.exe!SEMacro.Form1.AddOrRemoveEventHandlers Line 30 + 0x48 bytes Basic
      SEMacro.exe!SEMacro.Form1.CleanUP Line 72 + 0x10 bytes Basic
      SEMacro.exe!SEMacro.Form1.Dispose Line 10 + 0xa bytes Basic
      System.dll!System.ComponentModel.Component.Dispose + 0x12 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Form.WmClose + 0x239 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc + 0x1c2 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage + 0x10 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc + 0x31 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback + 0x57 bytes 
      [Native to Managed Transition] 
      [Managed to Native Transition] 
      System.Windows.Forms.dll!System.Windows.Forms.Control.SendMessage + 0x24 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Form.Close + 0x39 bytes 
    > SEMacro.exe!SEMacro.Form1.CloseForm Line 158 + 0xa bytes Basic


    R.D. Holland

  • Friday, June 01, 2012 7:10 PM
     
     

    R.D.

    What type of object is this command. Does it have any type of event that signals it's lifetime has ended? Otherwise the only thing that I can think of would be sometype of callback function, but, from my limited knowledge of your code, that doesn't seem to fit here. Not to mention it's not exactly a straight forward thing to do in VB.


    Programming is easy, understanding how is not.

  • Friday, June 01, 2012 7:19 PM
     
     

    Yea but probably much better ways, you was in noway telling that you use the me close on another thread. Then we would not have a so long thread but was the answer given in seconds or some hours.


    Success
    Cor

  • Friday, June 01, 2012 7:58 PM
     
     

    RD,

    When you mention posting a WM_CLOSE to the program's window handle and that would work.  The reason that works (don't know all the facts, i'm not a native guy) is the WM_CLOSE message is put in the message queue where the window would next process its message when it has a free cycle.

    While that COM event raised back to the .NET form is still in process the Form is not in a state where it can process messages in the queue.  The call stack has to complete and then the message pump can resume processing messages.

    The use of the Timer control mentioned earlier is a simple way to prove this very scenario.  See, when the COM event raises and sets the Timer to enabled then event call finishes its work and is no longer on the call stack.  Then separately, 50 ms later, the timer fires and calls the closeform.  This is mearly a way to prove that the program needs to reach a point where the call stack is no longer tied to the COM object before attempting to close the form when the reference to that very COM object exists in the form.

    If there was not a form level reference to the COM object, then when the form went to close and cleanup, it would not be cleaning up the COM object reference it has.  The attempt to clean up the COM object reference which is still tied to the call stack that is triggering the clean up is the point of contention,  my gut says.


    May we all make money in the sequel.

  • Friday, June 01, 2012 9:04 PM
     
     

    Cor,

    If I knew it was on another thread the first time I posted, I would have indicate that is so. So what is your solution that comes in seconds not hours? I'm not so thrilled with either of my solutions.


    R.D. Holland

  • Friday, June 01, 2012 9:19 PM
     
      Has Code

    Dave,

    Yes the com event call is still active as all this happens then. Normally I would easily handle this in a normal windows program by having the form app post WM_CLOSE or some such message to itself. Perhaps I should PInvoke.

    RD,

    I'm not certain that would work in this case where you would be sending the message in the form's event handler for your COM event, but it is worth I try.  I tried it with that demo app I posted the other day and it failed.  When sending a message to itself, you can you the control's(Form's) WndProc Method like this:

            Me.WndProc(New System.Windows.Forms.Message() With {.Msg = WM_Close})

    The following is food for thought and probably a waste of time, but it was a fun exercise.

    Establish a communication link between the two apps using messages.  This way the COM app could have the handle for the WinForm app and send WM_Close to it instead of the WinForm app handling the event (it seems as this is where everything gets out of wack).  To demo the concept, the following code is two WinForm apps. 

    App1:

    Imports System.Runtime.InteropServices
    
    Public Class Form1
    
       Private Const WM_EstablishContactString As String = "EstablishContact"
       Private WM_EstablishContact As Int32 = CInt(RegisterWindowMessage(WM_EstablishContactString))
       Private Const HWND_BROADCAST As Int32 = &HFFFF
    
       Private App2handle As IntPtr
    
       Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
          MsgBox("closing")
       End Sub
    
    
       Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
          MyBase.OnHandleCreated(e)
          'Incase handle get recreated for any reason, do this here
          'Broadcast mesage to all windows.  Put this app's handle i wParam
          Dim messageposted As Boolean = PostMessage(New IntPtr(HWND_BROADCAST), WM_EstablishContact, Me.Handle, IntPtr.Zero)
       End Sub
    
       Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
          If m.Msg = WM_EstablishContact AndAlso m.WParam <> Me.Handle Then
             'Store App2 handle to allow subsequent communications
             App2handle = m.WParam
          End If
          MyBase.WndProc(m)
       End Sub
    
    
    '-------- API Functions
    
       <DllImport("user32.dll", CharSet:=CharSet.Auto)> _
       Private Shared Function RegisterWindowMessage(ByVal lpString As String) As UInt32
       End Function
    
       <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
       Private Shared Function PostMessage(ByVal hWnd As IntPtr, _
                                           ByVal Msg As Int32, _
                                           ByVal wParam As IntPtr, _
                                           ByVal lParam As IntPtr) As Boolean
       End Function
    
       <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
       Private Shared Function SendMessage(ByVal hWnd As IntPtr, _
                                           ByVal Msg As Int32, _
                                           ByVal wParam As IntPtr, _
                                           ByVal lParam As IntPtr) As IntPtr
       End Function
    End Class
    

    App2:

    Imports System.Runtime.InteropServices
    
    Public Class Form1
    
       Private Const WM_EstablishContactString As String = "EstablishContact"
       Private WM_EstablishContact As Int32 = CInt(RegisterWindowMessage(WM_EstablishContactString))
       Private Const HWND_BROADCAST As Int32 = &HFFFF
       Private Const WM_Close As Int32 = &H10
    
       Private App1Handle As IntPtr
    
       Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
          If App1Handle <> IntPtr.Zero Then SendMessage(App1Handle, WM_Close, IntPtr.Zero, IntPtr.Zero)
       End Sub
    
    
       Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
          MyBase.OnHandleCreated(e)
          'Incase handle get recreated for any reason, do this here
          'Broadcast mesage to all windows.  Put this app's handle i wParam
          Dim messageposted As Boolean = PostMessage(New IntPtr(HWND_BROADCAST), WM_EstablishContact, Me.Handle, IntPtr.Zero)
       End Sub
    
       Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
          If m.Msg = WM_EstablishContact AndAlso m.WParam <> Me.Handle Then
             App1Handle = m.WParam 'Extract contacting app handle
             'Send message back to App1 to complete handshake
             SendMessage(App1Handle, WM_EstablishContact, Me.Handle, IntPtr.Zero)
          End If
          MyBase.WndProc(m)
       End Sub
    
    '-------- API Functions
    
       <DllImport("user32.dll", CharSet:=CharSet.Auto)> _
       Private Shared Function RegisterWindowMessage(ByVal lpString As String) As UInt32
       End Function
    
       <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
       Private Shared Function PostMessage(ByVal hWnd As IntPtr, _
                                           ByVal Msg As Int32, _
                                           ByVal wParam As IntPtr, _
                                           ByVal lParam As IntPtr) As Boolean
       End Function
    
       <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
       Private Shared Function SendMessage(ByVal hWnd As IntPtr, _
                                           ByVal Msg As Int32, _
                                           ByVal wParam As IntPtr, _
                                           ByVal lParam As IntPtr) As IntPtr
       End Function
    End Class
    


  • Friday, June 01, 2012 9:40 PM
     
     

    Peng1,

    The command is an API provided by our COM app that lets automation clients interact with the user and our application data model via our UI. The user can terminate the command via the escape key (as one example). The command signals the user wants to terminate it via an event that the gets fired to the automation client. I just need the automation client to close its form and let the command go (as it has a RCW).

    Basically the user starts the form app up, the form load code calls GetObject to get our application object and then calls an API, CreateCommand, which returns a COM IDispatch interface (returned as an RCW since the automation client is .NET) that is the "command". The client can then choose to connect up to command events and to a mouse object (again, returned as an IDispatch interface turned into an RCW) that also provides events. This allows for extending the functionality of our application by third party software vendors and customers too.

    One event the command object fires is a "Terminate" event. That is the event to which my VB code is reacting and trying to shut down the form. My code to shutdown includes disconnecting from the command and mouse events and releasing the command and mouse objects as well as the reference on the application (obtained via GetObject). It's not rocket science and its a pardigm we have had in place for a couple of decades. Worked fine with VB 6. No problem with C++ clients either. Been tougher than I thought it would be with VB .NET. I am being tasked to come up with the "best" coding practice for ISVs and customers by creating a VB project template that simply provides the basics. That is, the form I have has nothing on it (wizard/template cannot ascertain semantics, which is a good thing or else a lot of people would be out of work) and this task just shows how to get the app, create a command, use the mouse to locate some topological entities (we are a solids modeling engineering app that builds everything from frying pans to flying jets, blenders to locomotives.) The user can terminate the command by closing the form (no problem with that) or while working in our app's UI and using one of our methods for killing a command - e.g, when starting another command.

    Once I have the template ready to go, user's will get it and use it in the VS 2012 "create project" UI. The user gets to decide just what their app will do when interacting with our app.


    R.D. Holland

  • Friday, June 01, 2012 9:41 PM
     
      Has Code

    I wonder if the reference to the COM object were declared without the "WithEvents" and the event was mapped and unmapped manually would that help in your scenario.  I'm not sure but I'll throw out an example in case it does.

    Here is sample code that uses the "WithEvents" where a textbox (used to mimic a COM reference) has been used

    Public Class Form1
    
      Dim WithEvents myCOMRef As TextBox
    
      Private Sub myCOMRef_TextChanged(sender As Object, e As System.EventArgs) Handles myCOMRef.TextChanged
        'this is a routine to mimic a COM event
        CloseForm()
      End Sub
    
      ' This delegate enables asynchronous calls for closing
      ' the form.
      Private Delegate Sub CloseFormCallback()
      Public Sub CloseForm()
        ' InvokeRequired required compares the thread ID of the
        ' calling thread to the thread ID of the creating thread.
        ' If these threads are different, it returns true.
    
        If Me.IsHandleCreated AndAlso Me.InvokeRequired Then
          Dim d As New CloseFormCallback(AddressOf CloseForm)
          Me.Invoke(d)
        Else
          Me.Close()
        End If
      End Sub
    
    End Class

    Here is the equivalent sample code where the event is wired up and removed manually

    Public Class Form1
    
      Dim myCOMRef As TextBox  'textbox used to mimic a reference to a COM object
    
      Public Sub New()
    
        ' This call is required by the designer.
        InitializeComponent()
    
        ' Add any initialization after the InitializeComponent() call.
        myCOMRef = New TextBox
        AddHandler myCOMRef.TextChanged, AddressOf myCOMRef_TextChanged
    
      End Sub
    
      Private Sub myCOMRef_TextChanged(sender As Object, e As System.EventArgs)
        'this is a routine to mimic a COM event
        CloseForm()
      End Sub
    
      ' This delegate enables asynchronous calls for closing
      ' the form.
      Private Delegate Sub CloseFormCallback()
      Public Sub CloseForm()
        ' InvokeRequired required compares the thread ID of the
        ' calling thread to the thread ID of the creating thread.
        ' If these threads are different, it returns true.
    
        If Me.IsHandleCreated AndAlso Me.InvokeRequired Then
          Dim d As New CloseFormCallback(AddressOf CloseForm)
          Me.Invoke(d)
        Else
          RemoveHandler myCOMRef.TextChanged, AddressOf myCOMRef_TextChanged
          Me.Close()
        End If
      End Sub
    
    End Class
    


    May we all make money in the sequel.

  • Saturday, June 02, 2012 12:11 AM
     
     

    I already tried using the system interop services IConnectionPointContainer and IConnectionPoint APIs. I changed from "withevents" to "Implements". No difference. I have not yet tried AddHandler and RemoveHandler.

    I have used AddHandler and RemoveHandler in other cases and I have found that .NET floods my COM object with connection points. I get one connection point for each call to AddHandler. So for instance, my app object has about 16 methods in the event interface. If a .NET client called AddHandler for each, I end up with 16 connection point. That's when I hit the brakes and went with "Implements ISEApplicationEvents". I found that using the interop services to do the work myself resulted in a single connection point. My command and mouse objects each have 6 events so the number won't be too bad. But regardless of what method I use to connect (I know of four methods so far), the underlying technology is bound to be the same. The .NET runtime has to call back to my COM app to unadvise the connection, and of course the COM app has to call back to the .NET framework when it calls Release() on the event interface given it when .NET called Advise. So I'm not confident at this point after two distinct methodologies to connect and disconnect events has ended in failure. But I might give it a shot anyway as it cannot hurt to try and try again. Using "withevents" also only creates one connection regardless of how many events one actualy implements.

    Using "Implements" also has another advantage as there are no "ghost RCWs" created for which the VB user is unaware. Some of our events pass the sink the dispatch interface of one or more of our COM objects (for example the mouse API has locate capability and can return located objects to the client). If the user does not call AddHandler for any such event there is no chance to call ReleaseComObject. We then have "leaks" until GC runs, or in this case, the form app closes down and .NET decides to finally release the actual reference held on our COM objects. Yes for some reason even if the VB program does not implement such an event, the stub that is called still gets the com dispatch marshaled into the runtime, for no apparent reason.

    At least in this case we are out-of-proc and closing the form down forces .NET to release our objects in a somewhat timely mannor.  When I detect an in-proc .NET client (by traversing all the modules in our process to find mscoree), I create my own .NET object I have written and exposed to COM so I can CoCreate and then call an API on it that then runs GC. Oh, and with .NET 4, I now can find I have to run GC in two runtimes inserted into our app as the 2.0 and 4.0 runtime can exist side-by-side in a COM application. My module detection code resorts to using the full path once it finds "mscoree" in a module filename so I can discern exactly which runtime is inserted into our process. Getting GC to actually clean up all RCWs that are eligible for collection relies on a bit of trickery too but it seems to work as I have used windbg to examine the .NET objects still active after I run GC and all the RCWs I know I have no reference on do get cleaned up (besides I can set breakpoints on our Release() calls and see it happen live too but windbg can be easier if there are a lot of RCWs in play.

    Thanks for all your help (and everyone else). But it's Miller Time.


    R.D. Holland

  • Thursday, June 07, 2012 10:16 PM
     
     

    Dave,

    Pal, pal buddy ol' pal. When it rains it pours. Today someone came by and complained that they are getting an exception in some .NET code and I found that we have the same issue. To make life easy I thought I would use your technique to fix the issue. And I did.

    However (as always), I really need to have the call back routine take in arguments so I don't have to put everything in instance data. But I don't see any way to call Invoke with parameters. I have called InvokeMethod using parameters but that is a different beast.

    Is it possible to use your technique with parameters? I need to pass in ints, strings etc. and would even like to pass in controls from the form. Otherwise I will be writing a lot of small subroutines that handle specific controls and also have to start adding more members to the form (some events pass in arguments that are used to set control data). All that worked in VB6 but not anymore!


    R.D. Holland

  • Friday, June 08, 2012 12:25 AM
     
      Has Code
     But I don't see any way to call Invoke with parameters.

    Does this example help ?

    Public Class Form1
    
       Delegate Sub FredSaid(ByVal s As String, ByVal i As Int32)
       Delegate Sub BarneySaid(ByVal s As String)
    
       Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
          'method 1
          Dim d As New BarneySaid(AddressOf dubbadoo)
          d.Invoke("So, Fred, what am I gonna call you now? Boss? Chief? ")
    
          'method 2
          Invoke(New FredSaid(AddressOf dubbadoo2), New Object() {"No, simply Your Highness will do. ", 2})
    
       End Sub
    
       Sub dubbadoo(ByVal s As String)
          MsgBox(s)
       End Sub
    
       Sub dubbadoo2(ByVal s As String, ByVal i As Int32)
          For j As Int32 = 1 To i
             MsgBox(s)
          Next
    
       End Sub
    End Class

  • Friday, June 08, 2012 12:16 PM
     
     Answered Has Code

    TnTinMn's example is what you need.  To simplify things a bit here is some sample code that passes a string to a routine using the same pattern.

    Private Delegate Sub SetStatusCallback(ByVal msg As String)
      Private Sub SetStatus(ByVal msg As String)
        ' InvokeRequired required compares the thread ID of the
        ' calling thread to the thread ID of the creating thread.
        ' If these threads are different, it returns true.
    
        If lblStatus.InvokeRequired Then
          Dim d As New SetStatusCallback(AddressOf SetStatus)
          Me.Invoke(d, New Object() {[msg]})
        Else
          lblStatus.Text = msg
          lblStatus.Refresh()
        End If
      End Sub
    If you think there will be a significant number of related parameters you could create a class that contains those parameters and pass that one object rather than having to update your callback and method signatures each time you add or remove a parameter in the future.

    May we all make money in the sequel.

    • Marked As Answer by RD Holland Friday, June 08, 2012 1:01 PM
    •  
  • Friday, June 08, 2012 1:00 PM
     
     
    Thanks Dave. I was just looking at Control.Invoke on the MSDN and found the overload. I might go ahead and pass an object since some of the events we raise have a number of arguments and the form I am currently working with updates controls to reflect data passed via the event.

    R.D. Holland

  • Friday, June 08, 2012 5:52 PM
     
     

    Dave,

    This is getting odder by the day. I have found that this cross-thread issue I am dealing with ONLY SHOWS UP WHEN RUNNING THE APP USING THE DEBUGGER!

    I happened to talk to the original developer today and mentioned that his code was not updating the form's text box. He replied that it never works in the debugger and he knew about this "cross-thread" exception and message. He told me to just run without debugging and sure enough, the text boxes on the form update just fine. But start the program with the debugger, and everything fails miserably.

    I posted this in the debugger forum. Perhaps they can explain why I have no problem running the release or debug target from the IDE as long as I "start without debugging" (or just go to explorer and double click the exe or start it from the command prompt window).

    I'm beginning to wonder if the runtime handles COM events in a separate thread only when the debugger is attached to the process.


    R.D. Holland