¶Metro style apps and the Win32 API
It's been way too long since I posted, hasn't it? Time to change that.
There's been a bit of discussion about the difference between "desktop apps" and "Metro style apps" on Windows 8, particularly as it pertains to Windows RT (previously Windows on ARM), which currently isn't going to allow third party development of desktop apps. Part of the confusion comes from the fact that you can use native C++ code in a Metro style app. Native C++ doesn't mean you can just recompile an existing C++ app written on top of Win32, though, and there are lots of misconceptions about what this means for converting existing apps to Metro.
I haven't actually written a Metro app, but I have looked a bit at what's involved out of curiosity. Looking at the documentation and the requirements, I'm of the opinion that most C++ apps will require a major rework to conform to Metro app requirements. This is based on the restrictions to Win32 API usage that will require invasive changes to either the C++ code itself or the frameworks that the C++ code is based on. Nevertheless, it's useful to actually look at the API lists and get an idea of where the trouble spots are, which is what I'm going to do here. Think of this as a what-if exercise, kind of what would happen if someone at work just asked you for a wild guess at what would need to change to retarget Metro.
Of course, in order to do this we'd need some guidelines, so we're going to assume:
- That you actually want to use Windows and a Metro style app. Yeah, I know, definitely not always true... just pretend for the sake of this exercise, then.
- That the application is appropriate for Metro conversion and doesn't have obviously crazy Win32 usage. Sysinternals Process Explorer is not likely to be a good target app for conversion.
- That the UI is going to be rewritten from scratch, so that it actually suits Metro and a tablet. The entire Win32 UI subsystem is gone, so there's no point in wondering about that.
- That we're mainly talking about a C++ core that's reasonably well architected, i.e. has the Win32 API calls reasonably wrapped and is decently modular, so that if an API call has a direct replacement it isn't a big deal to swap it out. We're looking for stumbling blocks here, like a lack of equivalent functionality.
- That the resultant app will be written to properly conform to requirements and won't try to do things like subvert the API restrictions.
- That you're using Visual C++ and can easily interface to WinRT through C++/CX when needed. Code only compiles under GCC? Uh, sorry, that's a whole other set of problems that I can't cover here. I think it's possible to use WinRT entirely through COM APIs, but it's likely to, uh, build character.
Our main reference is going to be "Win32 and COM for Metro style apps" from MSDN:
...which, as of this writing, is preliminary and subject to change of course. I'm just going to go down this list, as well as lookup APIs to see if they're marked as "desktop only" or "desktop and Metro apps", and see what that means for existing code.
So, with all of this in mind, what stumbling blocks that we might encounter?
Well, time to update that file I/O code, starting with using CreateFile2() instead of CreateFile(). Not CreateFileEx()? Someone on the Windows team got tired of *Ex() jokes?
FlushFileBuffers() is allowed, which surprises me as it's one of the best ways to absolutely destroy system performance. I suppose it's still needed for reliable storage.
ReadFileEx() and WriteFileEx() are missing... which I think means no I/O completion ports. It seems like in some ways, Windows 3.1/9x style code actually survives the Metro transition better than NT-style code.
Memory mapped files are supported, interestingly. I guess it'd be bad to ban that as memory mapping helps save memory.
DirectInput: Gone, only XInput allowed. Okay, I can't cry about this one, because DirectInput was a maze of GUIDs and enumeration callbacks and wasn't really direct anything anymore, and XInput is much easier to use. The latter does suffer from the "all gamepads are Xbox 360 gamepads" problem, though.
Debugging and Error Handling
No SetUnhandledExceptionFilter(). This function was long abused in DLLs, so this is also understandable, but it's a bit disappointing as there were lots of good uses for this function, like dumping last ditch recovery information before the program dies. I guess the argument is that Metro style apps shouldn't crash....
I don't see SetVectoredExceptionHandler() either. Hmm.
Direct2D and Direct3D 11. Missing from this list: GDI, GDI+, DirectDraw, Direct3D 9, Direct3D 10, and OpenGL. I don't think there are many programs which wouldn't have to have their graphics layer rewritten for this one.
D3DCompile() is at least supported, which is handy if you need to do really crazy shader transformations. Interestingly, D3DDisassemble() is also supported.
Missing: WaveOut, DirectSound, Multimedia APIs (including Video for Windows), DirectShow. Okay, I'm definitely not shedding a tear for the last two. The replacements: Media Foundation, XAudio2, and Core Audio.
Not having used Media Foundation, I have to wonder how codecs work in this environment. Pluggable codecs help with format independence -- what basically made VirtualDub possible to begin with -- but they're also an magnificent mechanism for app cross-breakage.
XAudio2 doesn't bother me much... particularly if the program I'm working on has its own mixer and just wants a low latency output stream anyway.
Collateral damage: the removal of the multimedia APIs also removes timeGetTime(). I've seen systems where this is the only reliable timing source with millisecond precision. Hopefully QueryPerformanceCounter() is reliable on all Windows 8 systems.
No Winsock or WinInet. Perhaps understandable, given a desire to prevent random connections and the accumulated layers of cruft in Winsock, but there's a lot of tried and tested socket code out there, which will become less tried and tested when rewritten to use equivalent WinRT APIs.
The allowed DHCP calls are curious.
I don't know a whole lot about printing, but given that historically printing has been tied to GDI, it's not surprising that a lot has changed here.
This is where things get interesting.
A lot of COM stuff is still here. COM is underneath WinRT, so this doesn't surprise me. Looks like you'll have to finally change CoInitialize() to CoInitializeEx().
OutputDebugStringA() is absent, but OutputDebugStringW() is allowed. Don't know about you, but I don't generally have my trace messages in Debug builds in wide chars -- wrapper time. For that matter, do they really allow OutputDebugStringW() in a shipped app? That's one of my pet peeves. Traces in dev builds only, please.
GetModuleHandle() and GetModuleHandleEx() are missing. The latter is unfortunate as it's how a library can get the HINSTANCE of the DLL or EXE it's in, but maybe that's less needed without the USER APIs. I guess it's back
to the VirtualQuery() trick otherwise.
VirtualAlloc(), etc. are missing... Ow. That's painful, as that's the main way you allocate memory with alignment requirements, like for a custom allocator -- your own malloc() replacement, or an even more specialized heap that has major alignment requirements like a small block allocator or garbage collector. (HeapAlloc STILL doesn't support 16 byte alignment for SSE.) It's also how you generally allocate memory suited for FILE_FLAG_NO_BUFFERING... although it looks like CreateFileMapping() is still available, if you're willing to juggle the file handles too.
No CreateThread(). There's a Windows.System.Threading namespace suggested as a replacement, but it has a task API instead of a thread API. It looks like it might be abusable to simulate threads, although I'm not sure of the ramifications.
No HeapCreate(). That was an easy way to segregate allocations into another heap without dragging your own allocator.
Sleep() is gone. Okay, they want you to use another mechanism that doesn't lock up a thread... but occasionally, all you need is a plain sleep. I saw someone suggest WaitForSingleObject(GetCurrentThread()) with a timeout... I think I would be mortified to have to use that.
WaitForSingleObject() and WaitForMultipleObjects() have been deprecated instead for their Ex variants... err, OK. I'm not even sure you're allowed to use the APIs that would require an alertable wait.
The Ex/ExW versions of CreateEvent(), CreateMutex(), InitializeCriticalSection(), etc. are required. Okay, directly mappable, but pretty much guarantees revision of any threaded code. Looks like the basic semantics are the same at least so if everything's wrapped you can swap the isolation layer.
Hey look, PulseEvent() is finally dead... after being unusable for all 18 years that it existed....
No GetProcessAffinityMask(). This was one of the ways to tell how many hardware threads you should use. GetSystemInfo() is gone too... that makes it a bit hard to tell how many hardware threads are available, with 2+ being the most interesting test. Maybe there's a WinRT API for it.
FlushInstructionCache() is missing, VirtualAlloc/VirtualProtect() is missing, and CreateFileMapping() cannot set the EXECUTE flag. From what I can tell, this means you aren't allowed to do any sort of dynamic code generation in a Metro style app. This kinda sucks. Any high-performance emulator or VM is going to want to do this, and it's also useful for trampolines from callback pointers. I wonder if writable code segments are allowed.
Again, taking a look at the list above, there's a pretty good chance that the C++ library you might have been planning to reuse in your Metro style app will require some porting work and may possibly have blocking API usage issues, even if you're targeting only x86/x64 and your library is only a GUI-less utility library. Check the API list carefully and make sure the app you have in mind is even possible before estimating porting time.