Cluebat-man to the rescue

A weblog dedicated to Visual C++, interoperability and other stuff.

Detection of the VC2005 compiler version at compile time: RTM or SP1

I was hanging out in dotnet.languages.vc yesterday when I read an interesting question. Someone wanted to know how he could make a compile-time decision, based on whether a piece of code was compiled with VC2005 RTM or VC2005 SP1.

I told him to simply use the _MSC_VER macro that should tell you which version you are compiling with, and I asked him why he would need to know, since generally you shouldn’t have to care.

It turned out that the _MSC_VER macro reports the same major and minor version for the RTM and SP1 version. Tough luck. This is a bug IMO, but one that is there forever. Changing the macro in the next release won’t fix this one J

But the person asking the question really needed to know:

<quote>

Microsoft has made some changes to the CxxUnhandledExceptionFilter logic in
Visual C++ 2005 SP1. Because of the way that my code (in a dll) is linked
to executable files compiled with Visual C++ 6 (and that I cant touch),
this change has broken my functionality that I have for dumping debug
information in the event of a crash. I have a workaround that makes it work
again with VS2005 SP1 but that only works on VS2005 SP1 and not on VS2005.
Hence the need to detect the difference :)

</quote>

I searched around for a bit, but could not find any simple macros that could make a difference.

_CRT_ASSEMBLY_VERSION could be used because the CRT and compiler are distributed together, but it is not guaranteed to be correct in all scenarios. And even then, that macro is a string and not usable with preprocessor commands.

So there was no direct way to make the distinction.

However, SP1 fixed a ton of bugs, so if I could find anything that was fixed and detectable at compile-time, I had a solution.

I browsed around in the list of fixed bugs

http://blogs.msdn.com/vcblog/archive/2006/06/22/643325.aspx

And picked out this one:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=101702

Basically, it is a fix to make the sizeof() operator conformant to the C++ standard when used with a reference of an array. sizeof is evaluated at compile time, so that should do the trick.

Unfortunately, the result of a sizeof operation cannot be used to control the preprocessor, so my first solution was a runtime check that was fixed at compile-time.

Solution 1

  char test[10];

  if (sizeof(&test)==4)

    cout << "SP1";

  else

    cout << "RTM";

This is a simple solution, but the decision is still made at runtime, and both code paths exist inside the compiled binary.

I posted this solution, still not 100% satisfied, but any solution is better than no solution of course.

Solution 2

Ben Voigt looked at that solution and pointed out that the result of a sizeof() operation can be used to specialize a template.

Wanting to go all the way on this one, I created the following solution:

This template class represents the normal case of behavior up to and including VC2005 RTM. DoStuff is the method that will be called to invoke this version dependent code.

template <int i> class VcSelectorClass

{

public:

  static char Dummy[10];

  static void DoStuff(void)

  {

    //replace with own implementation

    cout << "RTM" << endl;

  }

};

 

This specialization represents the case of behavior of VC2005 SP1 and higher. The specialized case is for the integer 4. This is the size that should be reported by sizeof when a reference to an array is passed. All versions before SP1 report the actual array size. All version after and including SP1 report 4.

Of course, in a 64 bit world, this would be 8, but the original problem has to do with compilation for use with VC6 executables. These can only be 32 bit, so no attention has to be given to this detail for now.

template <> class VcSelectorClass<4>

{

public:

  static void DoStuff(void)

  {

    //replace with own implementation

    cout << "SP1" << endl;

  }

};

 

I don't want the user to supply his own sizeof calculation because a) it is easy to make a mistake, and b) it is butt-ugly. The typedef solves this very neatly.

To alleviate the need for the user to declare his own array somewhere, one is declared in the generic template.

typedef VcSelectorClass<sizeof(&VcSelectorClass<0>::Dummy)> VcSelector;

 

int _tmain(int argc, _TCHAR* argv[])

{

  //the template is specialized at compile time, so only 1

  //codepath is included in the compiled code.

  VcSelector::DoStuff();

  return 0;

}

Invoking the behavior is pretty elegant, and the templates are specialized at compile-time so only 1 code path is included in the compiled code.

You might wonder why I used template classes instead of template functions.

The reason is simple. At this moment, specialization of template functions is not part of the C++ standard. I have read reports that it is being considered, but for now my only choice was to use template classes. Unfortunately this makes the whole thing a little more verbose.

Solution 3

By now the original problem was solved good and well, but I felt the need to geek out and create something that I would put in production code without fear that it would come back to haunt me.

After all, the current solution would only work if the behavior has a simple difference between pre SP1 and post SP1.

Older versions might even need a different implementation, and post SP1 versions might not be backwards compatible.

And 64 bit compiles would also need to have a defined behavior, in case the VC6 requirement is not valid anymore.

For clarity I removed some of the code comments.

#ifdef _WIN64

//no 64 bit implementation yet.

#pragma message ("Warning: DoVcVerDepStuff has no 64 bit implementation yet")

#else

#if _MSC_VER < 1400

//implementation for VC2003 and earlier

void DoVcVerDepStuff(void)

{

  //implementation here

}

#elif _MSC_VER == 1400

template <int i> class VcSelectorClass

{

public:

  static char Dummy[10];

  static void DoStuff(void)

  {

    //RTM implementation

  }

};

 

template <> class VcSelectorClass<4>

{

public:

  static void DoStuff(void)

  {

    //SP1 implementation here

  }

};

 

typedef VcSelectorClass<sizeof(&VcSelectorClass<0>::Dummy)> VcSelector;

#define DoVcVerDepStuff VcSelector::DoStuff

 

#else

//there is no post SP1 implementation yet

#pragma message ("Warning: DoVcVerDepStuff has no post VC2005 implementation yet")

#endif

 

#endif //_WIN64

 

int _tmain(int argc, _TCHAR* argv[])

{

  DoVcVerDepStuff();

      return 0;

}

Conclusion

The original problem was solved neatly. Demo projects of the 3 solutions are available for download under the MIT license.

If anything, this proves that you should always update the version numbers for anything that get updated even 1 bit.

In this case, the problem could just as easily have been with the CRT instead of the compiler itself I don’t know enough of the original problem to judge that, but in this case the compiler version would have sufficed.

It is worth to note that I cannot possibly anticipate the behavior of another service pack for VC2005. If the version macro is updated, the programmer will be warned. If it isn’t, there is no other option than to look for another fixed bug and use that. But that cannot be accounted for at this moment. It will be the responsibility of the programmer to see if a new check is needed.

Attachment: VcVerCheck.zip
Posted: Jan 18 2007, 03:01 PM by vanDooren | with no comments
Filed under: