How to get Visual C++ 2010 MFC applications to run on Windows 2000

One of the annoying things about Visual Studio 2010 is that it does not create applications that can run on Windows 2000, Windows XP up to SP1, and Windows Server 2003 RTM. This can be embarrassing to say the least, you ship a cool app, and someone on one of these platforms gets an annoying error. See
http://qualapps.blogspot.com/2010/04/visual-c-2010-apps-dont-support-windows.html
for more information about this limitation.

Recently, someone over at stackoverflow discovered a quick and easy way of running Win32 (non-MFC) apps on Windows 2000 through the use of a little bit of assembly code – see:
http://stackoverflow.com/questions/2484511/can-i-use-visual-studio-2010s-c-compiler-with-visual-studio-2008s-c-runtime
Ignore the title of the above stack overflow question for the moment – the important part for our purposes, is Tomasz Grobelny’s MASM translation of snemarch’s FASM answer. I say this because Visual Studio 2010 has MASM tools built-in so we don’t need any extra downloads.

Let me explain. Win32 apps compiled with Visual C++ 2010 have a couple of hard dependencies (EncodePointer/DecodePointer) that we want to get rid of. The bit of assembly in the above article is exactly what we need.

I won’t go into a lot of details about that here, so if you’re only interested in Win32 apps the following article may not be of interest to you, except for the part about configuration of MASM in your project (keep reading below).

So what about MFC apps? Reading Jim’s article above, as you can see there are 4 more dependencies we want to get rid of, all Activation context functions.

So, how can we easily get rid of these without rebuilding MFC?

I had hoped it would be as easy as replacing afxstate.cpp with Visual Studio 2008 SP1′s version (which has most of the wrapper of the activation context APIs), however, Microsoft made it a bit more difficult for us. Looking at the source code and includes for mfc (if you have WinDiff handy and both 2008 SP1 and 2010 installed, feel free to following along with me) – in afxcomctl32.h – notice the devastating change in the macros: AFX_ISOLATIONAWARE_COMMON_ACTIVATE and AFX_ISOLATIONAWARE_FUNC_DEACTIVATE: Microsoft has changed the calls of AfxActivateActCtxWrapper to ActivateActCtx, and similarly AfxDeactivateActCtx to DeactivateActCtx. Unfortunately, these macros are baked into the MFC object code provided by Microsoft. Nothing we do is going to change that. So we have to workaround this problem. More on that later.

So, how about we replace afxstate.cpp as a start, with the 2008 implementation. Another way to accomplish the same thing, is to include a few redefining macros in our code, then immediately after, include the 2010 code in our stdafx.cpp. This will replace the obj that we’re linking to with the one in our project.

Now, back to those pesky calls to ActivateActCtx and DeactivateActCtx in the include file. This is where the first article at stack overflow helped me immensely. But, instead of doing what they did with EncodePointer and DecodePointer (created dummy functions instead of actually doing the encoding/decoding), we’re going to redirect any call ActivateActCtx and DeactivateActCtx in our binary over to our own Afx versions.

I won’t go into details about the assembly code here, suffice it to say, I’m not a very good assembly programmer, so this part was the most time consuming part of my research. Once I discovered the invoke command, it became easier to create a solution.

The final step of the puzzle was realizing that MFC apps have a hard dependency on gdiplus.dll, so on Windows 2000, make sure you copy gdiplus.dll to your program folder before running (there are exceptions to this requirement for certain apps, more on that below)

So, after all that, here it is, a walkthrough on how to create an MFC app in Visual Studio 2010 that runs on Windows 2000 (resulting executable tested on Windows 2000 SP4 including Update Rollup 1 KB891861 which is required for the HeapSetInformation function)

1) Launch Visual Studio 2010
2) Create an MFC app, keep all the defaults (yes, leave all that feature pack stuff on too :)) Hit Finish.
3) Right click on the project and hit Properties. Choose from configuation dropdown: All Configurations
4) Under configuration properties – General, choose Use of MFC – Use MFC in a Static Library
5) Under Linker – System, type in 5.0 beside Minimum Required version
6) Under Linker – Input, type in gdiplus.dll beside Delay loaded Dlls and then hit OK.
7) Right click on the project and choose “Build Customizations…” menu item.
8) Click on the checkbox beside “MASM (.targets,.props), and hit OK.
9) Right click on Source Files folder, and choose Add – New Item
10) Click on Code, then C++ file, then type in pointer.asm (not pointer.cpp) in the Name field and hit OK.
11) In the newly created pointer.asm, paste in the following MASM assembly code:

.model flat, C

AfxActivateActCtxWrapper PROTO STDCALL :DWORD,:DWORD
AfxDeactivateActCtx PROTO STDCALL :DWORD,:DWORD

.data
__imp__EncodePointer@4 dd dummy
__imp__DecodePointer@4 dd dummy
__imp__ActivateActCtx@8 dd ActActCtx
__imp__DeactivateActCtx@8 dd DeacActCtx

EXTERNDEF __imp__EncodePointer@4 : DWORD
EXTERNDEF __imp__DecodePointer@4 : DWORD
EXTERNDEF __imp__ActivateActCtx@8 : DWORD
EXTERNDEF __imp__DeactivateActCtx@8 : DWORD

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

ActActCtx proc uses ebx ecx hActCtx:DWORD,lpCookie:DWORD
invoke AfxActivateActCtxWrapper, hActCtx, lpCookie
ret 8
ActActCtx endp

DeacActCtx proc uses ebx ecx dwFlags:DWORD,ulCookie:DWORD
invoke AfxDeactivateActCtx, dwFlags, ulCookie
ret 8
DeacActCtx endp

end

12) Save the pointer.asm file, and open the stdafx.cpp file
13) Paste in the following code in the stdafx.cpp file (after the #include)


#define DELETE_EXCEPTION(e) do { if(e) { e->Delete(); } } while (0)
#include "afxpriv.h"

HANDLE AFXAPI AfxCreateActCtxW(PCACTCTXW pActCtx);
void AFXAPI AfxReleaseActCtx(HANDLE hActCtx);
extern "C" BOOL AFXAPI AfxActivateActCtx(HANDLE hActCtx, ULONG_PTR *lpCookie);
extern "C" BOOL AFXAPI AfxDeactivateActCtx(DWORD dwFlags, ULONG_PTR ulCookie);
extern "C" eActCtxResult AFXAPI AfxActivateActCtxWrapper(HANDLE hActCtx, ULONG_PTR *lpCookie);

// begin stuff ripped off from VC2008 SP1 afxstate.cpp

#define AFX_ACTCTX_API_INIT_PROCPTR(hKernel,name) pfn##name = (PFN_##name) GetProcAddress(hKernel, #name)
#define AFX_ACTCTX_API_PTR_DEFINE(name, type, params) typedef type (WINAPI* PFN_##name)params; PFN_##name pfn##name = NULL;
AFX_ACTCTX_API_PTR_DEFINE(CreateActCtxW, HANDLE, (PCACTCTXW));
AFX_ACTCTX_API_PTR_DEFINE(ReleaseActCtx, void, (HANDLE));
AFX_ACTCTX_API_PTR_DEFINE(ActivateActCtx, BOOL, (HANDLE, ULONG_PTR*));
AFX_ACTCTX_API_PTR_DEFINE(DeactivateActCtx, BOOL, (DWORD, ULONG_PTR));

AFX_STATIC void AFXAPI _AfxInitContextAPI()
{
static HMODULE hKernel = NULL;
if (hKernel == NULL)
{
hKernel = GetModuleHandle(_T("KERNEL32"));
ENSURE(hKernel != NULL);
AFX_ACTCTX_API_INIT_PROCPTR(hKernel,CreateActCtxW);
AFX_ACTCTX_API_INIT_PROCPTR(hKernel,ReleaseActCtx);
AFX_ACTCTX_API_INIT_PROCPTR(hKernel,ActivateActCtx);
AFX_ACTCTX_API_INIT_PROCPTR(hKernel,DeactivateActCtx);
}
}

eActCtxResult AFXAPI AfxActivateActCtxWrapper(HANDLE hActCtx, ULONG_PTR *lpCookie)
{
ENSURE_ARG(lpCookie!=NULL);

eActCtxResult eResult=ActCtxFailed;
if (pfnActivateActCtx != 0)
{
eResult=AfxActivateActCtx(hActCtx, lpCookie) ? ActCtxSucceeded : ActCtxFailed;
} else
{
eResult=ActCtxNoFusion;
}

return eResult;
}

// end of stuff ripped off from VC2008 SP1 afxstate.cpp

// initialize Context API functions
class InitContext
{
public:
InitContext() { _AfxInitContextAPI(); }
};

InitContext context;

// magic defines to avoid calling context APIs in afxstate.cpp
#define AfxActivateActCtxWrapper AfxActivateActCtxWrapperVC10
#define ActivateActCtx(hActCtx, lpCookie) (pfnActivateActCtx != 0 ? pfnActivateActCtx(hActCtx, lpCookie) : FALSE)
#define DeactivateActCtx(dwFlags, ulCookie) (pfnDeactivateActCtx != 0 ? pfnDeactivateActCtx(dwFlags, ulCookie) : FALSE)
#define CreateActCtxW(pActCtx) (pfnCreateActCtxW != 0 ? pfnCreateActCtxW(pActCtx) : INVALID_HANDLE_VALUE)
#define ReleaseActCtx(hActCtx) if(pfnReleaseActCtx != 0) { pfnReleaseActCtx(hActCtx); }

#include "..\src\mfc\afxstate.cpp"

14) Build your application.
15) Copy GDIplus.dll to your debug and/or release folder (where your EXE was built). You can get gdiplus for Windows 2000 from here:
http://www.microsoft.com/downloads/en/details.aspx?FamilyId=6A63AB9C-DF12-4D41-933C-BE590FEAA05A&displaylang=en
16) head over to a Windows 2000 machine (or VM) and copy your debug/release folder over there, and try running the app. If all steps above were performed correctly, you now have your first MFC 2010 app running on Windows 2000.

It was quite satisfying to see that Visual C++ 2010 application come up for the first time on Windows 2000.

Note: for Win32 (non-MFC) apps follow the same directions, except you only need to keep the code in the asm relating to EncodePointer/DecodePointer, and don’t worry about the code in the stdafx.cpp. Also you’ll need to manually change the C runtime library to the non-DLL (static) versions in the project properties (this step was done for us automatically when changing MFC from shared DLL to static)

About these ads

About tedwvc
On this blog you'll find some tips and tricks for dealing with Visual C++ issues.

9 Responses to How to get Visual C++ 2010 MFC applications to run on Windows 2000

  1. There is at least one more function that needs to be faked to run VS2010 compiled application on Windows 2000 RTM: HeapSetInformation. Have a look here: http://sharpsetup.eu/documentation/html/003ab441-765b-4eb6-80bd-33ab3aa8c3db.htm. Not sure if that will suffice to run MFC application on win2k rtm.

    • tedwvc says:

      Thanks for the tip and link about HeapSetInformation, hopefully someone can extend it to do a more elaborate callback using invoke (i.e. call it if it’s available on the platform). I’d also like to do with for EncodePointer/DecodePointer to use copies of the ones from the 2008 CRT i.e. _encode_pointer, _decode_pointer (left as an exercise for the reader :))

  2. Pingback: App-Fu: Making VS2010 Apps More Compatible « Misanthropic Geek Dot Net

  3. Marc Burkhardt says:

    Hi Ted,

    can anybody use your solution in commercial products ?

    best regards
    Marc Burkhardt

    • tedwvc says:

      If you mean to ask whether you have permission to use the solution in commercial products, the answer is yes, I have released this to the public domain and no attribution is required, it’s completely free in the public domain, feel free to extend the solution and post here with your results.

  4. Wow, great tutorial!
    However, I was not able to compile the project, if I was using
    #define _WIN32_WINNT 0×0500 // w2k

    The default Visual C++ 10 define is 0×0600 (vista). I don’t think it is a good idea to compile for vista platform and than link the stuff down to w2k (5.0).

    The program did run so far on my Windows 2000 machine – but I do not trust this!

    see http://msdn.microsoft.com/de-de/library/6sehtctf.aspx: “Values are not guaranteed to work if you include internal MFC headers in your application. For example, Windows 2000 is not supported in afximpl.h.”

    What do you think?

    • The problem here seems to be that you include afxstate.cpp which includes the MFC stdafx.h
      which has the following code:

      #undef _WIN32_WINNT
      #define _WIN32_WINNT _WIN32_WINNT_MAXVER

      and _WIN32_WINNT_MAXVER is defined by the latest windows SDK that is installed on your system (7.1 in my case –> 0×0601 as resulting winver).

      • tedwvc says:

        yeah, you’re right, the better solution is to not include afxstate.cpp in the stdafx.cpp, just have it as a separate build file in the project (you can include as an existing item and then change to not use precompiled headers)

  5. Pingback: Visual Studio 2010으로 Windows 2000용 프로그램 만들기 | The Dream of Super Surplus Power

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: