Okay, picking up where we left off last time: we have an application that now runs on .NET 4.6.2. So far it’s only tested on Windows 7 and Windows 10 platforms, which the .NET 4.8 version was targeting anyway. But .NET 4.6.2 is also supported on Windows Vista, so let’s find out where the abstractions leak.
To “well, actually” myself here right away: officially .NET 4.6 is the last version of .NET to be supported by Vista. However, .NET 4.6.1 and .NET 4.6.2 are minor updates, which I apparently have installed on all the Vista installations I have kept around. It’s been years so I don’t know exactly how I got them on there, but I believe it was an installation package that was officially targeted at Windows Server 2008 R2 SP1, but silently installed on Vista as well. So my theory is that they just ‘unofficially’ work on Vista, but are not advertised as such (I vaguely recall that I also tried to get .NET 4.7 and newer versions on Vista, but those would crash because of missing imports). I suppose for all intents and purposes, I could also have retargeted to .NET 4.6. But since I verified that .NET 4.6.2 was actually installed on my Vista systems (two x64 ones and one x86 one, so it was no coincidence), and they ran .NET 4.6.2 applications, I decided to stick to that. For the intents and purposes of this article, it doesn’t matter whether I would have targeted .NET 4.6 or .NET 4.6.2, the main point is to have a working .NET environment on Vista in which to run the application targeted to Windows 7 and newer.
The first thing I’d like to point out here is that .NET 4.6.2 was introduced with Visual Studio 2017, which does not run on Windows Vista. And even if we would target a version of .NET that was actually supported by tools that do run on Vista (which would be Visual Studio 2010, which supports .NET 4.0), we would run into the problem that I already mentioned last time: newer tools allow you to use newer versions of the C# language. The codebase would not actually compile in the Vista environment. I would have to rewrite various parts of the code to work around that. So making the code work in Vista is one can of worms, but making the code build on Vista is yet another can of worms.
This was not an area I was willing to explore. As I said, I was happy that the .NET 4.6.2 codebase was so similar to the .NET 4.8 codebase that a merge from .NET 4.8 to .NET 4.6.2 was trivial. If I were to rewrite the code to support an older language version, I would lose that advantage. Or, I would have to merge these changes back into the .NET 4.8 branch as well, in which case I would lose the advantage of using a newer language altogether.
Which means that I would actually be cross-developing: write, build and debug the code on a modern Windows in a modern Visual Studio, and then test on Vista. This would also mean that you can’t easily debug and single-step through the code on the Vista machine itself. So you may have to resort to remote debugging between a Vista and a modern Windows machine, or just do it the oldfashioned way: run the code on Vista with enough logging built in so you can figure out what the problems are, and make clever use of various tools to pinpoint problems.
Chromium Embedded Framework
Browsers… Notoriously bad in being backwards-compatible. When you integrate a browser into your own code, such as CEF (or in this case, technically CefSharp), it is no different. CEF is the Chromium Embedded Framework, which is based on Chromium, which you may know as the browser engine that powers Google Chrome and Microsoft Edge, among others. And as you may know, Chromium/Chrome stopped support for Vista years ago. Accessing the web on an unsupported/outdated OS is generally the most annoying part of using an old OS. You cannot get access to a recent browser that supports recent web standards, and at the same time most websites require a recent browser. So the browsers you can get to work on your outdated OS, won’t be able to open most webpages.
So, the problem here is that I cannot get a recent version of CefSharp to work on Vista. A clear case of a leaky abstraction, as CefSharp relies on native code, which in turn has dependencies on Windows 7 or newer. With the current build, the application would crash immediately on startup, as it failed to initialize the underlying CEF browser process. I could rollback to an earlier version of CefSharp that still supports Vista, but that would be so old that it wouldn’t be able to display most current webpages anyway. Aside from that, I’d have to go so far back that the CefSharp interface is significantly different, and lacks various features that we require. So any way I’d slice it, it would be a half-baked implementation.
Web views aren’t essential to our application. They can be used optionally, but you can just use regular text, images and videos to create content. So I decided to *really* make it optional: don’t crash the application when CefSharp is not available, but just run the application without the optional functionality.
The beauty of .NET is that any native DLL that is referenced, is loaded dynamically. With regular native code, you generally import DLL functions via the PE import table. That means that dependencies have to be resolved directly when your binary is loaded (a good point to mention the excellent Dependencies tool, with which you can debug missing imports, itself a spiritual successor to the old Dependency Walker tool).
With .NET you don’t have to worry about including functions and libraries that aren’t supported on the platform. Your code will still run, and there’s no problem as long as you avoid calling these functions specifically. And even then, there will ‘only’ be an exception, which you can catch and handle inside your application. So it’s much easier to make your application robust against missing imports.
In this case I decided to just make my code robust against a non-loading CefSharp. This seemed to be a good idea, that should have been in the codebase anyway, so I integrated this fix it into the main codebase. Now the application could start and run on Vista, under the condition that web views and other CefSharp-based things could not be used.
Vista supports Direct3D 11…
Well, that’s what I wrote many years ago. Or does it? The thing is that the Direct3D 11 I wrote about back in 2009 is not the same it is in 2022. There have been a few revisions over the years, adding new functionality and new versions of certain interfaces, the latest being 11.4. Of course these updates were never received by Vista. So you have to be careful not to use any of the new functionality, but stick only to the bare Direct3D 11.0 API.
Aside from the API itself, there’s also the drivers that are outdated. Vista support was dropped by GPU vendors many years ago, which can also mean that certain features, texture formats and such are not supported under Vista, even when they are supported on the same hardware with a newer OS/driver.
I ran into one specific problem with this, on an old Intel GM965 system (with an integrated X3100 GPU, Intel’s first DirectX 10 GPU). This was an issue I had run into before, with my own Direct3D10/11 engine. Namely, when you create a swap chain, you have to specify the pixel format. But, something strange happened with the move from DX9 to DX10. Namely, in DX9 only a small selection of formats was allowed for swap chains. They were all in RGB-order, and the basic variations were in number of bits per component and with or without alpha. So your choices were very limited, and it was basically impossible to pick the wrong one.
In DX10, the low-level management of buffers was split off into a separate DXGI layer. And DXGI introduced a lot more pixel formats. Most notably, there is no longer just a single go-to format for 32-bit RGB. There are now differences in pixel order as well. So for example, you have both DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_B8G8R8A8_UNORM. It’s somewhat strange that you would actually have to specify the pixel order, as the buffers for a swapchain cannot be accessed by the CPU. And for the shaders, the order does not matter, as the output of your shaders is into a datatype with r, g, b and a components, which will always be mapped correctly to the rendertarget, regardless of pixel order.
In practice, this usually isn’t a problem, as most GPUs and drivers support both formats anyway (this is actually a requirement for WDDM 1.1+ drivers). So whichever one you choose is fine. In fact, it seems that even the Direct3D team thought it was a bit silly, so they added a D3D11_CREATE_DEVICE_BGRA_SUPPORT flag. But on really old drivers and runtimes that may not be supported. And Vista on an Intel GM965 seems to be such a case: if I specify the BGRA flag, the device drops down to feature level 9_1, instead of 10 when I don’t specify the flag. And of course the format that the code picked, happened to be the unsupported one in DirectX 10 mode.
Funnily when I ran into that issue years earlier, I found that different Intel GPUs would support different formats. So one family only supported RGBA, and another only supported BGRA. I think that may be because the GM965 is a really early DX10 GPU, so it has an early DX10-level driver, with basic DXGI support, and WDDM 1.0. So DX11 runs on top of DX10 and the native DXGI here. The other Intel GPUs I tried were DX9, so you would instead run DX11 via the DX9 interface instead of the DX10 interface, and DXGI may be part of that interface. So Microsoft may be mapping to the hardware via the DX9 interface, and somehow the pixel format in the DX10 interface from Intel has the inverse pixel order. At least, that explanation makes the most sense to me. The GPUs are close relatives, so I would find it unlikely that Intel suddenly switched the pixel order around between GPU iterations. It may also explain why the device dropped to level 9_1 when forcing the BGRA-flag: only the Microsoft translation layer can support BGRA, the native DXGI interface can not.
A simple fix was to just use the CheckFormatSupport() method to try to find a format that has the D3D11_FORMAT_SUPPORT_DISPLAY flag set, to indicate that it can be used for a swap chain. Since this code merely makes the application more robust, I decided to also push this fix to the main codebase.
Windows Imaging Component
The Windows Imaging Component Framework (WIC) has been available since Windows Vista. So we should be good there, I suppose? Well, not entirely. Like with Direct3D 11, it has been updated over time. As it turned out, I was using IWICImagingFactory2 in my code somewhere. According to MSDN, this was introduced in Windows 8. However, this code runs without issue in Windows 7, so apparently it was added in a later update. Ah yes, another one of those Platform Updates.
Anyway, this interface is not available on Vista, not with any update. So I had to downgrade it back to the regular IWICImagingFactory. Apparently the code was not using any specific functionality from the newer interface at this point, so other than changing the type and instantiation of the interface, no changes were required.
Vista supports Media Foundation…
Or does it? Well, it depends on how you look at it. Media Foundation was first introduced with Vista, so yes, it has some level of support. However, much like Direct3D 11 and WIC, Media Foundation has received various updates over time, which haven’t all made it back to Vista.
It’s time to make a context switch here. So far we were only talking about C# code. The application was originally written with SlimDX. Since this had no support for Media Foundation, the implementation for that part was done with C++/CLR. So it’s still .NET code, but there’s native code involved, because once again our abstraction is leaking. The application has since been migrated to SharpDX, as SlimDX has been abandoned (and by now SharpDX has also been abandoned). But although SharpDX does offer interfaces for Media Foundation, the C++ code was never rewritten. So this next part deals with C++, not C#, and focuses mostly on the native interfacing with Media Foundation.
Microsoft made a bit of a mess of things here. Unlike Direct3D, there’s no clear versioning system in Media Foundation. So there’s no simple way to interface with a specific version of Media Foundation, or to check for optional features and APIs. What’s more, their own SDK makes it difficult to use.
For starters, where Direct3D uses a factory pattern, so you only need to include a few basic factory functions to create the required objects, and then go from there with QueryInterface and such, Media Foundation has a lot of functions that you link to directly. And if you happen to link to a function that is only supported in Windows 7 or higher, you will get the problem that your entire binary will fail to find the imports, and as such will fail to load. So you have to manually import these functions via LoadLibrary()/GetProcAddress() to avoid issues on older systems.
In my case, I found that a call to MFTRegisterLocalByCLSID was breaking my application. This function is only available on Windows 7 and newer. Not a problem until now. But when running on Vista, I need to remove the import. So I converted it to load dynamically. The call wasn’t specifically required for the video decoder anyway, so I did not have to make a workaround at this point.
The annoying part here is the SDK. As you may know, Microsoft allows you to specify a target Windows version, and the SDK headers will filter out any function definitions, data types, constants and such that are not supported on the target Windows version. But that doesn’t help us much here. I can target Vista, and then the MFTRegisterLocalByCLSID function is not defined at all. Sure, I can’t reference it, so I can’t break my code either. But that also means I can’t conditionally use it on a higher version of Windows that DOES support it. So that means I need to leave the target at Windows 7 or higher.
Anyway, after fixing the missing import, our code will run now, right? Well, yes and no. That is, my DLL now actually loaded correctly, so its code could be called. But it immediately failed on the MFStartup() call. Did I mention the Media Foundation versioning is a mess? Well, the thing is, you pass a constant to the MFStartup() call, which defines the version you want to initialize. This is a constant in the SDK, defined as MF_VERSION. So it basically doesn’t say which version you want to initialize specifically, but it is implicitly set to the version that your SDK supports.
Or well, it isn’t really. Because if we look at how MF_VERSION works, it looks like this:
#if !defined(MF_VERSION) #if (WINVER >= _WIN32_WINNT_WIN7) #define MF_SDK_VERSION 0x0002 #else // Vista #define MF_SDK_VERSION 0x0001 #endif // (WINVER >= _WIN32_WINNT_WIN7) #define MF_API_VERSION 0x0070 // This value is unused in the Win7 release and left at its Vista release value #define MF_VERSION (MF_SDK_VERSION << 16 | MF_API_VERSION) #endif //!defined(MF_VERSION)
So this boils down to there being only two possible values for MF_VERSION: Vista or Windows 7. These are controlled via the target version system in the SDK that I mentioned above.
This fails in two ways:
- There have been various updates to Media Foundation since the initial release of Windows 7, and the MF_VERSION constant has never been changed to reflect any of these changes. So effectively for Windows 7+, this value is meaningless.
- Even for distinguishing between Vista and Windows 7, this fails.
Why does it fail? Because Vista received Platform Updates, that’s why. Specifically, in 2011, Vista received the Platform Update Supplement. And it says this:
This update enables the playback of MP4 files, H.264 files, and AAC files by using the Source Reader component.
That is just one line, but it is a very significant update. This enables the new IMFSourceReader interface, and supports a number of file formats that weren’t previously available. It brings Media Foundation mostly on par with Windows 7. My code actually uses the IMFSourceReader interface, and we mainly use it to play back MP4/H.264 content, so this is very significant.
But, MF_VERSION. There is nothing between Vista and Windows 7. There is no way to configure the SDK to target Vista with the Platform Update Supplement installed. If you target Windows 7, you get the IMFSourceReader interface, but MF_VERSION will make MFStartup() fail. And if you target Vista, you can’t use the IMFSourceReader.
So I figured I would try a workaround: what if I target Windows 7, and get access to the IMFSourceReader, but I won’t use MF_VERSION, but instead manually construct the correct value for Vista?
I came up with this:
#define MF_SDK_VERSION_WIN7 0x0002 #define MF_SDK_VERSION_VISTA 0x0001 #define MF_VERSION_LATEST MF_VERSION #define MF_VERSION_WIN7 (MF_SDK_VERSION_WIN7 << 16 | MF_API_VERSION) #define MF_VERSION_VISTA (MF_SDK_VERSION_VISTA << 16 | MF_API_VERSION)
While it is not entirely clear to me what the significance of the version is in practice (using MF_VERSION_VISTA seems to work fine on every platform), I figured I could try to just call MFStartup() with the MF_VERSION_LATEST first, which would be the highest that the SDK can target. And if that fails, I can try calling it again with a lower version. Then you always initialize MFStartup() against the highest possible version on the platform.
And indeed, MFStartup() now worked on Vista, so the rest of my code could now finally be executed as well. And the Platform Update Supplement did what it said on the tin: the IMFSourceReader was supported on Vista, and it could play MP4/H.264 content. Excellent!
Again this bit of code seemed to only improve compatibility and robustness, so I merged it into the main codebase as well.
There was just one small glitch left: I had assumed that NV12 texture support was available. On most hardware/drivers it is, but again the GM965 was an exception. So that requires a small workaround, by specifically testing for format support, like the above RGBA/BGRA issue. And again, that is a bit of code that just makes the application more robust, so it doesn’t hurt to merge that into the mainline. There actually was a // TODO-comment in the code there, but it wasn’t relevant to fix it at the time, as we could simply assume that all the target hardware supported NV12 anyway.
At this point I became curious… How much further can we downgrade this code? Would .NET 4.0 be possible? Could we even get it to work in some form on Windows XP? The small print on the MFStartup()-page was intriguing:
This function is available on the following platforms if the Windows Media Format 11 SDK redistributable components are installed:
- Windows XP with Service Pack 2 (SP2) and later.
- Windows XP Media Center Edition 2005 with KB900325 (Windows XP Media Center Edition 2005) and KB925766 (October 2006 Update Rollup for Windows XP Media Center Edition) installed.
Wait? Does it say… XP? Media Foundation… on XP?
So, the score so far is: it runs on Vista, with everything still supported and working, with the exception of CefSharp. Let’s wrap the .NET 4.6.2/Vista part up here, and next time, let’s look into .NET 4.0 and Windows XP.