36

I have an application that needs to operate on Windows 2000. I'd also like to use Visual Studio 2010 (mainly because of the change in the definition of the auto keyword). However, I'm in a bit of a bind because I need the app to be able to operate on older OS's, namely:

  • Windows 2000
  • Windows XP RTM
  • Windows XP SP1

Visual Studio 2010's runtime library depends on the EncodePointer / DecodePointer API which was introduced in Windows XP SP2.

If using the alternate runtime library is possible, will this break code that relies on C++0x features added in VS2010, like std::regex?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • 1
    There may be a non-programming solution, such as wrapping the app in one of those single-app virtual-machine wrapper things. I don't know the first thing about those VMs, but it might be worth a few minutes checking. –  Mar 20 '10 at 20:46
  • 3
    The whole point of the app is to grab information about the running system. One of the things it has to diagnose non-updated windows installations and therefore I can't run this in a VM. – Billy ONeal Mar 20 '10 at 21:25
  • Why can't you use the 2010 CRT? – Alex Budovski Apr 01 '10 at 23:45
  • Are you sure you need XP SP1? There were many important security fixes introduced in SP2. – Adrian McCarthy Apr 01 '10 at 23:49
  • 6
    @Alex: I believe I explained that quite well in the question. It depends on APIs that don't exist until XP SP2, which causes any program built with 2010 to crash if you attempt to start it on earlier platforms. @Adrian McCarthy: Yes. One of the major functions of this logging tool is to tell users to actually update, but the tool needs to run to be able to tell them that. – Billy ONeal Apr 02 '10 at 00:00
  • related: [How to Enforce C++ compiler to use specific CRT version?](http://stackoverflow.com/questions/730134/), [VS 2008 - Link against older C runtime](http://stackoverflow.com/questions/693250/) – Piotr Dobrogost Feb 26 '11 at 21:53

8 Answers8

25

The very simplest solution is just to set the Platform Toolset in project settings in VS2010 to v900, which will use the Visual Studio 2008 libraries and compiler. This also means you lose C++0x features like auto, but to be honest, working around that with some typedefs is probably easier than building your own version of the CRT or other more complicated solutions. Alternatively, just use VS2008! I don't know if there are other C++0x features that are critical to your application though, you didn't mention - other than std::regex, which I think is still in the v900 toolset under the technical report 1 namespace (std::tr1::regex).

Just from the impression I get, I would predict the inconvenience of getting the VS2010 libraries to run on XP SP1 is greater than the convenience of C++0x features, so overall it wouldn't be worth it.

AshleysBrain
  • 22,335
  • 15
  • 88
  • 124
  • 3
    I concur. The other solutions presented are "cool" and that's the problem. I wouldn't dare ship a product with such "cool" hacks. What if things doesn't work out? It will be hell to debug and trace. I love auto, lambdas, r-value reference and decltype as much as the next guy but not at any expense. – Just another metaprogrammer Aug 13 '10 at 20:07
  • 1
    Thanks for the tip! I came to the conclusion that this solution was the best compromise and it works for me. I wrote up a post on how to do this here: http://www.zachburlingame.com/2011/03/targeting-windows-2000xp-rtmxp-sp1-from-visual-studio-2010/ – Zach Burlingame Mar 16 '11 at 14:11
  • 1
    @FuleSnabel: the other solutions are "cool"? They're pragmatic. Look up what En/DecodePointer are meant to do, and Code With Confidence. This isn't black magic. – snemarch Jul 20 '12 at 10:43
  • .... how did you get it to install in the first place? i get errror that it cant install.. – Glen Morse Apr 29 '13 at 08:46
24

You cannot use 2008 CRT, but you can prevent the new functions DecodePointer/EncodePointer to be linked from the kernel. It is quite easy to replace the new functions with stubs.

One might attempt following: Place code like this in your main.cpp source:

extern "C" {

  void *__stdcall _imp__DecodePointer(void *x) {return x;}
  void *__stdcall _imp__EncodePointer(void *x) {return x;}

};

The above does not work. While the basic idea is sound, the execution needs to be a little bit different. As described by snemarch in comment and another answer, __imp__ cannot be the function call, only the pointer to it. As it seems not possible to generate the pointer directly by the compiler, you need to assemble the following code with MASM and link against the produced object file.

.model flat

.data
__imp__EncodePointer@4 dd dummy
__imp__DecodePointer@4 dd dummy
EXTERNDEF __imp__EncodePointer@4 : DWORD
EXTERNDEF __imp__DecodePointer@4 : DWORD

.code
dummy proc
mov eax, [esp+4]
ret 4
dummy endp

end

The symbols from a project have preference against any symbols from libraries. DLL libraries are linked using .lib parts, which contain only __imp__ "vectors" jumping into the real functions. By replacing __imp__ "vectors" you do not touch the DLL linking, you replace the .lib part. I have verified there is no more any dependency of the exe on DecodePointer / EncodePointer.

Background

Statically linked library brings only used functionality into the application. It is possible to find which particular CRT function bring in those new API using linker verbose progress output:

Found __imp__EncodePointer@4
  Referenced in LIBCMT.lib(crtmboxw.obj)
  Referenced in LIBCMT.lib(invarg.obj)
  Referenced in LIBCMT.lib(handler.obj)
  Referenced in LIBCMT.lib(onexit.obj)
  Referenced in LIBCMT.lib(cmiscdat.obj)
  Referenced in LIBCMT.lib(tidtable.obj)
  Referenced in LIBCMT.lib(hooks.obj)
  Referenced in LIBCMT.lib(winsig.obj)
  Referenced in LIBCMT.lib(rand_s.obj)

Found __imp__DecodePointer@4
  // ... same list, only order differs ... 

This shows the new APIs are used in some of the CRT to provide more security for some functions which are deemed to provide frequent attack vectors.

With some effort it would be possible to use LoadLibrary / GetProcAddress to provide the real functionality where OS offers is, but I do not think it would really bring anything. The runtime functions which use DecodePointer / EncodePointer do not really need it to provide any encoding, all they need is the encoding to by symmetric. You do not really need the enhanced security (VS 2008 runtime would not give it to you either).

I hope there are no other obstacles waiting for you - I do not have an access to Win2k or XP pre SP2 system, therefore I am unable to try. If there are any exe header flags preventing even attempting to launch the exe on such systems, they should be easy to change.

Community
  • 1
  • 1
Suma
  • 33,181
  • 16
  • 123
  • 191
  • 2
    +1, neat and *relatively* clean solution. Darn annoying that MS has been adding (imho unnecessary) "kill off older Windows" support stuff since vs2005. – snemarch Aug 26 '10 at 22:16
  • 2
    Ah bother, this of course won't work - __imp_* are dword pointers to functions. The generated code is effectively interpreting the machine code of your De/EncodePointer functions as pointers and trying to call that memory location. You need to generate __imp_* pointers pointing to the dummy functions. – snemarch Aug 26 '10 at 22:32
  • 2
    @Suma: Would you be willing to release your implementation here under a BSD style or similar (liberal) license? I don't feel comfortable just copying your code here even though it's trivial into [my OSS projects](https://bitbucket.org/BillyONeal/instalog), which are BSD and BSL licensed. (And I don't want to impose additional licensing requirements on clients) – Billy ONeal Mar 26 '12 at 21:32
  • 6
    In case the [default license](http://creativecommons.org/licenses/by-sa/3.0/) covering all on this site not liberal enough: To the extent possible under law, I, Ondřej Španěl, have waived all copyright and related or neighboring rights to code in this answer. – Suma Mar 27 '12 at 08:39
  • 1
    While obvious, I feel compelled to note that, of course, the 'code' (what little there is ;p) will need to be implemented for all target CPUs (e.g. x86-64 or ARM). – dyasta Apr 23 '13 at 13:21
11

Suma's solution looked pretty promising, but it doesn't work: the __imp__*@4 symbols need to be pointers to functions, rather than the functions themselves. Unfortunately, I don't know how to make Visual C++ spit out a pointer with that kind of name generation... (well, __declspec(naked) combined with __stdcall does the trick, but then I don't know how to emit a pointer).

If using an assembler at build-time is OK, the solution is pretty trivial - assemble the following code with FASM and link against the produced object file, and presto - no EncodePointer/DecodePointer references in the exe:

use32
format ms coff

section ".data" data
public __imp__DecodePointer@4
__imp__DecodePointer@4 dd dummy

public __imp__EncodePointer@4
__imp__EncodePointer@4 dd dummy

section ".text" code
dummy:
mov eax, [esp+4]
retn 4
Community
  • 1
  • 1
snemarch
  • 4,958
  • 26
  • 38
  • This has the side-effect that you have to ship the VC++2010 runtime with your product, because they can't be downloaded from MS when they detect a Windows 2000 operating system, at least this is what I heard – Felix Dombek Jun 18 '13 at 12:51
8

As Visual Studio comes with support for MASM (see project properties -> Build Customizations...) the following translation of snemarch's code to MASM might be useful:

.model flat

.data
__imp__EncodePointer@4 dd dummy
__imp__DecodePointer@4 dd dummy
EXTERNDEF __imp__EncodePointer@4 : DWORD
EXTERNDEF __imp__DecodePointer@4 : DWORD

.code
dummy proc
mov eax, [esp+4]
ret 4
dummy endp

end

And remember to set Linker->System->Minimum Required Version to 5.0 (default is 5.1) to run on Windows 2000.

Tomasz Grobelny
  • 2,666
  • 3
  • 33
  • 43
  • thanks, I extended this idea to MFC apps - see http://tedwvc.wordpress.com/2010/11/07/how-to-get-visual-c-2010-mfc-applications-to-run-on-windows-2000/ – Ted. Nov 08 '10 at 15:44
  • +1 for masm. I never used it directly from within VStudio. Great tip. I found this link on [How to enable masm in Visual Studio](http://www.deconflations.com/2011/masm-assembly-in-visual-studio-2010/). However, you have to use static linking (or patch the runtime dlls...) – fmuecke Apr 27 '12 at 07:53
4

The usual work-around for this problem is to build your own custom version of the CRT. There are instructions for it here. You'll just need to edit the code to ignore EncodePointer and DecodePointer. (There should already be a #define for that.)

There are two other minor things you'll need to do:

  • Go to the Linker->Additional Library Directories setting and set C:\Microsoft Visual Studio 9.0\VC\lib as the first path to search. (I'm assuming you used the default install directory, otherwise change as appropriate.)
  • Change the subsystem version, in the PE header, to 5.00 (use the free CFF Explorer Suite if you don't have another tool handy for it).

That should allow your program to run on Windows 2000 as well as later versions.

Head Geek
  • 38,128
  • 22
  • 77
  • 87
  • 1
    Visual Studio 9.0 is Visual Studio 2008. That doesn't help here -- VS2008's toolset can already build for Win2k. RE: `(There should already be a #define for that.) ` <-- There is not. – Billy ONeal Aug 13 '10 at 14:00
  • I've never been able to keep track of which real version number goes with which year. Visual Studio 10.0 should work the same; I don't have a copy installed on any of my machines at present, or I'd confirm it for you. If there's no #define for using those functions, you could easily create a pair of functions of your own to replace them, calling the real ones on systems that support them and just returning the passed-in values on all others (or just using the pass-through version on all systems, if it works). Some preprocessor macro magic should take care of any differences. – Head Geek Aug 13 '10 at 14:26
  • I agree. VS2010 comes with the CRT source code; install it, copy it somewhere, remove the references to EncodePointer/DecodePointer, edit the BLDNT.CMD so that you get a static lib, and off you go. – Eric Brown Aug 18 '10 at 17:08
3

Option 1 - create a modified version of the 2010 runtime that redirects the problem API calls to a DLL that you supply. I don't know how easy or hard this would be - hopefully just a minor tweak to the symbol table, but it depends on the file format - and you're very likely to run into the reverse engineering clause of the license, of course.

Option 2 - Compare the exported symbols in two different versions of the runtime libs. If the symbols are the same, you have good odds of compatibility - though no guarantees. Its even possible that the lib file formats are different.

Option 3 - Check whether you can get access to runtime sources through the MSDN or similar, specifically in order to create a patched version.

Option 4 - Check whether you can use the 2010 compiler, but an older linker, perhaps configured in your solutions as a custom build step. Again, this depends on whether the obj and lib files are the same file format - but you may be able to write a small utility to patch simple differences like version numbers in a header. The older linker should have no problem linking in the older runtime - assuming the objs from the new compiler are compatible with it.

Option 5 - build DLLs in 2010 that don't need their own runtime, but which are loaded and hosted by an application built using the older compiler. Achieving the "no runtime" requirement for your DLLs may mean a lot of your libraries must be built in the hosting application, of course, and you may need to provide your own interfaces (via the host application) to library functions you need to work with - especially memory allocation stuff.

Options worth checking, but I'm sure you already thought of them all - sorry I have no idea whether any of them will work - or whether they'll almost work but cause intermittent problems.

  • #1. How does somebody do that? Also, this must be a standalone EXE. I'm statically linking to the library and cannot supply any DLLs to the system. #2. Again, I'm unaware how to do that. #3+4. Ok. #5. We're back to the "I can't include DLLs" problem :( +1 for taking the time to write a good answer though. – Billy ONeal Mar 21 '10 at 17:06
  • For #1, I'm talking about the lib file that gets linked into your app. It's just a file - it can be modified. You should already have lib.exe for doing things like extracting obj files from lib files. For the next step, you could look up obj2asm, an obj file disassembler. –  Mar 21 '10 at 17:55
  • 2
    Symbol lookup in general happens in listed order. If you provide "EncodePointer.lib" after the CRT but before KERNEL32.LIB, the linker will use that. However, you need the option "no default libs" to be able to list KERNEL32 explicitly last. – MSalters Apr 19 '10 at 14:54
  • @MSalters: The problem is generating such a lib -- if you try to implement EncodePointer or DecodePointer the compiler complains about not being able to implement a function marked as DLLIMPORT. – Billy ONeal Aug 11 '10 at 21:23
  • @Billy ONeal: When implementing them (in a .LIB), just don't declare them as DLLIMPORT. In that context, they're not. – MSalters Aug 16 '10 at 11:27
  • @MSalters: Err.. they're already declared DLLIMPORT in the windows headers. – Billy ONeal Aug 16 '10 at 13:53
  • 3
    Your library is supposed to _replace_ the Windows implemenations of EncodePointer and DecodePointer, therefore it shouldn't include Windows headers either. – MSalters Aug 16 '10 at 15:20
3

This would be a lot easier if you're allowed to use a DLL. Basically, write an EXE that requires no C runtime functions at all, by using the linker /ENTRYPOINT function. Once you've tested that your basic prerequisites are met, and reported any problems to the user via only Windows-provided APIs available on all target OSes (i.e. MessageBox), then call LoadLibrary to kick off the DLL that contains the bulk of your logic. That DLL can use the VS2010 runtime as usual. You can even avoid deploying two separate files by decompressing the DLL from a resource contained within your main .EXE at startup. (You can do this entirely within memory without writing the .DLL to disk, but not if you want to take advantage of the Windows PE loader to fixup all your imports).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Couple problems here. #1: That dll would still have dependency issues if I'm not mistaken, #2: a requirement for this application is that it makes no writes to the filesystem. – Billy ONeal Apr 02 '10 at 00:03
  • 2
    The DLL would have platform dependencies, but you would not load it until you had verified the platform you were running on. Ben Voigt's supplementary point about how to package your application is secondary to your question, and there is no need to perform any writes to the file system. – Seb Rose Aug 13 '10 at 08:33
0

Create a .LIB that implements the missing functionality and link it ahead of KERNEL32.LIB.

You will need to use the linker option /NODEFAULTLIB:kernel32.lib so that you can put your w2kcompat.lib ahead of kernel32.lib.

Gideon7
  • 9
  • 1
  • 2
    I tried to do this, but the compiler complains because the CRT has the offending functions marked as DLLIMPORT, and you cannot implement DLLIMPORT functions in a static library. You would have to recompile the CRT. – Billy ONeal Aug 11 '10 at 21:15
  • Why not implement these functions in a DLL? – Seb Rose Aug 12 '10 at 15:25
  • Implement these functions in a DLL and link against it as described by Gideon7 (when you build a DLL a .LIB file is also generated). These functions will need to be exported from the DLL as documented on the MSDN - http://msdn.microsoft.com/en-us/library/3y1sfaz2.aspx – Seb Rose Aug 13 '10 at 09:07
  • 2
    @Seb Rose: Because for this application I cannot have anything other than the one EXE. Single binary XCOPY deployment is very important for this application. It **must** be statically linked. – Billy ONeal Aug 13 '10 at 14:01
  • @Billy ONeal: So how much functionality do you need from the standard library? Could you get away without it, loading Windows DLLS explicitly and calling required functionality directly? – Seb Rose Aug 13 '10 at 14:21
  • @Seb: Unfortunately not :( Looks like I'm screwed into using VS2008 :( – Billy ONeal Aug 13 '10 at 14:48
  • Simply delay-load the respective imports by assigning a mock name instead of kernel32.dll and handling it in your own delay-load handler. Done that for ntdll.dll, for example ... just in case anyone has doubts. Because the linker will refuse delay-loading for certain DLLs, but with mock DLL names you're golden. – 0xC0000022L Jul 06 '21 at 21:54