Chapter 48 Host Integration through CNA.Devices
CNA::Devices is an SDL3 host-integration layer with no XNA or Windows Phone precedent. It is also absent from a default CNA build. Both facts belong before its API tour because code that compiles in a devices-enabled demo can otherwise look like an unconditional framework surface.
48.1 The option removes the types
CNA_DEVICES defaults to OFF. It gates all 25 installed headers, 14 production sources, and ten test files in modules/devices-ext; with the default setting the archive contains object files with no defined API symbols and its tests register no cases. Names such as Camera, FileDialog, and SystemTray do not merely throw at runtime—they do not exist as C++ types.
The option does not gate Chapter 47’s Microsoft::Devices::Sensors module. The option help text’s broad “device/sensor” wording is therefore misleading: Accelerometer, Gyroscope, Compass, Motion, and VibrateController compile in every configuration, while this chapter’s namespace is entirely conditional.
Enable it explicitly at configure time:
Any public header or application source using these types must be compiled with the same definition propagated by CNA’s build targets; hand-defining the macro without linking the enabled archive only postpones failure to link time.
48.2 The SDL wrapper map
The implemented classes are deliberately thin:
- Clipboard
-
gets, sets, and probes SDL clipboard text;
- DisplayInfo
-
exposes a window’s display scale and safe interactive area;
- Locale
-
returns SDL’s preferred locale list;
- MessageBox
-
shows simple or multi-button native dialogs;
- FileDialog
-
opens file, save, and directory choosers asynchronously;
- SystemTray
-
owns a tray icon and flat callback-driven menu;
- PowerInfo
-
exposes host power state, percentage, and remaining seconds;
- SystemInfo
-
reports logical CPU cores and physical RAM;
- UrlLauncher
-
forwards a URL to the platform; and
- Camera
-
polls SDL camera frames into a Texture2D.
There are no TODO, NotImplemented, or throw-placeholder tokens in the module, but thin forwarding still has behavioral edges. MessageBox reports support unconditionally, never parents the dialog to a window, sets no default/cancel button, and does not validate an empty label list despite the header contract. FileDialog and SystemTray platform checks are advisory: show/construct still forwards. PowerInfo’s three properties call SDL_GetPowerInfo separately, so they can represent three different snapshots. SystemTray exposes no usable icon object, and UrlLauncher does no URL validation.
48.3 Camera: poll, do not wait for a callback
Camera chooses the first reported device, requests RGBA32, and closes/reports unsupported if SDL negotiates another pixel format. Permission state is re-read on every State query because a platform prompt can resolve long after construction. TryAcquireFrame requires a destination texture with exactly the negotiated dimensions; it returns false without modifying the texture if permission is not ready, dimensions do not match, or no fresh frame is available.
The production tree never calls SDL_InitSubSystem(SDL_INIT_CAMERA). Unless the host initializes that subsystem itself, available-camera enumeration is empty and Camera settles in NotSupported. This is a latent initialization gap, not a permission denial. The backend’s Lost and Closed states are also unreachable in its current implementation.
48.4 Platform support is per class
SystemTray is genuinely desktop-only in SDL3; Android, iOS, and Emscripten have no tray backend. FileDialog is not equivalent: SDL3 supplies an Android implementation. Display scale and safe area depend on a real associated window. Message boxes and URL launchers inherit whatever the host platform permits, even though CNA’s support predicates can be optimistic.
This makes a namespace-level “desktop utilities” label useful as orientation but wrong as a capability test. Query the particular class, expect callbacks to be platform-threaded, and retain a non-dialog fallback for sandboxed or web targets.
48.5 Overlap with CNA::Input
Clipboard and PowerInfo duplicate functionality already present as the unconditional CNA::Input::Clipboard and CNA::Input::Power. They were created on parallel workstreams and use type-incompatible enums despite wrapping the same SDL calls. With CNA_DEVICES=OFF, the Input versions are the only clipboard and host-battery API.
Input’s Sensors is not a duplicate of Chapter 47: it is a stateless, poll-oriented open/read/close API returning raw vectors, while Microsoft.Devices exposes stateful WP7 objects, events, throttling, and disposal. InputDevices has no counterpart here.
48.6 Lifetime and test boundary
File dialogs, tray menus, and camera permission all cross native asynchronous boundaries. Application-owned state captured by their callbacks must outlive the native operation, and UI callbacks should hand work back to the game thread before touching graphics state. Host shutdown must destroy these objects before global SDL teardown; the sensor/haptic side additionally uses the explicit shutdown coordinator described in Chapter 47.
There is a sharper callback-boundary gap in the present implementation. Both the file-dialog result trampoline and the tray-entry trampoline invoke caller-supplied C++ callbacks directly from an SDL C callback, with no try/catch. A handler that throws can therefore unwind across a C ABI boundary; callers must currently keep those handlers non-throwing. This is distinct from the sensor module’s native diagnostic policy above, which already catches and records exceptions at the corresponding boundary.
The ten devices-ext test sources exercise fake/native-wrapper contracts only when the gate is on; in a default build they silently contribute zero tests. A CI matrix that never configures CNA_DEVICES=ON proves neither the public types nor their platform forwarding. Camera, dialogs, tray integration, and permissions still require real-host validation beyond those unit tests.