Games for Windows and the DirectX SDK blog

Technical tips, tricks, and news about game development for Microsoft platforms including desktop, Xbox, and UWP


Project maintained by walbourn Hosted on GitHub Pages — Theme by mattgraham
Home | Posts by Tag | Posts by Month

Dual-use Coding Techniques for Games, part 1

win10, win8, winphone, xbox

Originally 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 as sealed. Also note that VS 2010 considers override 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
  • find_if_not, copy_if, is_sorted,etc.
  • exception_ptr
ü ü ü ü
<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, and uintptr_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<>, and reinterpret_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 incorrect printf format specifications--note that VS 2015 does check printf 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 HRESULTs and do not enable exception handling of any kind, although some do use it. Dual-use shared code can use HRESULTs 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)