Microsoft's compiler is filled with weird quirks and was literally not C99-compliant until 2019, which means it is quite difficult to port software meant for a more standards-compliant compiler to it. This page means to compile everything that we may want or need to know in order to port our code to this awful compiler.
Since Visual Studio 6.0 is extremely old it's expected that you may encounter some roadblocks when trying to install it on more modern systems. If you're on Windows XP or earlier you don't have to do anything, simply install as usual and everything should work out-of-the-box.
If you are on Windows Vista or 7, as per this blog post, you should deselect the Tools | OLE/COM Object Viewer when installing, otherwise the installation will fail with an “RegCreateKey failed for \Interface\OLEViewerIViewerCLSID. Access is denied” error. Now if you are on Windows 8, 10 or later you should follow these instructions and at the time can only install Visual Basic 6, no Visual C++ 6 yet.
The most dreaded aspect of programming for Windows… Having to use UTF-16 for Unicode is just awful, but sadly it's what was available at the time that Microsoft needed strings that could represent characters from all over the world. This horrible decision in the past means that today where we live in a more civilized, UTF-8 society, we have to be constantly performing conversions between MBCS and WCHAR when programming for Windows, this can be easily done with a couple of helper functions since Microsoft's API for this is absolutely awful:
/** * Converts a UTF-8 multibyte string into an UTF-16 wide-character string. * @warning This function allocates memory that must be free'd by you! * * @param str UTF-8 string to be converted. * * @return UTF-16 wide-character converted string or NULL if an error occurred. */ wchar_t *utf16_mbstowcs(const char *str) { wchar_t *wstr; #ifdef _WIN32 int nLen; /* Get required buffer size and allocate some memory for it. */ wstr = NULL; nLen = MultiByteToWideChar(GL_CODEPAGE, 0, str, -1, NULL, 0); if (nLen == 0) goto failure; wstr = (wchar_t *)malloc(nLen * sizeof(wchar_t)); if (wstr == NULL) return NULL; /* Perform the conversion. */ nLen = MultiByteToWideChar(GL_CODEPAGE, 0, str, -1, wstr, nLen); if (nLen == 0) { failure: MessageBox(NULL, _T("Failed to convert UTF-8 string to UTF-16."), _T("String Conversion Failure"), MB_ICONERROR | MB_OK); if (wstr) free(wstr); return NULL; } #else size_t len; /* Allocate some memory for our converted string. */ len = mbstowcs(NULL, str, 0) + 1; wstr = (wchar_t *)malloc(len * sizeof(wchar_t)); if (wstr == NULL) return NULL; /* Perform the string conversion. */ len = mbstowcs(wstr, str, len); if (len == (size_t)-1) { free(wstr); return NULL; } #endif /* _WIN32 */ return wstr; } /** * Converts a UTF-16 wide-character string into a UTF-8 multibyte string. * @warning This function allocates memory that must be free'd by you! * * @param wstr UTF-16 string to be converted. * * @return UTF-8 multibyte converted string or NULL if an error occurred. */ char *utf16_wcstombs(const wchar_t *wstr) { char *str; #ifdef _WIN32 int nLen; /* Get required buffer size and allocate some memory for it. */ nLen = WideCharToMultiByte(GL_CODEPAGE, 0, wstr, -1, NULL, 0, NULL, NULL); if (nLen == 0) goto failure; str = (char *)malloc(nLen * sizeof(char)); if (str == NULL) return NULL; /* Perform the conversion. */ nLen = WideCharToMultiByte(GL_CODEPAGE, 0, wstr, -1, str, nLen, NULL, NULL); if (nLen == 0) { failure: MessageBox(NULL, _T("Failed to convert UTF-16 string to UTF-8."), _T("String Conversion Failure"), MB_ICONERROR | MB_OK); return NULL; } #else size_t len; /* Allocate some memory for our converted string. */ len = wcstombs(NULL, wstr, 0) + 1; str = (char *)malloc(len * sizeof(char)); if (str == NULL) return NULL; /* Perform the string conversion. */ len = wcstombs(str, wstr, len); if (len == (size_t)-1) { free(str); return NULL; } #endif /* _WIN32 */ return str; }
Also by default if you're using an old compiler like Visual C++ 6 you need to manually set up the project to use Unicode and be compatible with more modern versions of Windows. In order to do this you simply have to follow the following steps:
UNICODE and _UNICODE preprocessor symbols:UNICODE,_UNICODEwWinMainCRTStartupwmainCRTStartupSometimes we want to have access to “special folders” like the Desktop, My Documents, AppData, just to name a few. Most of these have environment variables associated with them, but expanding environment variables in a program seems very bad, thankfully Microsoft provides a couple of interesting functions for us to use in order to fetch these paths.
If we are targeting Windows 95+ (with the Internet Explorer upgrade) we must use the SHGetSpecialFolderPath function. If we are targeting Windows 2000+, or can install a redistributable, the recommendation is to use SHGetFolderLocation since the previous function call has been deprecated in this version. If we are targeting Windows Vista+ then we can use the even fancier SHGetKnownFolderPath.
Here are some examples of how to use these functions:
A collection of functions that may require a bit of shimming if you come from the saner world of POSIX:
As usual, when it comes to Windows, multithreading is not as simple or easy as under a POSIX system. Microsoft made sure to implement their multithreading API in multiple ways, one you can use the CRT, the other one you can't. In any case, just ignore everything and follow these instructions.
One of the most used capabilities when dealing with parallel operations are mutexes, they allow us to ensure that resources that are shared between multiple processes can be safely guarded against race conditions. Windows has the concept of mutexes, but they are extremely bad in terms of performance, although they have the “benefit” of working across processes. Usually what you want to use is the CRITICAL_SECTION and its associated functions, these resemble much of the pthreads API, although with that characteristic Win32 API flavor, and are very lightweight.
As with everything in Win32 land the Windows CE implementation of multithreading suffered a lot, especially when dealing with PocketPCs and non-Handheld PCs. We can understand that this was done due to the lack of resources of such devices, but still. Here are some online resources on the subject:
Here are some resources that were used as references to write this section. These will most likely go into greater detail on the subject and give you a greater understanding of the API instead of just providing a reference.
If you want to have proper compatibility with every single 32-bit Intel-based Windows release ever made since Windows 95 you are required to use Microsoft's Visual C++ 6, which is a whole can of worms, but it is what you need to do in order to have full backward compatibility. Something that alleviates a bit of the pain of using this toolchain is the Windows Server 2003 Platform SDK, which can be downloaded using the following instructions.
You may need to install each SDK individually from the setup folder instead of running setup.exe, since this may cause issues if you're running a more modern version of Internet Explorer.
Porting an application from the standard in UNIX land Berkley sockets to WinSock might be a bit of a nightmare depending on the architecture of the project, if your application was developed with portability in mind, even going as far as wrapping every single socket function, you can more easily port your code to Winsock, but you may still run into problems.
As soon as we try to compile any piece of code that was made for a more standardized sockets implementation one thing will become extremely apparent: You'll be missing a lot of stuff. In order to get a saner environment for socket programming under Windows it's required to install the Windows Server 2003 Platform SDK.
According to Microsoft's documentation, you should not include windows.h when you are using Winsock2 since it tries to include the old Winsock 1.1 stuff which generates loads of conflicts with the redefinitions in the Winsock2 headers. In order to get around this, since your application or libraries might be including windows.h you should define globally (in the project's settings) the WIN32_LEAN_AND_MEAN preprocessor definition.
Another difference from other platforms is that the setsockopt() function takes its optval parameter as a const char * instead of the more common void * requiring your parameters to be redefined just for Windows.
Whenever you must catch errors from resulting socket interactions, where under a POSIX system you simply check the contents of the errno variable after a function returns -1, under Winsock2 you must instead check if the function returned SOCKET_ERROR, and then call and store the return value of WSAGetLastError() and compare against Microsoft's WSA-prefixed error code definitions. This is all described in Microsoft's Return Values on Function Failure MSDN article. For more in-depth information on the subject of how to deal with Winsock2 errors read MSDN articles Handling Winsock Errors and Error Codes - errno, h_errno and WSAGetLastError.
Another interesting quirk of Winsock error codes seems to be the fact that it doesn't have a EAGAIN definition, and that for situations where EAGAIN would've been used it's suggested to instead use WSAEWOULDBLOCK.
Beware that whenever you're dealing with in_addr_t, it isn't defined and you must instead define it as unsigned long.
Yet another interesting quirk was discovered by us where if you have a large TCP packet that gets split at the source and must be reassembled at the client, a call to recv() will fail with a WSAEFAULT even though the buffer is large enough to hold the received information, requiring us to always set the MSG_WAITALL flag. This hasn't been thoroughly tested and we were unable to pinpoint with great accuracy the real cause of the issue, but at least for now, we've attributed it to the partial packet thing. Keep in mind that older versions of the compiler don't have this flag available, so you'll need to implement this functionality manually by looping through the recv() call until all expected bytes have been received.
At the end of your journey if all the compilation errors have gone away be sure to link against the ws2_32.lib library to avoid the dreaded linker errors.
Most importantly after all this make sure to add the following code to your main() application entry point:
int main(int argc, char **argv) { #ifdef _WIN32 WORD wVersionRequested; WSADATA wsaData; int ret; #endif /* _WIN32 */ #ifdef _WIN32 /* Initialize the Winsock stuff. */ wVersionRequested = MAKEWORD(2, 2); if ((ret = WSAStartup(wVersionRequested, &wsaData)) != 0) { printf("WSAStartup failed with error %d\n", ret); return 1; } #endif /* _WIN32 */ /* Perform everything in your application. */ /** * If your application exits prematurely make sure that WSACleanup() gets * called beforehand. */ #ifdef _WIN32 /* Clean up the Winsock stuff. */ WSACleanup(); #endif /* _WIN32 */ return ret; }
Here are the references that were most likely consulted in order to find all of these issues in real-world codebases and catalog them here:
You'll quickly realize that if your target is a GUI Win32 application you won't have access to a console to print stuff out and quickly debug things. This is due to the way that Windows is architected and its subsystems work. In order to get a console you'll either need to allocate one and redirect every stream to that one or create an EDIT control anywhere on your application, create a pipe, redirect the streams to your pipe, then read from it and append the text to your EDIT field. Either way here are some resources to get you started:
Finding memory leaks is always a though thing. Visual Studio has some tools which can be extremely useful when trying to find leaks, but you can also do it programmatically on every debug run of your application:
#ifdef DEBUG #include <crtdbg.h> #endif // DEBUG int _tmain(int argc, TCHAR* argv[]) { #ifdef DEBUG // Initialize memory leak detection. _CrtMemState snapBegin; _CrtMemState snapEnd; _CrtMemState snapDiff; _CrtMemCheckpoint(&snapBegin); #endif // DEBUG // TODO: Do stuff here... #ifdef DEBUG // Detect memory leaks. _CrtMemCheckpoint(&snapEnd); if (_CrtMemDifference(&snapDiff, &snapBegin, &snapEnd)) { OutputDebugString(_T("MEMORY LEAKS DETECTED\r\n")); OutputDebugString(L"----------- _CrtMemDumpStatistics ---------\r\n"); _CrtMemDumpStatistics(&snapDiff); OutputDebugString(L"----------- _CrtMemDumpAllObjectsSince ---------\r\n"); _CrtMemDumpAllObjectsSince(&snapBegin); OutputDebugString(L"----------- _CrtDumpMemoryLeaks ---------\r\n"); _CrtDumpMemoryLeaks(); } else { OutputDebugString(_T("No memory leaks detected. Congratulations!\r\n")); } #endif // DEBUG return 0; }
When developing applications for a given operating system it's always good to know about its standards, defaults, and most importantly its interface guidelines in order to make your application look “native”. In order to get a comprehensive look at Microsoft's recommendations of what Windows applications should look and feel you must check out their Layout Guidelines, most importantly since Visual Studio provides no help in this area, you should check out their Recommended sizing and spacing section which describes in detail how to size and space controls in Win32 applications.
It's not only important to follow Microsoft's guidelines for the UI aspects of the application, but also for the developer side of the application, using naming conventions that are accepted by other Windows developers. Here are a collection of articles related to this topic:
Since Visual Studio is quite an old application with lots of versions and many changes along the years, one of the most important aspects that you need to know in order to develop a project that can be compiled in multiple versions of this platform is the _MSC_VER constant that's defined for every version of the application suite. A comprehensive list of these definitions can be found here, but here are some of the most important ones for us:
170014001200
If you're seeing things like error C2733: second C linkage of overloaded function 'wmemchr' not allowed when trying to compile an application under Visual C++ 6 using the official stdint.h shim it means that you haven't paid attention to the comments in the file itself:
// For Visual Studio 6 in C++ mode and for many Visual Studio versions when // compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}' // or compiler give many errors like this: // error C2733: second C linkage of overloaded function 'wmemchr' not allowed #ifdef __cplusplus extern "C" { #endif # include <wchar.h> #ifdef __cplusplus } #endif
Changing from extern “C” to extern “C++” should fix all your issues.
It's extremely important to note that, only under eMbedded Visual C++ 3, all registry-related functions such as RegOpenKeyEx and RegQueryValueEx return ERROR_INVALID_PARAMETER instead of ERROR_INVALID_PARAMETER when a key hasn't yet been created. Also the lpdwDisposition parameter of RegCreateKeyEx is mandatory under the same compiler, not including it will result in an Access Violation error.
ImageLists are a crucial component from the CommonControls library. They allow us to add icons to our applications in a much easier manner and lets us associate indexes with them, but they do have some gotchas that are nice to know in order to take full advantage of them, specially when developing with multiple versions of Windows in mind.
Most of the time when building ImageLists we want to use them for standardized icons throughout our application, these usually use the small and large metrics defined by the operating system. In order to get these we can use the GetDeviceMetrics() function and ask for the SM_CXSMICON and SM_CYSMICON for small icons and SM_CXICON and SM_CYICON for large icons. The values returned by the function can be directly used to create an ImageList with ImageList_Create(). To learn more about these metrics consult the About icons page of the Microsoft documentation.
When loading icons under Windows CE it's very important to note that the LoadIcon() function only loads icons of size SM_CXICON, if you use these in other size contexts the icon will simply be clipped. As the documentation states:
LoadIconcan only load an icon whose size conforms to theSM_CXICONandSM_CYICONsystem metric values. Use theLoadImagefunction to load icons of other sizes.
Given this explanation we can substitute our uses of LoadIcon() with the following to get a different resolution:
(HICON)LoadImage(m_hInst, MAKEINTRESOURCE(IDI_ICONRES), IMAGE_ICON, SM_CXSMICON, SM_CXSMICON, 0);
The issue now is that we need to keep the HICON handles in a list somewhere so that we can call DestroyIcon() on them when its time to dispose of them. This was done automatically when we used LoadIcon().
Throughout the many versions of Windows, specially in the XP and pre-XP days, it was common for computers to not be capable of rendering in 24-bit or 32-bit colors, so whenever we are creating ImageLists we have to take this into consideration. One easy cop out from this situation is instead of passing a specific bit depth for the ImageList_Create() function we pass the ILC_COLORDDB flag. There was this interesting discussion on when to use this flag.
If you think you could have a sane standard library for C++ under Microsoft's eMbedded Visual C++ you are completely wrong. For some reason they shipped it with one that was missing some bits and in some cases it was completely different from the published standard back in the day. You can get a much better and standards-compliant STL (based on SGI's work) from STL for eMbedded Visual C++. Also you should read the additional information on limitations and usage to learn more about it.
To install this we just have to decompress it either in the project's folder or somewhere else, go to Tools/Options…/Directories and add the folder where it was decompressed to the includes path for all targets.