// F08 — DPI / runtime rescale, Win32 implementation
// (../../../features/f08-dpi-scaling.md). Extends the scaffold
// (../scaffold/app.d) into a Per-Monitor-v2 DPI observatory:
//
// * SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
// before any HWND exists (druntime's core.sys.windows predates the
// Win10-1607 DPI API surface, so the contexts and the user32 entry points
// are declared below and resolved with GetProcAddress — which also logs
// which of them the host provides).
// * Logs every DPI source: GetThreadDpiAwarenessContext,
// GetWindowDpiAwarenessContext, GetDpiForWindow, GetDpiForSystem,
// GetSystemDpiForProcess, plus the GetDeviceCaps(LOGPIXELSX) legacy value.
// * Immutability proofs: a second SetProcessDpiAwarenessContext call (the
// process awareness is write-once), and a WSI_LATE_AWARENESS=1 mode that
// creates the window FIRST and only then calls the API — capturing the
// documented ERROR_ACCESS_DENIED ordering rule. The late mode then probes
// SetThreadDpiAwarenessContext sub-process granularity: the existing
// window keeps its creation-time awareness, a second window created on
// the PMv2 thread gets the new one.
// * WM_DPICHANGED: logs old/new DPI and the suggested rect, honors it with
// SetWindowPos, reallocates the DIB backbuffer on the resulting WM_SIZE,
// and logs logical (96-DPI units) vs physical client size on every
// resize. The frame renders 1-physical-px hairlines (border + center
// crosshair) over the scaffold gradient so scaling artifacts are visible.
//
// WSI_AUTO_EXIT=1 bounds the run (default ~1.2 s; WSI_RUN_MS overrides — the
// findings doc uses a longer run while changing the compositor output scale
// under winewayland to hunt a live WM_DPICHANGED). Exit 0 in all modes.
//
// Only druntime's built-in core.sys.windows bindings — no third-party packages.
module (module) appapp;
import (package) corecore.(package) core.syssys.(package) core.sys.windowswindows.(module) core.sys.windows.windowsWindows API header module
Translated from MinGW API for MS-Windows 4.0
Source
core/sys/windows/windows.d
windows;
import instrument;
// ---------------------------------------------------------------------------
// The Win10 DPI-awareness surface (winuser.h, 1607+/1803+) — absent from
// druntime's core.sys.windows. DPI_AWARENESS_CONTEXT values are pseudo
// handles; the functions are user32 exports resolved at runtime so a missing
// export is a logged finding, not a loader failure.
alias (alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT = HANDLE;
(alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT app.dpiCtxdpiCtx(int (parameter) int nn) nothrow @nogc
{
return cast((unresolved type) DPI_AWARENESS_CONTEXTDPI_AWARENESS_CONTEXT) cast(ptrdiff_t) n;
}
enum (constant) int app.CTX_UNAWARE = -1CTX_UNAWARE = -1;
enum (constant) int app.CTX_SYSTEM_AWARE = -2CTX_SYSTEM_AWARE = -2;
enum (constant) int app.CTX_PER_MONITOR_AWARE = -3CTX_PER_MONITOR_AWARE = -3;
enum (constant) int app.CTX_PER_MONITOR_AWARE_V2 = -4CTX_PER_MONITOR_AWARE_V2 = -4;
enum (constant) int app.CTX_UNAWARE_GDISCALED = -5CTX_UNAWARE_GDISCALED = -5;
enum (constant) int app.WM_DPICHANGED = 736WM_DPICHANGED = 0x02E0;
enum (constant) int app.WM_GETDPISCALEDSIZE = 740WM_GETDPISCALEDSIZE = 0x02E4;
struct (struct) app.ApiApi
{
extern (Windows) nothrow @nogc:
BOOL function((alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT) (field) _error_ app.Api.SetProcessDpiAwarenessContextSetProcessDpiAwarenessContext;
(alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT function() (field) _error_ app.Api.GetThreadDpiAwarenessContextGetThreadDpiAwarenessContext;
(alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT function((alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT) (field) _error_ app.Api.SetThreadDpiAwarenessContextSetThreadDpiAwarenessContext;
(alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT function(HWND) (field) _error_ app.Api.GetWindowDpiAwarenessContextGetWindowDpiAwarenessContext;
BOOL function((alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT, (alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT) (field) _error_ app.Api.AreDpiAwarenessContextsEqualAreDpiAwarenessContextsEqual;
UINT function(HWND) (field) _error_ app.Api.GetDpiForWindowGetDpiForWindow;
UINT function() (field) _error_ app.Api.GetDpiForSystemGetDpiForSystem;
UINT function(HANDLE) (field) _error_ app.Api.GetSystemDpiForProcessGetSystemDpiForProcess;
}
__gshared (struct) app.ApiApi _error_ app.apiapi;
void void app.loadDpiApi() nothrowloadDpiApi() nothrow
{
HMODULE (local variable) _error_ u32u32 = GetModuleHandleW("user32"w.ptr);
static foreach (name; __traits(allMembers, Api))
{
__traits(getMember, api, name) = cast(typeof(__traits(getMember, api, name)))
GetProcAddress(u32, name.ptr);
logEvent("api name=%s present=%d", name.ptr,
__traits(getMember, api, name) !is null ? 1 : 0);
}
}
// Render an awareness context as a stable name for the log (the raw value is
// an opaque handle; equality must go through AreDpiAwarenessContextsEqual).
const(char)* app.ctxNamectxName((alias) app.DPI_AWARENESS_CONTEXT = _error_DPI_AWARENESS_CONTEXT (parameter) DPI_AWARENESS_CONTEXT ctxctx) nothrow
{
if (api.api.AreDpiAwarenessContextsEqualAreDpiAwarenessContextsEqual is null)
return "unknown";
if (api.api.AreDpiAwarenessContextsEqualAreDpiAwarenessContextsEqual(ctx, dpiCtx(CTX_PER_MONITOR_AWARE_V2)))
return "per_monitor_aware_v2";
if (api.api.AreDpiAwarenessContextsEqualAreDpiAwarenessContextsEqual(ctx, dpiCtx(CTX_PER_MONITOR_AWARE)))
return "per_monitor_aware";
if (api.api.AreDpiAwarenessContextsEqualAreDpiAwarenessContextsEqual(ctx, dpiCtx(CTX_SYSTEM_AWARE)))
return "system_aware";
if (api.api.AreDpiAwarenessContextsEqualAreDpiAwarenessContextsEqual(ctx, dpiCtx(CTX_UNAWARE_GDISCALED)))
return "unaware_gdiscaled";
if (api.api.AreDpiAwarenessContextsEqualAreDpiAwarenessContextsEqual(ctx, dpiCtx(CTX_UNAWARE)))
return "unaware";
return "unrecognized";
}
// One line with every DPI source the platform exposes, tagged by call site.
void app.logDpiSourceslogDpiSources(const(char)* (parameter) const(char)* whenwhen, HWND (parameter) HWND hwndhwnd) nothrow
{
const _error_ winDpiwinDpi = hwnd && api.api.GetDpiForWindowGetDpiForWindow ? api.api.GetDpiForWindowGetDpiForWindow(hwnd) : 0;
const _error_ sysDpisysDpi = api.api.GetDpiForSystemGetDpiForSystem ? api.api.GetDpiForSystemGetDpiForSystem() : 0;
const _error_ procDpiprocDpi = api.api.GetSystemDpiForProcessGetSystemDpiForProcess
? api.api.GetSystemDpiForProcessGetSystemDpiForProcess(GetCurrentProcess()) : 0;
HDC _error_ screenscreen = GetDC(null);
const _error_ capscaps = GetDeviceCaps(screen, LOGPIXELSX);
ReleaseDC(null, screen);
logEvent("dpi_sources when=%s window=%u system=%u process=%u devcaps=%d thread_ctx=%s",
when, winDpi, sysDpi, procDpi, caps,
api.api.GetThreadDpiAwarenessContextGetThreadDpiAwarenessContext
? ctxName(api.api.GetThreadDpiAwarenessContextGetThreadDpiAwarenessContext()) : "unknown".ptr);
}
// ---------------------------------------------------------------------------
// Demo state: scaffold DIB backbuffer + DPI bookkeeping.
enum UINT_PTR (constant) _error_ app.TIMER_ID = __errorTIMER_ID = 1;
enum (constant) int app.TICK_MS = 16TICK_MS = 16;
struct (struct) app.DemoDemo
{
HWND (field) _error_ app.Demo.hwndMainhwndMain; // the second (granularity-probe) window must not quit us
HDC (field) _error_ app.Demo.memDcmemDc;
HBITMAP (field) _error_ app.Demo.dibdib, (field) _error_ app.Demo.stockBmpstockBmp;
uint* (field) uint* app.Demo.pixelspixels;
int (field) int app.Demo.widthwidth, (field) int app.Demo.heightheight; // physical client pixels (= DIB size)
uint (field) uint app.Demo.dpidpi = 96; // current per-window DPI; scale = dpi / 96
uint (field) uint app.Demo.frameframe, (field) uint app.Demo.ticksticks;
uint (field) uint app.Demo.runMsrunMs = 1200; // auto-exit deadline (WSI_RUN_MS overrides)
uint (field) uint app.Demo.nDpiChangednDpiChanged;
bool (field) bool app.Demo.autoExitautoExit, (field) bool app.Demo.latelate;
bool (field) bool app.Demo.firstPaintDonefirstPaintDone;
}
__gshared (struct) app.DemoDemo _error_ app.gg;
void void app.createBackbuffer(int w, int h) nothrowcreateBackbuffer(int (parameter) int ww, int (parameter) int hh) nothrow
{
if (g.(field) _error_ g.dibdib !is null)
{
SelectObject(g.g.memDcmemDc, g.g.stockBmpstockBmp);
DeleteObject(g.g.dibdib);
g.g.dibdib = null;
g.g.pixelspixels = null;
g.g.widthwidth = g.g.heightheight = 0;
}
if ((parameter) int ww <= 0 || (parameter) int hh <= 0)
return;
BITMAPINFO (local variable) _error_ bmibmi;
bmi.bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
bmi.bmiHeader.biWidth = w;
bmi.bmiHeader.biHeight = -h; // top-down
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* (local variable) void* bitsbits;
g.g.dibdib = CreateDIBSection(null, &bmi, DIB_RGB_COLORS, &bits, null, 0);
if (g.(field) _error_ g.dibdib is null)
{
logEvent("error what=CreateDIBSection code=%lu", GetLastError());
return;
}
g.g.pixelspixels = cast(uint*) bits;
g.g.widthwidth = w;
g.g.heightheight = h;
g.g.stockBmpstockBmp = cast(HBITMAP) SelectObject(g.g.memDcmemDc, g.g.dibdib);
logEvent("buffer_alloc size=%dx%d bytes=%d", w, h, w * h * 4);
}
// Scaffold gradient + 1-physical-px hairlines: a white border rectangle and a
// center crosshair. At the correct buffer size they are razor-sharp; any
// bitmap-stretch (the DPI-virtualization failure mode) blurs them visibly.
void void app.drawFrame() nothrowdrawFrame() nothrow
{
if (g.(field) _error_ g.pixelspixels is null)
return;
const (local variable) const(_error_) ww = g.(field) _error_ g.widthwidth, (local variable) const(_error_) hh = g.(field) _error_ g.heightheight;
const (local variable) const(_error_) blueblue = (g.(field) _error_ g.frameframe * 4) & 0xff;
foreach ((parameter) yy; 0 .. h)
{
uint* _error_ rowrow = g.g.pixelspixels + cast(size_t) y * w;
const _error_ greengreen = h > 1 ? (y * 255) / (h - 1) : 0;
foreach ((parameter) xx; 0 .. w)
{
const _error_ redred = w > 1 ? (x * 255) / (w - 1) : 0;
row[x] = cast(uint)((red << 16) | (green << 8) | blue);
}
}
foreach ((parameter) xx; 0 .. w) // 1-px horizontal hairlines: border + center
{
g.g.pixelspixels[x] = 0xffffff;
g.g.pixelspixels[cast(size_t)(h - 1) * w + x] = 0xffffff;
g.g.pixelspixels[cast(size_t)(h / 2) * w + x] = 0xffffff;
}
foreach ((parameter) yy; 0 .. h) // and vertical
{
g.g.pixelspixels[cast(size_t) y * w] = 0xffffff;
g.g.pixelspixels[cast(size_t) y * w + (w - 1)] = 0xffffff;
g.g.pixelspixels[cast(size_t) y * w + (w / 2)] = 0xffffff;
}
}
// ---------------------------------------------------------------------------
extern (Windows) LRESULT app.wndProcwndProc(HWND (parameter) HWND hwndhwnd, UINT (parameter) UINT msgmsg, WPARAM wParam, LPARAM lParam) nothrow
{
switch (msg)
{
case WM_CREATE:
if (g.g.memDcmemDc is null) // the probe window shares nothing
g.g.memDcmemDc = CreateCompatibleDC(null);
return 0;
case WM_DPICHANGED:
// wParam: new X DPI (low word) / Y DPI (high word); lParam: the
// OS-suggested window rect at the new scale. Honoring it verbatim is
// the documented contract — it keeps the window under the cursor and
// at an equivalent logical size on the new monitor.
++g.g.nDpiChangednDpiChanged;
const _error_ newDpinewDpi = cast(uint)(wParam & 0xffff);
const RECT* _error_ rr = cast(RECT*) lParam;
logEvent("dpi_changed old=%u new=%u suggested=%dx%d+%d+%d",
g.g.dpidpi, newDpi, r.right - r.left, r.bottom - r.top, r.left, r.top);
g.g.dpidpi = newDpi;
SetWindowPos(hwnd, null, r.left, r.top,
r.right - r.left, r.bottom - r.top, SWP_NOZORDER | SWP_NOACTIVATE);
InvalidateRect(hwnd, null, FALSE);
return 0;
case WM_GETDPISCALEDSIZE: // PMv2-only negotiation hook before DPICHANGED
logEvent("msg name=WM_GETDPISCALEDSIZE dpi=%u", cast(uint) wParam);
goto default; // FALSE from DefWindowProc = default linear scaling
case WM_SIZE:
const _error_ ww = cast(int)(lParam & 0xffff);
const _error_ hh = cast(int)((lParam >> 16) & 0xffff);
// Under per-monitor awareness the client rect IS physical pixels;
// the logical (DIP) size is derived: logical = physical * 96 / dpi.
logEvent("resize size=%dx%d scale=%u.%02u logical=%dx%d",
w, h, g.g.dpidpi / 96, (g.g.dpidpi % 96) * 100 / 96,
w * 96 / g.g.dpidpi, h * 96 / g.g.dpidpi);
if (wParam == SIZE_MINIMIZED)
return 0;
if (w != g.g.widthwidth || h != g.g.heightheight)
createBackbuffer(w, h);
return 0;
case WM_PAINT:
PAINTSTRUCT _error_ psps;
HDC _error_ hdchdc = BeginPaint(hwnd, &ps);
++g.g.frameframe;
drawFrame();
if (g.g.pixelspixels !is null)
BitBlt(hdc, 0, 0, g.g.widthwidth, g.g.heightheight, g.g.memDcmemDc, 0, 0, SRCCOPY);
if (!g.g.firstPaintDonefirstPaintDone)
{
g.g.firstPaintDonefirstPaintDone = true;
logEvent("first_pixel_presented size=%dx%d dpi=%u", g.g.widthwidth, g.g.heightheight, g.g.dpidpi);
}
EndPaint(hwnd, &ps);
return 0;
case WM_TIMER:
if (wParam != TIMER_ID)
return 0;
++g.g.ticksticks;
InvalidateRect(hwnd, null, FALSE);
if (g.g.autoExitautoExit && g.g.ticksticks * TICK_MS >= g.g.runMsrunMs)
DestroyWindow(hwnd);
return 0;
case WM_CLOSE:
logEvent("close_requested");
goto default;
case WM_DESTROY:
if (g.g.hwndMainhwndMain !is null && hwnd !is g.g.hwndMainhwndMain)
return 0; // the probe window: no teardown, no quit
KillTimer(hwnd, TIMER_ID);
createBackbuffer(0, 0);
if (g.g.memDcmemDc !is null)
{
DeleteDC(g.g.memDcmemDc);
g.g.memDcmemDc = null;
}
logEvent("summary dpichanged=%u final_dpi=%u", g.g.nDpiChangednDpiChanged, g.g.dpidpi);
PostQuitMessage(0);
return 0;
default:
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
}
// ---------------------------------------------------------------------------
bool bool app.envFlag(const(wchar)* name) nothrowenvFlag(const(wchar)* (parameter) const(wchar)* namename) nothrow
{
WCHAR[16] (local variable) _error_ bufbuf;
const (local variable) const(_error_) nn = GetEnvironmentVariableW(name, buf.ptr, buf.length);
return n >= 1 && n < buf.length && buf[0] == '1';
}
uint uint app.envUint(const(wchar)* name, uint def) nothrowenvUint(const(wchar)* (parameter) const(wchar)* namename, uint (parameter) uint defdef) nothrow
{
WCHAR[16] (local variable) _error_ bufbuf;
const (local variable) const(_error_) nn = GetEnvironmentVariableW(name, buf.ptr, buf.length);
if (n < 1 || n >= buf.length)
return (parameter) uint defdef;
uint (local variable) uint vv;
foreach ((parameter) ii; 0 .. n)
{
if (buf[i] < '0' || buf[i] > '9')
return def;
v = v * 10 + (buf[i] - '0');
}
return (local variable) uint vv;
}
void void app.setAwareness(const(char)* when, int ctx) nothrowsetAwareness(const(char)* (parameter) const(char)* whenwhen, int (parameter) int ctxctx) nothrow
{
if (api.(field) _error_ api.SetProcessDpiAwarenessContextSetProcessDpiAwarenessContext is null)
{
logEvent("awareness_set when=%s skipped=api_missing", when);
return;
}
SetLastError(0);
const (local variable) const(_error_) okok = api.SetProcessDpiAwarenessContext(dpiCtx(ctx));
logEvent("awareness_set when=%s ctx=%s ok=%d err=%lu",
when, ctxName(dpiCtx(ctx)), ok ? 1 : 0, ok ? 0 : GetLastError());
}
HWND app.createDemoWindowcreateDemoWindow(HINSTANCE (parameter) HINSTANCE hInsthInst, const(wchar)* cls, const(wchar)* title) nothrow
{
return CreateWindowExW(0, cls, title, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 480, 320, null, null, hInst, null);
}
int int D main()main()
{
instrumentInit("f08_dpi_win32");
logEvent("init_start");
g.g.autoExitautoExit = envFlag("WSI_AUTO_EXIT"w.ptr);
g.g.latelate = envFlag("WSI_LATE_AWARENESS"w.ptr);
g.g.runMsrunMs = envUint("WSI_RUN_MS"w.ptr, 1200);
logEvent("mode auto_exit=%d late_awareness=%d run_ms=%u",
g.g.autoExitautoExit ? 1 : 0, g.g.latelate ? 1 : 0, g.g.runMsrunMs);
void app.loadDpiApi() nothrowloadDpiApi();
logDpiSources("before_awareness", null);
if (!g.(field) _error_ g.latelate)
{
// The production ordering: declare PMv2 before ANY window exists.
void app.setAwareness(const(char)* when, int ctx) nothrowsetAwareness("before_window", (constant) int app.CTX_PER_MONITOR_AWARE_V2 = -4CTX_PER_MONITOR_AWARE_V2);
// Write-once proof: a second call must fail (ERROR_ACCESS_DENIED).
void app.setAwareness(const(char)* when, int ctx) nothrowsetAwareness("second_call", (constant) int app.CTX_SYSTEM_AWARE = -2CTX_SYSTEM_AWARE);
logDpiSources("after_awareness", null);
}
HINSTANCE (local variable) _error_ hInsthInst = GetModuleHandleW(null);
auto (local variable) wstring clsNameclsName = "wsi-f08-class"w;
WNDCLASSEXW (local variable) _error_ wcwc;
wc.cbSize = WNDCLASSEXW.sizeof;
wc.lpfnWndProc = &wndProc;
wc.hInstance = hInst;
wc.lpszClassName = clsName.ptr;
wc.hCursor = LoadCursorW(null, IDC_ARROW);
if (!RegisterClassExW(&wc))
{
logEvent("error what=RegisterClassExW code=%lu", GetLastError());
return 1;
}
HWND (local variable) _error_ hwndhwnd = createDemoWindow(hInst, clsName.ptr, "wsi-f08-dpi-scaling"w.ptr);
if (hwnd is null)
{
logEvent("error what=CreateWindowExW code=%lu", GetLastError());
return 1;
}
logEvent("window_created");
g.g.hwndMainhwndMain = hwnd;
if (api.(field) _error_ api.GetDpiForWindowGetDpiForWindow !is null)
g.g.dpidpi = api.api.GetDpiForWindowGetDpiForWindow(hwnd);
if (api.(field) _error_ api.GetWindowDpiAwarenessContextGetWindowDpiAwarenessContext !is null)
logEvent("window_ctx hwnd=main ctx=%s",
ctxName(api.api.GetWindowDpiAwarenessContextGetWindowDpiAwarenessContext(hwnd)));
logDpiSources("after_window", hwnd);
if (g.(field) _error_ g.latelate)
{
// The ordering rule: setting process awareness AFTER a window exists
// is documented to fail (the default awareness is locked in by then).
void app.setAwareness(const(char)* when, int ctx) nothrowsetAwareness("after_window", (constant) int app.CTX_PER_MONITOR_AWARE_V2 = -4CTX_PER_MONITOR_AWARE_V2);
logDpiSources("after_late_set", hwnd);
// Sub-process granularity: thread-scoped awareness still works…
if (api.(field) _error_ api.SetThreadDpiAwarenessContextSetThreadDpiAwarenessContext !is null)
{
auto (local variable) _error_ prevprev = api.SetThreadDpiAwarenessContext(dpiCtx(CTX_PER_MONITOR_AWARE_V2));
logEvent("thread_awareness_set ctx=per_monitor_aware_v2 prev=%s ok=%d",
ctxName(prev), prev !is null ? 1 : 0);
// …the existing window keeps its creation-time awareness, while a
// window created NOW inherits the thread's new context.
if (api.(field) _error_ api.GetWindowDpiAwarenessContextGetWindowDpiAwarenessContext !is null)
logEvent("window_ctx hwnd=main ctx=%s note=unchanged_after_thread_set",
ctxName(api.api.GetWindowDpiAwarenessContextGetWindowDpiAwarenessContext(hwnd)));
HWND (local variable) _error_ hwnd2hwnd2 = createDemoWindow(hInst, clsName.ptr, "wsi-f08-second"w.ptr);
if (hwnd2 !is null && api.(field) _error_ api.GetWindowDpiAwarenessContextGetWindowDpiAwarenessContext !is null)
{
logEvent("window_ctx hwnd=second ctx=%s dpi=%u",
ctxName(api.api.GetWindowDpiAwarenessContextGetWindowDpiAwarenessContext(hwnd2)),
api.api.GetDpiForWindowGetDpiForWindow ? api.api.GetDpiForWindowGetDpiForWindow(hwnd2) : 0);
DestroyWindow(hwnd2);
}
api.SetThreadDpiAwarenessContext(prev);
}
}
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
SetTimer(hwnd, TIMER_ID, TICK_MS, null);
MSG (local variable) _error_ msgmsg;
while (GetMessageW(&msg, null, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
logEvent("exit code=%d", cast(int) msg.wParam);
return cast(int) msg.wParam;
}