1

Ive been banging my head on this now for days. I am developing a com object in vb.net to display a splash screen. This will be called from vb script and Powershell to provide status to users while other instructions are occurring in the script. I am receiving this error intermittently any help would be appreciated.

Here is a link that I have been using to help to develop my application. http://msdn.microsoft.com/en-us/library/ms233639.aspx

Public Class Progress

Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress
Private Delegate Sub FormDelegate()
Private ShowDelegate As FormDelegate
Private UnLdDelegate As FormDelegate
Private t As Thread

Public Sub New()

    MyBase.New()

    If IsNothing(appContext) Then
        frmSplash = New frmProgress
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()
        ShowDelegate = New FormDelegate(AddressOf frmSplash.Show)
        HideDelegate = New FormDelegate(AddressOf frmSplash.Hide)
        UnLdDelegate = New FormDelegate(AddressOf frmSplash.Close)
    End If

 End Sub

MessageLoop

 Private Sub StartMessageLoop()
    Application.Run(appContext)
 End Sub

ShowForm

Public Sub Show()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    End If
End Sub

Unload

 Public Sub Unload()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    End If
End Sub

Thread Exit Event

 Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

The error is generally being thrown when i call the Show Method from a powershell script. Any help would be greatly appreciated. Thanks

Complete Code

Private ShowLock As New ManualResetEvent(False)
Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress

Private Delegate Sub FormDelegate()
Private Delegate Sub DisplayDelegate(ByVal msg As String)
Private Delegate Sub FormSettings(ByVal val As Boolean)

Private ShowDelegate As FormDelegate
Private HideDelegate As FormDelegate
Private UnLdDelegate As FormDelegate

Public Sub New()

    MyBase.New()
    frmSplash = New frmProgress

End Sub

Public Sub SetDisplayText1(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub


Public Sub SetDisplayText2(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText2)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label2.Text = msg
        frmSplash.Label2.Refresh()
    End If

End Sub

Public Sub SetTitle(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTitle)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Text = msg
    End If
End Sub


Public WriteOnly Property AlwaysOnTop() As Boolean

    Set(ByVal value As Boolean)
        SetTop(value)
    End Set
End Property


Public Sub Show()

    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated

    If IsNothing(appContext) Then
        Dim t As Thread
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()
    Else
        ShowLock.WaitOne()
        If frmSplash.InvokeRequired Then
            If frmSplash.IsHandleCreated = True Then
                ShowDelegate = New FormDelegate(AddressOf Show)
                appContext.MainForm.Invoke(ShowDelegate)
            End If
        Else
            frmSplash.Show()
        End If

    End If
End Sub

Public Sub SetTop(ByVal val As Boolean)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTop)
        frmSplash.Invoke(d, New Object() {[val]})
    Else
        frmSplash.TopMost = val
        frmSplash.TopLevel = val
    End If
End Sub

Public Sub Hide()

    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            HideDelegate = New FormDelegate(AddressOf Hide)
            appContext.MainForm.Invoke(HideDelegate)
        End If
    Else
        frmSplash.Hide()
    End If

End Sub

Public Sub Unload()

    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            UnLdDelegate = New FormDelegate(AddressOf Unload)
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    Else
        frmSplash.Close()
    End If

End Sub

Private Sub StartMessageLoop()
    Application.Run(appContext)
End Sub

Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

Protected Overrides Sub Finalize()
    MyBase.Finalize()
End Sub

Public Sub frm_HandleCreated()
    ShowLock.Set()
End Sub

My Debug Lines would lead me to believe that the crash is occurring here

 Public Sub Show()

    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated

    If IsNothing(appContext) Then
        Dim t As Thread
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()

Thanks for all the help so far.

56K
  • 37
  • 1
  • 6
  • Is that the full Show method? There's no else condition? Also, can you show the stack trace and/or the method that updates the form to show status? – Chris Shain Feb 24 '12 at 16:05
  • That is the full show method nothing special, The win form that is showing is just a form with a couple labels and progressbar set with a value of 100. The form also reads a single value from a registry key that contains a Telephone number when it loads. After that it just sits there until we call the unload method. – 56K Feb 24 '12 at 16:27
  • Nonetheless, I'd like to see the rest of the code... The code you have posted thus far looks fine, so I suspect the problem lies elsewhere. Specifically, I'd like to see the method that updates progress on the form after it is shown. – Chris Shain Feb 24 '12 at 16:28
  • Thats it the progress bar is never updated it is set to the maximum value at design time and never updated after. Is there away to attach all the code in a file or do i have to post it all on the board? – 56K Feb 24 '12 at 16:35
  • You can only post as you did the rest of the code- you can edit the question and add it. Lets start with a stack trace from the exception- I might be able to figure it from that. – Chris Shain Feb 24 '12 at 16:37
  • Ok this is an embarrassing question... But how would one do a stack trace As this error is occuring when i call the com object from a powershell script outside of the Visual Studio IDE. Just learning here sorry. – 56K Feb 24 '12 at 16:55
  • No problem- I think I don't need it. See my answer below – Chris Shain Feb 24 '12 at 17:03

2 Answers2

3

After creating the MainForm, access its Handle property on the same thread that will end up being "the UI thread." Assign it to some throwaway variable, the point is just to access it. WinForms lazily initializes the Handle, so it isn't valid until the property is accessed or it's first shown. InvokeRequired works by looking at the thread the Handle was created on, so if tries to lazily initialize it on the background thread, bad things can happen.

See this question and answer for some more details.

That may not be it, but it sure sounds like something I've hit before. Hope it puts you on the right track.

Community
  • 1
  • 1
Nicholas Piasecki
  • 25,203
  • 5
  • 80
  • 91
  • I was thinking along these lines too, but he is checking the `HandleCreated` property in the methods he showed us. Thus my question about what else we are not seeing. – Chris Shain Feb 24 '12 at 16:09
  • I thought that, but he's checking it for the splash form, but I don't see him checking it for the main form, which is what he is actually invoking on, unless I've had too much Mt Dew. – Nicholas Piasecki Feb 24 '12 at 16:13
  • What main form? The `Progress` class doesn't inherit `Form`. All of this happens from a script. Lay off the Dew :-) – Chris Shain Feb 24 '12 at 16:15
  • appContext.MainForm.Invoke(ShowDelegate) in `Progress`'s `Show` method, right? – Nicholas Piasecki Feb 24 '12 at 16:16
  • Oh lawd I'm mixing up the MainForm PROPERTY on the appContext, confused that I usually always have an actual form called MainForm. Nothing to see here, please move along – Nicholas Piasecki Feb 24 '12 at 16:17
  • `appContext` is defined as `appContext = New ApplicationContext(**frmSplash**)`, and started on a "background" thread, `Application.Run(appContext)` – Chris Shain Feb 24 '12 at 16:18
  • I wasn't calling those methods in the powershell script but i'm glad i know that now because it will save me grief down the line. All that my test script was doing was calling a show and an unload. with a sleep method of 5 seconds in the middle. I will work on your suggestions and get back to you asap thanks for all the help – 56K Feb 24 '12 at 17:19
2

OK, I see what the problem is. My guess is that you are calling one of the SetDisplayText methods from the PS script. You need to protect those with checks on InvokeRequired and IsHandleCreated, plus you need to provide synchronization around the initial display of the form. My VB.NET is a bit rusty, but I'll do my best:

First, add this as a local variable:

Private ShowLock As New ManualResetEvent(False);

Then, add a handler for the HandleCreated event on frmSplash, and in that handler:

Public Sub Form_HandleCreated(...)
     ShowLock.Set
End Sub

Then in your ShowForm method, wait for that guy to get set:

Public Sub Show()
    ShowLock.WaitOne
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    End If
End Sub

That way you are sure that the form's handle has been created before the first thread attempts to Show it (or do anything else).

Finally, protect those SetXXX methods (do this for anything that touches the form and might get called from the Script):

Public Sub SetDisplayText1(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New SetDisplayText1Delegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub

EDIT: Try this- I took your code from above and re-worked it a little.

Private ShowLock As New ManualResetEvent(False)
Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress

Private Delegate Sub FormDelegate()
Private Delegate Sub DisplayDelegate(ByVal msg As String)
Private Delegate Sub FormSettings(ByVal val As Boolean)

Private ShowDelegate As FormDelegate
Private HideDelegate As FormDelegate
Private UnLdDelegate As FormDelegate

Public Sub New()

    MyBase.New()
    frmSplash = New frmProgress
    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated
    Dim t As Thread
    appContext = New ApplicationContext(frmSplash)
    t = New Thread(AddressOf StartMessageLoop)
    t.IsBackground = True
    t.SetApartmentState(ApartmentState.STA)
    t.Start()

End Sub

Public Sub SetDisplayText1(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub


Public Sub SetDisplayText2(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText2)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label2.Text = msg
        frmSplash.Label2.Refresh()
    End If

End Sub

Public Sub SetTitle(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTitle)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Text = msg
    End If
End Sub

Public WriteOnly Property AlwaysOnTop() As Boolean
    Set(ByVal value As Boolean)
        SetTop(value)
    End Set
End Property

Public Sub Show()
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            ShowDelegate = New FormDelegate(AddressOf Show)
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    Else
        frmSplash.Show()
    End If
End Sub

Public Sub SetTop(ByVal val As Boolean)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTop)
        frmSplash.Invoke(d, New Object() {[val]})
    Else
        frmSplash.TopMost = val
        frmSplash.TopLevel = val
    End If
End Sub

Public Sub Hide()
    ShowLock.WaitOne()    
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            HideDelegate = New FormDelegate(AddressOf Hide)
            appContext.MainForm.Invoke(HideDelegate)
        End If
    Else
        frmSplash.Hide()
    End If

End Sub

Public Sub Unload()
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            UnLdDelegate = New FormDelegate(AddressOf Unload)
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    Else
        frmSplash.Close()
    End If

End Sub

Private Sub StartMessageLoop()
    Application.Run(appContext)
End Sub

Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

Protected Overrides Sub Finalize()
    MyBase.Finalize()
End Sub

Public Sub frm_HandleCreated()
    ShowLock.Set()
End Sub
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • So just so that I am sure i understand . The first 2 sections of code would go into my windows form class form progress? And the rest would go into my class that i posted above? and the event that you specify is a Threading.ManualResetEvent?. – 56K Feb 24 '12 at 17:56
  • The first two go in your Progress class. You also need to use addhandler to connect the Form_OnHandleCreated method to the HandleCreated event on frmSplash – Chris Shain Feb 24 '12 at 18:11
  • I have made the suggested changes ... Its still occuring I have modified my program slightly and the code has been reposted your last post allowed me to understand what was going on. I am still such a script kiddie I hope some day i will be able to play with the big guys. Please let me know if you see anything else. I am getting a little frustrated. thanks again for all your help!! – 56K Feb 24 '12 at 19:48
  • Sorry for the frustration- see the new edit I put in above. Its very close to what you did, but with some minor modifications. – Chris Shain Feb 24 '12 at 19:57
  • Don't be sorry if it wasnt for you not sure what i would do . – 56K Feb 24 '12 at 19:59
  • Cool- glad we finally got this sorted! – Chris Shain Feb 25 '12 at 00:11