Quantcast
Channel: Games for Windows and the DirectX SDK
Viewing all articles
Browse latest Browse all 72

Dual-use Coding Techniques for Games, part 1

$
0
0

Writing shared code for Windows Store 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 (indicated by WINAPI_FAMILY set to WINAPI_PARTITION_APP). Traditional Win32 desktop apps have access to a larger desktop API family (indicated by WINAPI_FAMILY set to WINAPI_PARTITION_DESKTOP), 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.

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.

Note the majority of this article applies to Windows phone 8 using the Windows phone SDK 8.0. Windows phone 8 development makes use of a WINAPI_FAMILY of WINAPI_FAMILY_PHONE_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.

Note that with careful coding, it is possible to also support Visual Studio 2010 with the Windows 8.0 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 FeatureVS 2010VS 2012
nullptrüü
static_assertüü
override / final*üü
Lambda expressionsüü
Rvalue referencesüü
decltypeüü
autoüü
Strongly typed enumerations ü
Forward declared enumerations ü
Ranged-based for loops ü
Initializer listsûû
Variadic templatesûû

* = In VS 2010, final was implemented as sealed

Note: A future update to Visual C++ will include support for additional C+11 features including initializer lists, variadic templates, uniform initialization, function template default arguments, delegating constructors, explicit conversion operators and raw strings.

Use of the older standalone DirectX SDK is not recommended or supported for Windows Store apps. 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?” 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

<array>, <memory>, <random>, <regex>,
lt;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>ûû
<cuchar>, <cfenv>, <ctgmath>, <cstdalign>, <cstdbool>ûû

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++ headerNotes
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.

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.

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 currently supported for Windows Store apps, and is therefore not recommended for ‘dual-use’ or Windows Store app scenarios.

When writing architecture-specific code, make use of the _M_IX86 (32-bit), _M_X64 (64-bit), and _M_ARM machine architecture defines for conditional compilation. All three are “Little Endian” platforms (Windows RT included).

Note: The VS 2012 toolset fully supports x86, x64, and ARM. 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-

std::shared_ptr<MyObject> obj( 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) ? 0: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.h”

Microsoft::WRL::ComPtr<ID3D11InputLayout> inputLayout;

device->CreateInputLayout(…, &inputLayout );

-or-

device->CreateInputLayout(…, inputLayout.ReleaseAndGetAddressOf() )

Note: When building with VS 2012 or VS 2010 with the Windows 8.0 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.

Note: When passing these objects to other functions, you can pass raw pointers and use .get() on the memory control object on each call, or pass the smart pointer object. 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.

(continued in part 2)


Viewing all articles
Browse latest Browse all 72

Trending Articles