Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Downgrading a .NET Applications from 64 bit to 32 bit for the WebBrowser Control


If you're like most developers these days, when you build a Windows desktop or Console application you most likely use the default Any CPU target in Visual Studio to build your application:

What Any CPU does is essentially build your binary so it can execute either in 64 bit or 32 bit. If you launch an EXE created with Any CPU on a 64 bit machine the app will run 64 bit, and will run 32 bit on a 32 bit platform of Windows.

Most of the time this is exactly what you want and it should be your default for most applications.

But, most of the time is not all the time and of course I wouldn't be writing this post if Mr. Murphy wouldn't have kicked my ass on something yet again. In this post I describe how Markdown Monster which started out as a 64 bit application ended up getting downgraded to 32 bit and as a result ended up running much more smoothly and reliably. While it's a special case mostly due to the heavy interaction with several WebBrowser controls, it's still something to keep in mind when you're building desktop applications for Windows.

64 bit vs 32 bit

Before I get into my application specific issues let's look at advantages and disadvantages of each of the bitness platforms.

These days most of us run 64 bit versions of Windows, so it makes sense to run our applications in 64 bit as well. Or does it? When you open your Task Manager in Windows you're likely to find that a large percentage of the applications you run all day long are actually 32 bit. Take a look at my Task Manager right now:

OK, there are other apps like iTunes, Paint.NET, Fiddler, Beyond Compare, Adobe products, Nitro that I use often and that are 64 bit, but the fact remains: 32 bit for desktop applications is not uncommon and not what you should think of as legacy. 32 bit isn't going away anytime soon because a large percentage of Windows apps we all use are running 32 bit.

64 bit and 32 bit in .NET

If you build .NET applications you can easily choose between 32 bit and 64 bit compilation modes, or even better you can have the application auto-detect which OS it's running under and use 64 bit or 32 bit depending on the OS bitness. .NET makes it easy to either create pure 64 bit applications or a mixed mode application that can run either in 64 bit or 32 bit mode depending on the OS Bitness that launched it. Most of the time I'd recommend going with the Any CPU build target which provides this auto-detect operation of the final executable.

64 bit offers a much bigger memory virtual address space (8 TB in theory!) and in theory 64 bit is the native platform so it should be faster. It offers larger 64 bit registers and these can perform some operations in a single operation that requires multiple ops on 32 bit registers due to the register size and improved instruction set. For computationally intensive applications - and especially those dealing with integer or bit math, performance can be considerably improved with 64 bit (up to 2x in theory in reality much less).

In reality, few desktop or Windows Console applications need more than the ~1.5gb effective address space a 32 bit app can address, and I've yet to see any significant performance improvement from a 64 bit app over a 32 bit app in typical desktop or business applications.

I have however seen many occasions where the opposite is true, where 32 bit applications are much more snappy than the 64 bit equivalent. And - as it turns out that's true for my Markdown Monster application.

Running into 64 bit issues with the WebBrowser Control

I've been working on Markdown Monster which is a Windows WPF application, that extensively uses Interop to interact with a number of Web Browser control instances. The application initially was built using Any CPU which means it was running as 64 bit application for anybody running on a 64 bit OS.

Making the WebBrowser control behave is tricky business all by itself, but after beating the feature functionality into submission, I noticed that there were occasional and very mysterious hard crashes that would bring the application down. The same operations would randomly fail - sometimes just opening a certain file, sometimes updating an preview. Checking event log data shows non-descript crashes in jscript.dll which is the IE Javascript engine. Markdown Monster uses a lot of Javascript - the editor (Ace Editor) is a huge Javascript library that manages the entire editing experience via js script code and the previewer uses a bunch of custom script to manage the preview syncing between the editor and preview windows, with the .NET Application in the middle as a proxy.

It wasn't just me either - a number of users reported some of these mysterious crashing issues on GitHub, often with repeatable steps that wouldn't repeat consistently. Open a specific file and it would fail 1 out of 10 times even immediately after startup going through the same steps. Argh! Those are the worst issues to debug.

Debugging and 64 Bit

A few days ago I posted about some 64 bit debugger problems I ran into while debugging my application and while trying to track down some 32 bit vs. 64 bit issues.

I realized that I was seeing drastically different behavior between the version being debugged and the version I run standalone. A number of interactions between the WPF application the HTML preview or the editor would fail in the production version which runs 64 bit, but it always worked just fine in the debugger running 32 bit - the problem wouldn't duplicate. Initially I attributed that to the fact the app was running under the debugger but after realizing that the debugger was running 32 bit I actually tried running the app in 32 bit.

32 bit Surprise

To my surprise I found that the odd failures I saw in 64 bit - mostly browser interaction related issues in the preview pane - did not occur in the 32 bit version even when not debugging. Things just worked as they were supposed to without the occasional odd failures. The WPF app captures the DOM document and then initializes Interop by passing a .NET reference to the Javascript code, and that code would occasionally and somewhat randomly fail in 64 bit - in 32 bit that code never fails.

Specifically this code:

PreviewBrowser.LoadCompleted += (sender, e) =>
{                
    bool shouldScrollToEditor = PreviewBrowser.Tag != null && PreviewBrowser.Tag == "EDITORSCROLL";
    PreviewBrowser.Tag = null;

    dynamic window = null;
    MarkdownDocumentEditor editor = null;
    try
    {
        editor = GetActiveMarkdownEditor();
        
        dynamic dom = PreviewBrowser.Document;
        window = dom.parentWindow;
        dom.documentElement.scrollTop = editor.MarkdownDocument.LastBrowserScrollPosition;

        // *** THIS FREQUENTLY FAILS IN 64BIT NOT 32BIT
        window.initializeinterop(editor);

        if (shouldScrollToEditor)
        {
            try
            {
                // scroll preview to selected line
                if (mmApp.Configuration.PreviewSyncMode == PreviewSyncMode.EditorAndPreview ||
                    mmApp.Configuration.PreviewSyncMode == PreviewSyncMode.EditorToPreview)
                {
                    int lineno = editor.GetLineNumber();
                    if (lineno > -1)
                        window.scrollToPragmaLine(lineno);                                
                }
            }
            catch
            { /* ignore scroll error */ }
        }
    }
    catch
    {
        // try again
        Task.Delay(200).ContinueWith(t =>
        {
            try
            {
                // *** STILL FAILS IN 64BIT (ie. not a timing error)
                window.initializeinterop(editor);
            }
            catch (Exception ex)
            {
                mmApp.Log("Preview InitializeInterop failed", ex);
            }
        });
    }
};

In 64 bit the code would frequently, but not always fail while trying to call the initializeinterop() function - which is a global Javascript function in the preview document. The error states that initializeinterop is not found, which is crazy - the document loads and that function is available. Script code is loaded before doc content so the function is always there. Yet in 64 bit I frequently got errors that fired into the exception handler (which would also fail on the delayed retry).

In 32 bit - the exception handler is never triggered. Reviewing my telemetry logs confirms that end users also very frequently see errors with this same issue on 64 bit. With the 32 bit version in circulation those errors have stopped coming in.

In addition to these odd load issues no longer triggering, I also noticed that the editor control was behaving much more smoothly in 32 bit. Text entry is much smoother and the inline preview refreshes that update the preview HTML go much quicker and without affecting the editor's cursor while updating - previously in 64 bit mode a spinning busy cursor would briefly appear.

In theory 64 bit and 32 bit should behave the same, but clearly in this case the reality is that 32 bit and 64 bit have quite different behaviors.

Posted in WPF  .NET  

The Voices of Reason


 

Colin Nicholls
December 23, 2016

# re: Downgrading a .NET Applications from 64 bit to 32 bit for the WebBrowser Control

Cool post, Rick. It confirms what my gut has been telling me for some time about 32 vs 64 bit apps on 64-bit OS. Unless you need the larger memory space (Sample Libraries in a DAW, for example?) you're often going to have a better experience with the 32-bit version.


Mark Woan
January 06, 2017

# re: Downgrading a .NET Applications from 64 bit to 32 bit for the WebBrowser Control

Have you considered an alternative web browser control such as awesomium?


Rick Strahl
January 06, 2017

# re: Downgrading a .NET Applications from 64 bit to 32 bit for the WebBrowser Control

@Mark - did you read the post? There's a whole section on that topic.


Upy
March 29, 2017

# re: Downgrading a .NET Applications from 64 bit to 32 bit for the WebBrowser Control

That's not the whole story, is it?

You make it sound like it's definitely the WebBrowser's or the .NET framework's fault, but you have a lot of interop code on your side, such as this: https://github.com/RickStrahl/MarkdownMonster/blob/master/MarkdownMonster/_Classes/WebBrowserHostUIHandler.cs

On many of IDocHostUIHandler's methods, even if you return E_NOTIMPL, you must set some of the out arguments to null. My guess is that this is just the tip of the iceberg. There are more COM interfaces, native calls, DllImports, etc. to be reviewed. For instance, it's enough that one uses e.g. int instead of IntPtr to crash the application.

Now, although I'm saying it's less likely, I'm not ruling out bugs in the WebBrowser control or the .NET framework. Stack traces would really be useful, both native and managed (try using WinDbg with SOS.dll extension).


Rick Strahl
March 30, 2017

# re: Downgrading a .NET Applications from 64 bit to 32 bit for the WebBrowser Control

@Upy - Yeah, totally get what you're saying. This stuff is hard to track down and figure out exactly because the interfaces are complex, so incredibly poorly documented (even after all these years!) and Microsoft has made zero effort in trying to improve the base behavior of the Web Browser control, especially in WPF. That's even though it's probably one of the most frequently used complex controls in .NET applications - Web content continues to be important and a hybrid model (like what I'm doing in Markdown Monster) is becoming more popular for a lot of things.

Realistically we shouldn't have to drop down to interop to do basic things as override DPI behavior, script error handling - this sort of thing should be done once by the people who own this control and know how the underpinnings are wired into the WPF control.

You're probably right about an errand pointer being a problem. However, the fact that things work 90% of the time and failures occur on the same requests that work at other times seems to be a good indicator of instability that likely not caused by an invalid pointer assignment (which would blow up more consistently). The other issue is that the Web Browser control is a 32 bit control so no matter what there's some additional code that has to run to thunk that adds overhead and additional potential for failure.

All I can say is that for all the applications I run with the control, the 32 bit ones feel snappier and run with significantly fewer browser incurred failures.

 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017