Supporting Windows XP SP2+ with vNext
If you’ve been following Visual Studio development at all, you’ll no doubt be very interested in the Visual Studio 11 Beta, especially as it ships with a go live license.
We certainly were; however, a significant number of our customers still use and need to back up and restore Windows XP systems, so the news that vNext Beta would not support building executables for Windows XP was a showstopper for us. Indeed, the latest news is that XP Support will not ship with the RTM, however, it may be supplied later at an unspecified date.
However, we were keen to start using the latest and greatest features of Visual Studio 11 now, especially with our soon to be unleashed beta code. It turns out were are not the only ones interested in XP support, and Ted had a solution.
So we can understand how Ted’s solution works, we’ll begin with a quick primer on linker resolution. There are two ways to link an executable; statically or dynamically. Static linking is the easier of the two to understand — you look for a symbol and you change all references to that symbol to the relative address from the base of the executable. Well — sort of, anyway. It’s good enough here.
This all happens at compile time; dynamic linking on Windows often uses an import library to symbols that look like this:
__imp_
When Windows runs your executable, the import library pulls in the right references, and the stub functions call the real functions. For more, have a read of this article.
Now, the statically linked versions of these libraries use exactly the same names to link against — except that the relevant implementation is pulled directly into the executable.
Now, the final thing to understand is order. When the linker is searching for a symbol, it uses the first version of a symbol it finds and imports that object. As such, it is possible to override what gets imported into an executable for compilation.
Now, the reason vNext does not support Windows XP is because the MFC and CRT libraries have been built to assume symbols available on Vista or greater. As such, an executable built using vNext and run on Windows XP will attempt to use functions that do not exist in say, for example, kernel32.dll. When the dynamic link helper attempts to resolve the function it won’t be able to, and the application will not start.
You can probably see where this is going. The solution then is fairly straight forward — all we need to do is define those symbols, implement the relevant functions and ensure these, and not the default libraries, get linked.
That’s what Ted’s solution, to which I’ve contributed in a small part, does. Firstly, some assembler files are used to define function calls with the correct names, as using __imp_ prefixed functions from C is not easy (or nice). We’ll use an example function, CompareStringEx. Firstly, Ted’s implementation lives in AfxCompareStringEx, so we define a prototype (forward declaration, if you like):
AfxCompareStringEx PROTO :QWORD,:DWORD,:QWORD,:DWORD,:QWORD,:DWORD,:QWORD,:QWORD,:QWORD
32-bit versions of this must also specify a calling convention — the Windows API uses stdcall. Then in our data section, we define our reference:
.data
__imp_CompareStringEx dq AfxCompareStringEx
All that remains after this is to export the symbol, so the linker can find it:
EXTERNDEF __imp_CompareStringEx : DWORD
Note the sizes of the fields used correspond to the sizes of the datatypes on a 64-bit platform — pointers are QWORDs on 64-bit, and dq defines a qword data item. The 32-bit versions of these examples use DWORD pointers.
The actual implementations of these functions generally works like this:
[code language=”c”]// function pointer type
typedef BOOLEAN (WINAPI *pFunctionName)(**args**);
// are we running on Vista?
if (IsVista)
{
// have we already obtained a pointer to the function we want?
if (!FunctionName_p)
{
// open a handle to kernel32.dll
HMODULE mod = GetModuleHandle( _T("KERNEL32.DLL"));
if (mod)
{
// get the address of the function; cast it to our function pointer.
FunctionName_p = (pFunctionName)
GetProcAddress(mod, "FunctionName");
}
}
// call the function with our arguments
return FunctionName_p(**args**);
}
else
{
// XP implementation
}
[/code]
Hopefully that’s pretty clear — if we’re using Vista onwards, all we do is call the Vista functions. However, on XP we basically have to reimplement these functions, which is done. This is what Ted has done in the various c++ files he provides.
So, looking to get XP SP2 / Server 2003 support for your application? Get over to Ted’s blog and download the XP support code he has on offer. Don’t forget — you must be using static linkage.