Last week I’ve decided it’s time to apply a few long overdue patches some people have submitted. The main issue with patches is that the patch submitter and patch applier are never the same person, unless you’re on lithium in which case the code is bound to be intriguing any way you spin it. But the lack of clear coding guidelines or my code review process is a whole other topic.
One of the patches was Anders’ WinVer.nsh patch for Windows Server 2008 support along with some other nifty little features. You would think this would be a pretty simple patch, but Microsoft had a surprise for us in this case. I admire Microsoft for their dedication for backward compatibility and basic API coherence, but in this case of version detection, they got it a bit mixed up. There are three API functions to get the version, two of them work on all versions of Windows and one of them has two operation modes. To get special features information there’s another completely unrelated inconspicuous function. The format of the returned data depends on the version and on operation mode used. In short, it’s a bag full of fun and games and there’s never a dull moment testing every little change on every possible configuration of Windows.
For the original version of WinVer.nsh, I used the simplistic GetVersion API which requires about 3 lines of code. Later on a patch was submitted to support verification of service pack numbers which required the usage of GetVersionEx’s two modes of operation. This required quite a bit more code, but that code was only used when SP were specifically checked. With the latest patch for Windows Server 2008 support, the simplistic API was no longer enough and a full blown function using every possible API and doing a lot of math and bit shuffling was required. And therein lies the catch.
As we yet to have developed real code optimization mechanisms, code duplication makes the installer bigger and bigger is not better in this case. The code could go into a function which will be called by every usage of WinVer.nsh, but that would mean a warning will be generated in case the function is never called because it can’t be optimized. A requirement to declare the usage of WinVer.nsh could be added, but that would break the number one rule I’ve learned from Microsoft – backward compatibility. All three issues are on the top 10 frequently asked questions list and getting my costumers a reason to ask them even frequently-er is not in my wish list.
As the code size grew bigger WinVer.nsh, I started pondering of a way to solve this. The obvious solution would be adding code optimization and that’s already functioning neatly in my beloved nobjs branch that’s sadly not yet ready for prime time. And so I had to think of another idea that could work with the current branch and so Artificial Functions were conceived. Instead of letting the compiler create the function, I’ve used some of the lesser known features to create them on my own. A combination of runtime and compile-time black magia using both old and new features allowed me to get rid of the code duplication.
To make sure the code of the function isn’t inserted more than once, the good old !ifndef-!define-!endif combo is used. But the function can be called from more than one scope and so it must be globally locatable. Exactly for this purpose, global labels were added over six years ago. However, that’s not all as the function must somehow return control to the original code that called it. To do this Return is used at the end of the function’s code and Call is used to treat the global label as a function and build a stack frame for it. Last but not least, we have to do deal with uninstaller functions that can’t jump to code in the installer as they don’t share the same code. The new __UNINSTALL__ definition saves the day and helps differentiate installer’s and uninstaller’s code.
!macro CallArtificialFunction NAME
!ifndef __UNINSTALL__
!define CallArtificialFunction_TYPE inst
!else
!define CallArtificialFunction_TYPE uninst
!endif
Call :.${NAME}${CallArtificialFunction_TYPE}
!ifndef ${NAME}${CallArtificialFunction_TYPE}_DEFINED
Goto ${NAME}${CallArtificialFunction_TYPE}_DONE
!define ${NAME}${CallArtificialFunction_TYPE}_DEFINED
.${NAME}${CallArtificialFunction_TYPE}:
!insertmacro ${NAME}
Return
${NAME}${CallArtificialFunction_TYPE}_DONE:
!endif
!undef CallArtificialFunction_TYPE
!macroend
When combined all together, it not only solves the code size issue for WinVer.nsh, but also rids the world of two very frequently asked questions about our standard library. It took a few good hours, but after converting FileFunc.nsh, TextFunc.nsh and WordFunc.nsh to use the new Artificial Functions; there’s no longer a need to use forward decelerations for those commonly used functions and calling them in uninstaller code is no different than calling them in the installer.
!include "FileFunc.nsh"
!insertmacro GetFileExt
!insertmacro un.GetParent
Section Install
${GetFileExt} "C:My DownloadsIndex.html" $R0
SectionEnd
Section un.Install
${un.GetParent} "C:My DownloadsIndex.html" $R0
SectionEnd
Goes on a diet and elegantly transforms into:
!include "FileFunc.nsh"
Section Install
${GetFileExt} "C:My DownloadsIndex.html" $R0
SectionEnd
Section un.Install
${GetParent} "C:My DownloadsIndex.html" $R0
SectionEnd
I love it. This trick is so sinister. It reminds me of the days Don Selkirk and Dave Laundon worked on LogicLib. Coming to an installer near you this Christmas.