Dual-use Coding Techniques for Games, part 1
win10, win8, winphone, xboxOriginally posted to Chuck Walbourn's Blog on MSDN,
Writing shared code for Windows Store, Xbox, Universal Windows Platform (UWP) apps, and Win32 desktop apps
Introduction
Apps written for the Windows Store make use of the Windows Runtime (WinRT) and a restricted subset of Win32 APIs located in the core API family. Traditional Win32 desktop apps have access to a larger desktop API family, but this is subject to various levels of OS support required for each function. These two taken together can make it challenging to write shared code libraries and helper functions that can successfully compile for both Windows Store apps and Win32 desktop applications supporting Windows Vista, Windows 7, and Windows 8.x.
In general, applications should be written to target either the Windows Store or the Win32 desktop. Windows Store apps make use of a distinct UI, input, system-integration, and presentation model which is not supported for Win32 desktop applications even on the Windows 8 Desktop. Targeting the Windows RT (a.k.a. Windows on ARM) platform requires writing a Window Store app, while targeting down-level platforms such as Windows Vista and Windows 7 require writing Win32 desktop apps. Trying to address both of these with the same EXE is not possible, and each will have significant platform-specific code.
The purpose of this series of posts is to talk about the overlap, and how developers creating shared libraries and game middleware can write C++ code that will successfully compile for both platforms.
Windows phone and Xbox: This article also applies to Windows phone 8.x and Xbox development using the XDK or GDKX (see ID@Xbox)
Windows 10: The guidance here applies to Universal Windows Platform (UWP) apps for Windows 10 as well.
Win32 desktop app | WINAPI_FAMILY_DESKTOP_APP |
Windows Store app Universal Windows Platform (UWP) apps for Windows 10 |
WINAPI_FAMILY_APP (Windows 8.0 SDK)or WINAPI_FAMILY_PC_APP (Windows 8.1 SDK) |
Windows phone 8 (deprecated) |
WINAPI_FAMILY_PHONE_APP |
Microsoft GDKX (Xbox One, Xbox Series X|S) |
WINAPI_FAMILY_GAMES |
Xbox One XDK (Xbox One) |
WINAPI_FAMILY_TV_TITLE |
Xbox One ADK (deprecated) |
WINAPI_FAMILY_TV_APP |
Compiler Toolsets and SDK Selection
To author Windows Store apps, developers must use Visual Studio 2012 which includes the Windows 8.0 SDK. This same toolset can be used to target Win32 desktop apps for Windows 8 (Desktop), Windows 7, and Windows Vista. For this article, the focus is on using this compiler toolset.
Visual Studio 2013 is used to target Windows Store apps for Windows 8.1 and includes the Windows 8.1 SDK. Visual Studio 2015 is used to target Universal Windows Platform (UWP) apps for Windows 10 when using the Windows 10 SDK. These comparison tables have been updated to compare the C++11 features with the newer compilers.
Note that with careful coding, it is possible to also support Visual Studio 2010 with the Windows 8.x SDK for building Win32 desktop apps. In some specific cases some extra functionality is needed that is otherwise handled by Visual Studio 2012’s C++11 Standard Library, and this means restricting language feature use to VS 2010’s C++0x support and avoiding the use of C++/CX language extensions.
C++11 Language Feature | VS 2010 | VS 2012 | VS 2013 | VS 2015 |
nullptr |
ü | ü | ü | ü |
static_assert |
ü | ü | ü | ü |
override / final \* | ü | ü | ü | ü |
Lambda expressions | ü | ü | ü | ü |
Rvalue references | ü | ü | ü | ü |
decltype |
ü | ü | ü | ü |
auto |
ü | ü | ü | ü |
Strongly typed enumerations | ü | ü | ü | |
Forward declared enumerations | ü | ü | ü | |
Ranged-based for loops |
ü | ü | ü | |
Variadic templates | ü | ü | ||
Uniform initialization and initializer lists | ü | ü | ||
Delegating constructors | ü | ü | ||
Raw string literals | ü | ü | ||
Explicit conversion operators | ü | ü | ||
Default template arguments for function templates | ü | ü | ||
Alias templates | ü | ü | ||
Defaulted functions | ü | ü | ||
Deleted functions | ü | ü | ||
Non-static data member initializers (NSDMIs) | ü | ü | ||
Attributes, constexpr , ref-qualifiers, inheriting constructors,
char16_t , char32_t , Unicode string literals,
user-defined literals, extended sizeof , inline namespaces,
unrestricted unions, noexcept , thread_local , magic statics,
Universal character names in literals |
ü | |||
C99: __func__ , long long |
ü | |||
Expression SFINAE | û | û | û | Update 1 |
In VS 2010,
final
was implemented assealed
. Also note that VS 2010 considersoverride
to be a MSVC extension and generates warning C4481.
VS 2017 supports all C++11 features as well as C++14 and C++17. VS 2019 supports C++11, C++14, C++17, and portions of the C++20 draft.
Use of the legacy standalone DirectX SDK is not recommended or supported for Windows Store apps or on Xbox. It includes many legacy technologies that are not supported for this platform, and thus their use complicates the goal of ‘dual-use’ coding. See the blog posts “Where is the DirectX SDK (2021 Edition)?” and “DirectX SDKs of a certain age” for more information.
C++11 Standard Library
The majority of the C++11 Standard Library is supported for both Windows Store apps and Win32 desktop apps. This provides a large breadth of functionality that is common and safe to use for ‘dual-use’ scenarios.
C++11 header |
VS 2010 |
VS 2012 |
VS 2013 | VS 2015 |
<array>, <memory>, <random>, <regex>,
<tuple>, <type_traits>,
<unordered_map>, <unordered_set> |
ü | ü | ü | ü |
<stdint.h>, <cstdint> |
ü | ü | ü | ü |
unique_ptr<T> |
ü | ü | ü | ü |
cbegin(), cend(), crbegin(), crend() |
ü | ü | ü | ü |
<forward_list> |
ü | ü | ü | ü |
<algorithm> and <exception> updates
|
ü | ü | ü | ü |
<allocators> |
ü | ü | ü | ü |
<codecvt> |
ü | ü | ü | ü |
<system_error> |
ü | ü | ü | ü |
emplace(), emplace_front(), emplace_back(), etc. |
ü | ü | ü | |
<chrono> \* |
ü | ü | ü | |
<ratio> |
ü | ü | ü | |
<scoped_allocator> |
ü | ü | ü | |
<atomic>, <condition_variable>, <future>, <mutex>,
<thread> |
ü | ü | ü | |
<intializer_list> |
ü | ü | ||
std::make_unique<T> (C++14) |
ü | ü | ||
C99: <stdbool.h>, <complex.h> / <ccomplex,
<fenv.h> / <cfenv>,
<inttypes.h> / <cinttypes>, <ctgmath> |
ü | ü | ||
C99: <uchar.h> / <cuchar> |
ü | |||
C99: <tgmath.h> , some printf format specifiers |
ü |
VS 2012 / VS 2013’s
high_resolution_clock
has some known issues. This is fixed in VS 2015 or later.
The majority of Visual C++ functions in the C Runtime are available for Windows Store apps, but there are some specific headers which are not fully available.
Visual C++ header | Notes |
agents.h
concrt.h |
The majority of the Concurrency Runtime (ConcRT) is available. There is, however, no support for the advanced scheduler (i.e. schedule groups, contexts) |
concrtrm.h |
The Concurrent Runtime (ConcRT) resource manager is not available to Windows Store apps. |
conio.h |
No functions in this header are available |
ctype.h, cctype |
isleadbyte and _isleadbyte_l are not available |
direct.h |
Only _mkdir, _rmdir, _wmkdir, and _wrmdir are available |
io.h |
_pipe is not available |
locale.h, clocale |
Obsolete locale functions are not available |
malloc.h |
_resetstkoflw is not available. |
mbctype.h, mbstring.h |
All multi-byte (_ismb, _mb* ) functions are not available. |
process.h |
Most process and DLL related functions are not available.
exit and abort are the only functions available for Windows Store apps. |
stdio.h, cstdio |
_pclose, _popen, _wpopen functions are not available. |
stdlib.h, cstdlib |
POSIX/DOS-style environment variables and related functions & types are not supported for Windows Store apps.
There is also no equivalent for _seterromode, _beep, or _sleep . |
tchar.h |
The _MBCS mode is not supported for Windows Store apps. You can only use _UNICODE . |
time.h, ctime |
System-time functions (_getsystime, _setsystime ) are not available.
Note you can use Win32 APIs for GetSystemTime and GetLocalTime , but not set the time in a Windows Store app. |
wchar.h, cwchar |
codeisleadbyte, _Isleadbyte_l, _wgetcwd, and _getddcwd are not supported. |
wctype.h, cwctype |
Obsolete is_wctype is not supported. |
Update: If you are interesting in using Boost, take a look at this blog post.
Machine Architectures
Windows Store apps should compile for Windows x86 (32-bit), Windows x64 (64-bit) native, and Windows RT (ARM). Win32 desktop apps should compile for Windows x86 and Windows x64 native. Most C/C++ code should work fine across all platforms if using platform-neutral types.
- Use portable types. Use
size_t
,ptrdiff_t
, and the various<stdint.h>
(<cstdint>
) types (i.e.int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, intptr_t,
anduintptr_t
). - Group pointers in structures and classes. Most data types do not change size when moving to x64 native, but pointers become 8 bytes (known as the "LLP64" data model). The default pack setting for x64 is 16 rather than 8 to ensure structures are padded to a natural alignment including pointers. Mixing pointers with other data types in structures results in more padding than would happen if the pointers were grouped together.
- Prefer C++ style casts. Use of
const_cast<>
,static_cast<>
, andreinterpret_cast<>
rather than C-style casts can help highlight potential pointer-truncation issues more easily. - Use maximum warnings (
/Wall
). A number of warnings that tend to highlight 64-bit portability issues include C4302 and C4826 are off by default. You can disable specific warnings to reduce 'noise' as they are identified by#pragma warning
or/wd
. - Use
/analyze
. Static code analysis will highlight a number of issues, particularly using the incorrectprintf
format specifications--note that VS 2015 does checkprintf
format specifications even without using/analyze
, but it is not as extensive.
Inline assembly is not supported for x64 native or ARM compilation, so it should be avoided generally. You can make use of intrinsics instead. Avoid using MMX™ intrinsics (i.e. those from the mmintrin.h header or that operate with the __m64
type) to ensure the same code works for both x86 and x64 native. For ARM, there is a full set of intrinsics available in armintr.h
and arm_neon.h
.
The ability to write standalone assembly for all machine architectures is not supported for Windows Store apps for Windows 8.0 and requires the VS 2013 toolset with the Windows Driver Kit 8.1. Use of standalone assembly is not recommended for ‘dual-use’ code.
When writing architecture-specific code, make use of the _M_IX86
(32-bit), _M_X64
(64-bit), _M_ARM
, and _M_ARM64
machine architecture defines for conditional compilation. All are “Little Endian” platforms.
The VS 2012, VS 2013, and VS 2015 toolsets support x86, x64, and ARM. VS 2017 (15.9 update) or later also supports ARM64. VS 2010 has no support for ARM targets.
Exception-Safe Coding
Windows Store apps make use of C++ exception handling and are compiled with /EHsc
. Many Win32 desktop applications use HRESULT
s and do not enable exception handling of any kind, although some do use it. Dual-use shared code can use HRESULT
s or other error codes and leave the decision to use exception handling to the client code. (See DirectXTex for an example of this approach.) Alternatively, dual-use shared code can throw either C++ standard exceptions or Windows Store app Platform
exceptions through specific compiler techniques. (See DirectXTK for an example of this approach.)
Since dual-use code can be used in the context of exception handling, it is strongly recommended that you make use of ‘exception-safe’ coding practices. C++ exception handling takes advantage of the language and ensures that objects are properly destructed when leaving scope normally or when processing an exception. When using the C++11 Standard Library, those containers are already written to be ‘exception-safe’.
The main area where this impacts ‘dual-use’ shared code and C++ code in general is when allocating resources. The guidance here is to never rely on calling delete, delete [], CloseHandle, Release,
etc. directly but have the destructor of a class instance handle it automatically. This technique is known as Resource Acquisition Is Initialization (RAII). This ensures that the code will behave well both in normal operation and in the cases where exception handling is used. The C++11 Standard Library provides a number of classes that make implementing this pattern fairly straight-forward.
Traditional C++ | Exception-safe C++ |
MyObject \* obj = new MyObject; |
std::unique_ptr<MyObject> obj(new MyObject); -or- auto obj = std::make_unique<MyObject>(); Note: VS 2013 or later includes std::make_unique<T> , but it is not available in older toolsets.
-or- auto obj = std::make_shared<MyObject>(); |
BYTE \* buffer = new BYTE[ 2048 ]; |
std::array<uin8_t, 2048> buffer;
-or- std::unique_ptr<uin8_t[]> buffer( new uint8_t[2048]; ) |
float \* buffer = _aligned_malloc( 2048, 16 ); |
struct aligned_deleter { void operator()(void \* p) { _aligned_free(p); } }; std::unique_ptr<float, aligned_deleter> buffer( _aligned_malloc(2048,16)); |
HANDLE h = CreateFile(…); if ( h == INVALID_HANDLE) // error | struct handle_closer { void operator()(HANDLE h) {
assert(h != INVALID_HANDLE_VALUE); if (h) CloseHandle(h); } }; inline HANDLE safe_handle( HANDLE h ) { return (h==INVALID_HANDLE_VALUE) ? nullptr:h; } std::unique_ptr<void, handle_closer> hFile( safe_handle( CreateFile(…) ) ); if ( !hFile ) // error |
CRITICAL_SECTION cs; InitializeCriticalSection(&cs); EnterCriticalSection(&cs ... LeaveCriticalSection(&cs); |
std::mutex m; { std::lock_guard lock(m); // lock on m held until end of scope } |
ID3D11InputLayout \* inputLayout = NULL; device->CreateInputLayout( …, &inputLayout );
SAFE_RELEASE(inputLayout); |
#include <wrl/client.h> using Microsoft::WRL::ComPtr;
ComPtr<ID3D11InputLayout> inputLayout; device->CreateInputLayout(…, &inputLayout );
-or- device->CreateInputLayout(…, inputLayout.ReleaseAndGetAddressOf() ) |
When building with the Windows 8.x SDK for both Win32 desktop applications and Windows Store apps you can use Windows Runtime Library’s ComPtr
. This is similar to ATL’s CComPtr
. See this page for more information.
When passing these objects to other functions, you can use .get()
on the smart pointer on each call and pass the parameter as a raw pointer, or you can pass the smart pointer object directly. When using smart pointer objects as parameters, pass them by constant reference, similar to other STL containers, in order to avoid additional temporary copies and to avoid excessive reference count increment and decrement cycles.
The use of this ‘exception-safe’ pattern has the added benefit of ensuring you do not need to make use of explicit try / catch
blocks in your code to handle resource cleanup. This contributes to keeping ‘dual-use’ code agnostic to the use of Exception Handling while still being ‘exception-safe’ when it is used.
Visual C++: If you call new
, then by default it will throw a C++ exception for out-of-memory conditions per the C++ Standard. If you want to explicitly check for null instead, then you must use new (std::nothrow)
instead. See this blog post as well.
ThrowIfFailed: Modern Microsoft samples make use of a helper ThrowIfFailed
which generates a C++ exception if an HRESULT is a failure. This is intended as a ‘fatal fast fail’, and generally simplifies COM-based code handling. See this page for usage and implementation notes.
(continued in part 2)