Running IPv6 Code in Multiple Windows Environments

Building a Program for the IPv6 Environment

I'll take the same old program shown in Listing 1 and demonstrate how you can target that program for the IPv6 environment explicitly. All that you need to do is to add a preprocessor directive that instructs the compiler to build the program for Windows XP or later system. The directive is _WIN32_WINNT and its value for different operating systems is given in the following table:

Operating System

Value of _WIN32_WINNT

Windows 2000

0x0500

Windows XP

0x0501

Windows Server 2003

0x0502

The values for _WIN32_WINNT are the version numbers that are used in Platform SDK header files. Defining this directive explicitly in the program enables the SDK APIs added for that version of OS. For example, defining this directive as 0x0501 will enable Windows XP-specific functions. Because IPv6 calls are supported from Windows XP (SP1) onwards, you can define _WIN32_WINNT>=0x0501 to target a program for IPv6 environment. You can do it in two ways:

Define the macro in the program itself, before including any header file. as shown in Listing 4:

Now, let us also perform dumpbin/imports on the resulting executable of this program. It shows the output given in Listing 5. Again, for simplicity's sake, I'm showing imports for WS2_32.dll only, but actual dumpbin ouput also has quite a few imports from KERNEL32.dll.

You'll notice that this time the executable has only one import from WS2_32.dll; that is for the function we actually called in the program.

Comparing the Two Programs

The output of dumpbin/imports in Listing 5 reveals one important concept. Microsoft has written header files for the Winsock2 API in such a way that the compiler can choose the appropriate functions to call based on a targeted environment. For the program in Listing 1, we didn't specify any particular environment to target. So, the compiler generated code that can run on all the environments, even on the environments where IPv6 is not supported. But for the program in Listing 4, we explicitly specified a preprocessor directive to target Windows XP. In this case, the compiler generated code with new IPv6 calls, which can run only on those environments that have IPv6 support.

That explains how Microsoft maintains backward compatibility for the code written with newly added IPv6 calls.

Preparing the Environment for IPv6

Now, if you run the executable from the program in Listing 4 on a default installation of Windows XP (SP1), it will run successfully, but the getaddrinfo() call will fail. The reason is that IPv6 protocol is not installed by default. You can install it by running the simple command "ipv6 install" on a command prompt. In Windows Server 2003 also, IPv6 doesn't come in a default installation. You can install it by using the Properties dialog box of Local Area Connection under Network Connections. Once you install the protocol, the program will run successfully and give the expected results.

Behind the Scenes

Now, you know exactly what happens that enables the backward compatibility. But, how does it happen? In this section, we'll explore the actual mechanism that is making this happen.

The getaddrinfo() function is declared in ws2tcpip.h. At the bottom of this header file, you will find the following code:

//
// Unless the build environment is explicitly targeting only
// platforms that include built-in getaddrinfo() support, include
// the backwards-compatibility version of the relevant APIs.
//
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT <= 0x0500)
#include <wspiapi.h>
#endif
In the file wspiapi.h there is following preprocessor instruction:
#define getaddrinfo WspiapiGetAddrInfo

So, the call to getaddrinfo() goes to WspiapiGetAddrInfo(). The implementation of this function is provided in this header file only. Do not be surprised to see the implementation of the function in a header file. Microsoft had to do it to provide the backward compatibility. At run time, the calls to getaddrinfo() actually call the implementation of WspiapiGetAddrInfo() provided in this header file.

Internally, WspiapiGetAddrInfo(), with the help of another function, WspiapiLoad(), calls the appropriate implementation of getaddrinfo() as per the current environment. This choice is made in the following steps:

It tries to find the getaddrinfo in ws2_32.dll (for Windows XP and Windows Server 2003) using LoadLibrary and GetProcAddress calls. If successful, it does the mapping.

If the preceding step fails, it tries to find the getaddrinfo in wship6.dll (for the IPv6 technology preview on Windows 2000). If successful, it does the mapping.

If the preceding step also fails, it maps the call to the WspiapiLegacyGetAddrInfo routine. This function is implemented in the wspiapi.h header file. This routine internally calls legacy Winsock routines already available in ws2_32.dll (for systems with no IPv6 support) to provide the same functionality.

Exactly the same procedure that is followed for calling getaddrinfo() is also followed for the other two IPv6 calls, getnameinfo() and freeaddrinfo(). The ten function imports that you saw in dumpbin/imports output in Listing 2 was because of the implementations of these functions provided in the wspiapi.h file.

Note that all this procedure is followed only when you do not define the _WIN32_WINNT directive or its value is less than or equal to 0x0500. So, the executable of the program in Listing 1 will go through all this procedure. But, if the _WIN32_WINNT macro is defined and its value is more than 0x0500, the compiler maps the function call statically and the header file wspiapi.h is not included.

Conclusion

The support for IPv6 is of varied levels in different versions of Windows operating systems. But, you still can write code with new IPv6 functions and run that code on earlier versions of Windows operating systems that do not have IPv6 support. Internally, this is done by dynamically finding the implementation of new functions at run time. If the implementation of new functions is not found, the calls to them are mapped to older IPv4 functions. If you do not want this dynamic searching, you can define a preprocessor directive to target the IPv6 environment explicitly. The good thing about this is that everything happens behind the scenes and programmers do not need to worry about backward compatibility.

About the Author

Sanjay Narang is a software analyst at Hewlett-Packard, India. He is certified as a MCSD.NET and SCJP. He is a Post Graduate from the Indian Institute of Information Technology, Bangalore.

Sanjay has been involved in designing and developing Micosoft-based solutions for more than five years in various domains: CRM, eGovernance, and Instant Messaging. He can be contacted at sanjai_narang@yahoo.com.