In my opinion, you should install and toy around with two main options for a while and stick with one of them for your own development: Visual C++ Express and MinGW.
Visual C++ Express
It's always a nice thing to have it installed, as it enables you to compile and try out lots of code (not MFC, though). You should only install an older version if you don't have access to code with which you must share the same C runtime, such as exchanging file descriptors that the other party must be able to
fclose(), or exchanging memory pointers that the other party must be able to
The compiler is cl.exe. The compiler differences will bug you when and where you least expect them, but it's generally affordable to make the same source files compile in several compilers.
The make tool is nmake.exe. Nmake is mostly like other makes, but be careful about hairy stuff such as inline inclusion and proprietary extensions, and the execution of shell commands (sh vs cmd.exe) and *nix tools. As a basic rule, don't try to maintain Makefiles that are valid on both nmake and another make, it's much easier to just maintain separate makefiles.
The IDE is quite nice, although the Express version doesn't have it all, such as remote debugging.
MASM is downloadable separately from the Express editions, but it's included with the paid versions.
MinGW + MSYS
MinGW is a minimal set of native GNU tools, such as GCC, and ported headers, which are (basically) translations from VC++ specifics to GCC's ones. The main advantage over Cygwin is that it compiles bare apps and libraries which don't depend on a middle layer that emulates POSIX (cygwin1.dll).
MSYS brings you part of the remaining toolchain you're used to, such as sh and make. These are not entirely bare apps (msys-1.0.dll takes the place of cygwin1.dll), but you're only going to make use of them, you won't generally target for MSYS unless you're developing MSYS.
You must be aware that it runs in an environment very similar to Cygwin, especially about pathnames. They're mostly interchangeable and most tools do the right thing with whatever kind of pathname you pass them, but keep in mind that this might not always be the case, especially if a pathname has spaces.
Installation may be a hassle, you must follow the most recent instructions closely and try out one thing or another between steps. However, once it's done, you may just copy the entire tree to anywhere else and point to it.
MinGW comes with as, so you don't have to learn another assembler.
I've experienced better runtime performance with the output of VC++, but on rare times I'd wish it was as easy to get the development environment up and running from thin air (e.g. another directory, a USB pendrive) like with MinGW+MSYS.
But again, I've felt this need very few times. The machine-wide installation of VS or VC++ is straight forward. It's possible to make it portable, but you'll have to either install it once or get other tools to extract what you need from the installer, then you need to know what to copy and change vcvars*.bat to rely on its current path, rather than on environment variables such as VS*COMNTOOLS and some hardcoded values such as the setting of VCINSTALLDIR. And last but not least, be sure you're not violating some license when and where you use it.
VC++ version from 2003 onwards will make your output EXE or DLL depend on Microsoft's C runtime for that version as an assembly. Reread the last sentence. That usually means you'll just have to make sure the machines where you deploy your code should have the corresponding runtime msvcr*.dll files properly installed. The redistributable packages are available somewhere inside VC++ directory and from http://download.microsoft.com.
On the other hand, most open-source projects expect GCC, so you'll get less compilation warnings and a little lower risk of getting weird bugs at runtime with such projects. At least, bugs that are related with the module itself, it might have other bugs due to interaction with libs compiled with some other compiler (e.g. mismatched calling conventions).
If I remember correctly, MinGW's output depends on msvcrt.dll, an aging version of Microsoft's C runtime that didn't change name between VC++ versions up until VC++ 6.0. Unofficially, it's present in every Windows installation, so you'll probably not have to bundle it. On the other hand, you might want to bundle a version that you know it works, so that it may replace an older version of that DLL. In the case of an EXE, you may simply put it on the same directory instead, if you find that the installed one is older.
If you're just extending your knowledge, I don't recommend you go for a paid version of Visual Studio. If you were more commited into it, like extending your products or your services for another platform, then it might be worth it. Also, before the 2012 versions, you didn't have the x64 cross-compiler and OpenMP in the Express editions.
From the API point of view, you'll have to remind yourself to not get frustrated wth it in comparison with *nix APIs, at least not until you (try to) understand what historical reasons there might be and the difference in architectures.
Get used to:
- Huge parameter lists or huge-struct parameters
- Security parameters
- Parameters that can be
NULL for reasonable defaults (e.g. security parameters, in most cases)
- Reserved parameters that must be
NULL or must be zero (m.b.z.). They may be repurposed in newer Windows versions
- Functions that do a trillion different things (e.g.
- Function names that end in
For the Win32 API, use
GetLastError(), it's thread-local. Not all functions report errors through it, and be careful that some functions that do won't set the last error to success (0), in which case you should reset it with
SetLastError() before invoking them. For the C runtime functions, keep using
errno, it's also thread-local.
The various graphical Win32 APIs have a high learning curve (which native GUI API doesn't?). Some window messages are obsolete, some operations are not officially documented (e.g. changing the owner of a Window), some things are old-school functions-and-structs for window creation and message dispatching while others are OLE/COM objects inside containers with callback dispatch-by-name events, etc. But if you follow the specs and use as few undocumented behaviour as possible (better yet, none), chances are your software will work unmodified for many years to come.
From time to time it's often good to know when are your calls entering kernel-mode (not kernel32.dll, it's user-mode).
My experience with abstracting required features between several OSes is that you must understand the architecture of the OSes you want to target before anything else. Unfortunately, even this analysis will be based on the current state of things, so try to make decisions based on the parts of the architecture that are the least probable to change (much) over time.
The various Win32 asynchronous I/O APIs, for example, are quite a pleasant surprise, despite being a bit complex to start with. But to take advantage of it, you must be prepared for a post-poned coding style when using such abstraction/library. And note that if you use I/O completion ports, they manage thread pools and less context switching for you, so you'd either not take advantage of that for a lowest-common-denominator abstraction, or you'd implement at least the thread pool on other OSes to make it scalable at the expense of a bigger library.
Note: I'm not advising you to make a cross-platform asynchronous I/O library, there are a few already.
Finally, the console is not like the usual *nix terminals. It's a blocking user-mode device that kernel32.dll masquerades into calls to an internal service made available by a sub-system. There's no asynchronous I/O, and line mode implies either that you rely on Windows' own line mode editing while blocking for a new line or that you handle raw console input. The console handles are not duplicatable across processes and are thus not inheritable either, but the console itself may be shared with the child process (the default). You don't get a terminal where you type ahead in echo mode until it's read (exception: Cygwin, because it implements its own raw console input handler).