Search code examples
.netwinformsexceptionf#wndproc

Exception thrown when WndProc is overloaded


I'm trying to get at the Close event of the .NET WebBrowser type, which doesn't seem to work out of the box. (EDIT: This event is emitted when the window.close() call is issued in a script running in the browser.)

One solution I've seen is to extend the WebBrowser class and override the WndProc method.

My extension code is as follows:

type internal ExtendedBrowser () = class
    inherit System.Windows.Forms.WebBrowser()

    let WM_PARENTNOTIFY : int = 0x0210
    let WM_DESTROY      : int = 0x0002

    let closed : Event<unit> = new Event<unit>()

    do ()

    [<System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")>]
    override this.WndProc (msg : Message byref) =
        match msg.Msg with
        | wm when wm = WM_PARENTNOTIFY ->
            if (not base.DesignMode) && (msg.WParam.ToInt32() = WM_DESTROY)
            then closed.Trigger()
            base.DefWndProc(ref msg)
        | _ ->
            base.WndProc(ref msg)

    member this.Closed = closed.Publish
end

This ends up causing an exception to be thrown when an instance of the type is accessed:

Unhandled Exception: System.Reflection.TargetInvocationException: Unable to get the window handle for the 'ExtendedBrowser' control. Windowless ActiveX controls are not supported. ---> System.ComponentModel.Win32Exception: Error creating window handle.
   at System.Windows.Forms.NativeWindow.CreateHandle(CreateParams cp)
   at System.Windows.Forms.Control.CreateHandle()
   at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.WebBrowserBase.DoVerb(Int32 verb)
   at System.Windows.Forms.WebBrowserBase.TransitionFromRunningToInPlaceActive()

   --- End of inner exception stack trace ---
   at System.Windows.Forms.WebBrowserBase.TransitionFromRunningToInPlaceActive()

   at System.Windows.Forms.WebBrowserBase.TransitionUpTo(AXState state)
   at System.Windows.Forms.WebBrowser.get_AxIWebBrowser2()
   at System.Windows.Forms.WebBrowser.PerformNavigate2(Object& URL, Object& flag
s, Object& targetFrameName, Object& postData, Object& headers)
   at System.Windows.Forms.WebBrowser.Navigate(String urlString)
   at [PRODUCT].Application.WebBrowser..ctor() in C:\[PATH]\WebBrowser.fs:line 107
   at Program.Main.main(String[] args) in C:\[PATH]\Program.fs:line 79
Press any key to continue . . .

Currently it's erroring on a call to Navigate("about:blank") (the first access of the instance after its construction). I can comment out the WndProc override and things work fine (besides missing the close event).

Someone said you had to put the security attribute on the WndProc override so I did that and it didn't fix things.

Someone else said you can disable the DEP, but I tried and it didn't let me exempt the EXE.

An instance of the extension is being created in a wrapper for the browser (also called WebBrowser) and an instance of this is being created in my main, which is runing in an [STAThread] (which also seems to be required).

Does anyone know what could be going wrong?

(What I'm really after is a way to get notification of the close event, so if someone knows an alternate route to that I'd be happy to hear it.)


Solution

  • I found a post where someone else had had the same problem overriding the WndProc method in F#, and the solution there worked for me; working code is as follows:

    type ExtendedWebBrowser () = class
        inherit System.Windows.Forms.WebBrowser()
    
        let WM_PARENTNOTIFY : int = 0x0210
        let WM_DESTROY      : int = 0x0002
    
        let closed : Event<unit> = new Event<unit>()
    
        do ()
    
        override this.WndProc (msg : Message byref) =
            match msg.Msg with
            | wm when wm = WM_PARENTNOTIFY ->
                if (not base.DesignMode) && (msg.WParam.ToInt32() = WM_DESTROY)
                then closed.Trigger()
                base.DefWndProc(&msg)
            | _ ->
                base.WndProc(&msg)
    
        member this.Closed = closed.Publish
    end
    

    It seems that F# treats ref a bit differently than C#: Doing some reading on Parameters and Arguments in F# -- see the Passing by Reference section -- it seems that the ref call copies its argument and uses that copy as the value for the referential cell; so, what I had been passing was a reference cell containing a copy of the contents of msg rather than a reference to the original. The address-of operator (&) gets the address of the value, so that's what was needed for the arguments to the DefWndProc and WndProc methods instead of wrapping them in refs.