Files
MicrosoftIris/logs/UIXrender/FullSurface.md
T

24 KiB
Raw Blame History

UIXrender.dll — full public surface completion log

Append-only. Do not edit previous entries. Newest entries at the top (matching Architecture.md/EngineCore.md's actual observed order despite the header wording).


2026-07-22 — Complete: 193/193 exports implemented, verified against the built binary

Result: the export surface is complete and mechanically verified. All 193 Sp* entry points that the four managed consumers P/Invoke for are implemented, and the NativeAOT-published UIXrender.so exports exactly those 193 — no missing export (which would be a runtime EntryPointNotFoundException waiting to happen) and no stray one. Verified by diffing nm -D --defined-only UIXrender.so against the extern declarations in NativeApi.cs/ExtensionsApi.cs/FormApi.cs/Protocol/EngineApi.cs; the diff is empty. That check is now a CI step (new linux job) so it can't silently regress.

Verification actually performed (not just "it compiles"):

  • dotnet build MicrosoftIris.sln — clean apart from the 5 pre-existing SimpleDebugClient/SimpleIrisApp errors already documented in EngineCore.md (IDebuggerClient.InterpreterStep, DebugSettings.DebugConnectionUri — debugger APIs, untouched by this work).
  • dotnet publish -r linux-x64 -p:PublishAot=true — succeeds, zero trim/AOT warnings (see the AOT section below; they were fixed, not suppressed wholesale).
  • Tests/UIXrender.Engine.Tests — extended from 8 checks to 49, all passing, and they exercise real behaviour rather than construction: UIXList mutation/move/availability, WaveParser against a hand-built RIFF/WAVE buffer, XmlLiteReader walking elements/ attributes/text/EOF, RichTextObject copy-paste/undo/redo/read-only/max-length, TextMetrics wrapping, SchemaRegistration reflecting over a probe type, and UIXVariant round-trips. Reached via a new InternalsVisibleTo, since the exports themselves are [UnmanagedCallersOnly] and uncallable from C#.

AOT correctness fixes made while publishing (these were real, not cosmetic):

  • Enum.GetValues(Type) in EnumSchema was an IL3050 — it constructs a T[] of the enum type at runtime, which can genuinely fail under NativeAOT, and this project publishes AOT. Replaced with Enum.GetValuesAsUnderlyingType, which needs no dynamic array construction.
  • The reflection trim warnings (IL2026/IL2070/IL2075) were resolved by annotating the schema entry points with [DynamicallyAccessedMembers(All)] and, where the types genuinely come from a host-chosen assembly loaded at runtime (SpLoadDll), by [UnconditionalSuppressMessage] with a written justification — the trimmer cannot see into a runtime-chosen assembly by definition, so the host must root markup-visible assemblies itself. Suppressing with a reason keeps a future real warning from being lost in the noise.

What is real vs. what is honestly not — the part worth reading before trusting this:

Real, working logic: memory, tracing, engine transport (local + a genuinely functional TCP/named-pipe/UDP remote channel), the ~48-export reflection/schema system, UIXList, data-binding/query, XmlLite (System.Xml-backed), image decode (StbImageSharp, cross-platform), WAV/PCM parsing, module + embedded-resource loading, HTTP/file download, string & image handle marshaling, the IRawUIXServices callback bridge, IME registration/dispatch, registry-change notification (Windows), and the entire rich-text editing model (content, selection, clipboard, undo/redo, read-only, max length, wrap, scale, scrollbars, timers, key forwarding) with real IRichTextCallbacks notifications.

Not real, and deliberately failing loudly rather than faking success — each returns E_NOTIMPL (or 0/null) with a // TODO, so a caller gets an honest error instead of plausible-looking garbage:

  • SpDx9CompileEffect — no cross-platform effect-compilation abstraction exists; binding d3dcompiler_47.dll would violate the dependency policy (decision 1 below).
  • SpRichTextRasterize — needs a glyph rasterizer. Returning an empty bitmap would render as invisible text and read as a layout bug, so it fails instead.
  • SpBitmapLoadResource, SpLoadFontResource — Win32 resource sections / platform font registration, no equivalent yet.
  • SpCreateNotifyWindow, SpExtractDroppedFileNames — Win32 message-only window and shell drag-drop.
  • TransportProtocol.VC — meaning never recovered (Architecture.md open question #2).

Approximate, and flagged as the single biggest caveat: TextMetrics. Measurement is derived from font-height ratios typical of Latin UI faces, not from real font tables. Line height and baseline are close to exact; per-character advance is an average, so measured text width is approximate and will not match a real rasterizer. Word wrapping itself is real logic — only the width feeding it is estimated. Practical consequence: layout driven by these numbers will be plausible but not pixel-accurate. This is the top open question below; it should be replaced wholesale by a font backend, not built upon.

Documented assumption (per CLAUDE.md's uncertainty procedure): SpXmlLiteCreateXmlReader receives a byte buffer whose encoding is unstated — NativeXmlReader feeds it either a pinned UTF-16 string (no BOM) or a raw file buffer (typically UTF-8). Real XmlLite sniffs the encoding, so this does the same: BOM first, then the "every second byte is zero" pattern that identifies BOM-less UTF-16 ASCII, else UTF-8.

Things read off the decompiled sources rather than guessed (each would have been a silent, hard-to-find bug):

  • ListChanged's type codes are Microsoft.Iris.Data.UIListContentsChangeType, confirmed from DllProxyList.ListChanged's direct cast — not invented.
  • SpXmlLiteRead/MoveToFirstAttribute/MoveToNextAttribute must return a failing HRESULT at end-of-stream, because NativeXmlReader gates its loops on SUCCEEDED(...) — returning S_FALSE (the natural XmlLite idiom) would have produced an infinite loop.
  • SpXmlLiteCreateXmlReader's length is a byte count (content.Length * 2 over a pinned string), not a character count.
  • Every bool/out bool in the original DllImports carries no [MarshalAs] override, so the CLR marshals it as a 4-byte Win32 BOOL — these are int on the export side, not byte. Getting this wrong would have corrupted the stack on every such call.
  • The COM callback interfaces are all ComInterfaceType.InterfaceIsIUnknown, so their vtable slots start at 3; slot assignments follow each interface's declaration order, read from its own source file.

Open questions carried forward (not guessed at):

  1. Text measurement/rasterization needs a real font backend — the largest remaining gap by far. Needs a decision on taking a text-shaping dependency (SixLabors.Fonts, HarfBuzz) versus platform-native text APIs behind an abstraction. Deliberately not decided unilaterally in this pass.
  2. TransportProtocol.VC (value 1) — still unidentified.
  3. The ~26 "export-only" natives with no managed caller anywhere in this repo (SpCreateObject, SpFindClass, SpAttachWndProc, SpGetMessageA/W, ...) — not implemented, because there is no signature to mirror and CLAUDE.md forbids guessing. They're absent from the built library; if a consumer surfaces, they can be added then.
  4. Whether the schema subsystem's CLR-type projection matches what real .uix markup expects at runtime — it satisfies the shape of every accessor, but nothing in this repo exercises markup against it end-to-end yet.

2026-07-22 — Landed: shared infrastructure, memory, tracing, transport remainder, schema, assets, lists

Written and building clean (dotnet build UIXrender -f net8.0) as of this entry. Logging mid-flight per the user's reminder, not at the end.

Shared infrastructure (new, used by every subsystem below):

  • Engine/HandleTable.cs — the one place opaque IntPtr handles are minted. Every export that hands back a "pointer" (schema objects, bitmaps, XML readers, rich-text objects, data queries) returns a GCHandle, never a real object address. Safe because nothing outside UIXrender is permitted to dereference these — all four managed consumers type them as opaque IntPtr/HANDLE. Generalises the convention SpRenderThreadInit already used for its thread handle.
  • UIXInterop/NativeString.cs extended with InternUni/AllocUni. Non-obvious detail worth recording: exports like SpQueryTypeName/SpQueryPropertyName hand out a char* that the managed caller never frees, so the callee must own it for the process lifetime. Interning by value bounds that — schema names come from a fixed registered set, so the pool stops growing after each name's first request, instead of leaking one allocation per call. Per-call values (query results, ToString output) use the non-interned AllocUni instead.
  • Interop/Com/ComVtable.cs — callback interfaces (IUIXListCallbacks, IRawUIXServices, IRichTextCallbacks, IImeCallbacks) can't be received as [MarshalAs(Interface)] params by an [UnmanagedCallersOnly] method, so they arrive as IntPtr and are called through their vtable. Slot numbering starts at 3 because all four are ComInterfaceType.InterfaceIsIUnknown — verified from each interface's own attributes, not assumed.

Subsystems landed:

  1. MemorySpMemAlloc/SpMemFree real via NativeMemory. Correction made while writing: SpFreeDib initially routed to GDI's DeleteObject; removed. This implementation never creates DIB sections (bitmaps are our own unmanaged allocations), so calling GDI to free them would have been both wrong and a needless graphics-API dependency. Now frees through the same allocation path that produced it.
  2. TracingSpUpdateTraceSettings/SpLogTrace now real (prefix/timestamp/category formatting, debugger + optional file sink), joining the existing init/uninit pair.
  3. Engine transport remainderSpInvoke (real: sync direct call / async via thread pool, through the supplied function pointer), SpWaitMessage (real timeout wait), SpPeekMessage (documented "no message available" — there is no Win32 message queue behind this yet and LocalChannel never calls it), SpObjectRelease (real IUnknown::Release through the object's own vtable), and the four SpRemote* entry points.
  4. Remote channel (Subsystems/Remote/RemoteServerConnection.cs) — genuinely working out-of-process transport over TcpListener/NamedPipeServerStream/UdpClient, with a length-prefixed BufferInfo + payload framing and a reader thread that dispatches into the same EngineService/ContextRegistry the local channel uses — so a remote context is indistinguishable from a local one to the rest of the engine. TransportProtocol.VC returns E_NOTIMPL: its meaning was never recovered (open question #2 in Architecture.md) and CLAUDE.md forbids guessing.
  5. Native reflection / type-schema (~48 exports, the single largest group) — real, projecting CLR types through the original export surface via System.Reflection. Recorded as a deliberate substitution, not a stub: the C++ gadget classes the original reflected over don't exist here and never will, but every accessor keeps its original signature and semantics, so the managed caller can't tell the difference. Type IDs are process-wide and stable (the managed side round-trips them across unrelated calls); IDs 015 are reserved for the primitives UIXVariant carries directly.
  6. UIXList (17 exports) — real store, and it keeps the virtualization distinction real (per-slot availability, FetchSlowData, WantSlowDataRequests) rather than pretending every item is resident, because the managed ListBox's scrolling depends on it. The ListChanged type codes are not guessed: DllProxyList.ListChanged (UIX/Microsoft/Iris/CodeModel/Cpp/DllProxyList.cs) casts the argument straight to Microsoft.Iris.Data.UIListContentsChangeType, so the numbering was read off that decompiled enum.
  7. AssetsSpBitmapLoadFile/LoadBuffer real cross-platform decode via StbImageSharp; SpBitmapLoadRaw real (pixel copy, no decoder involved); SpSoundLoadBuffer real (RIFF/WAVE chunk parser — no codec needed since the managed side's only declared format is WAVE_FORMAT_PCM). Non-obvious detail: stb decodes to RGBA byte order but Iris's SurfaceFormat.ARGB32 is 0xAARRGGBB as a little-endian uint, i.e. BGRA in memory — the channel swap in BitmapStore is required, not incidental. SpBitmapLoadResource is E_NOTIMPL + TODO: Win32 resource sections have no cross-platform equivalent and this project has no PE resource reader yet.

Remaining for this session: data-binding (SpData*), XmlLite, GDI+ init pair, misc OS (DPI/download/DLL+resource loading/notify window/IME/dropped files/registry notify/HTTP), native services callbacks + string/image handle marshaling, and rich text/simple text.


2026-07-22 — Dependency policy: "Silk.NET abstractions, not specific graphics APIs"

Trigger: mid-implementation the user narrowed the earlier "prefer Silk.NET over stubs" direction to: use Silk.NET abstractions wherever possible, avoid specific graphics APIs. That is a materially different instruction from "bind Silk.NET's D3D packages", and it changes three decisions already in flight. Recording the reasoning before acting on it, since two of the three outcomes are "no dependency added", which would otherwise look like the instruction was ignored.

Verified first, not assumed: NuGet is reachable from this machine and Silk.NET.Core 2.23.0 restores cleanly (probe project in the scratchpad, not committed). So "couldn't get the package" is not the reason for any decision below — each is a deliberate fit judgement.

Decision 1 — SpDx9CompileEffect: no Silk.NET.Direct3D.Compilers, returns E_NOTIMPL with a // TODO. Silk.NET.Direct3D.Compilers is a thin binding over d3dcompiler_47.dll — i.e. exactly "a specific graphics API", Windows-only, and the narrowest possible thing to hard-code into a project whose stated goal is cross-platform revival. Silk.NET has no cross-platform shader/effect-compilation abstraction to use instead (checked: Silk.NET.Core/Maths/Windowing/Input are the abstraction layer, and none covers HLSL effect compilation; the D3D/OpenGL/Vulkan packages are all API-specific bindings). Since there's no abstraction available and the instruction rules out the specific API, the honest outcome is an explicit E_NOTIMPL + TODO rather than a Windows-only binding smuggled in behind #if WINDOWS. Logged as an open question below, not silently dropped.

Decision 2 — no Silk.NET.Windowing/Silk.NET.Input for SpCreateNotifyWindow, SpGetMouseCursorInfo, SpGetDpi. These look like the right fit (they're genuine cross-platform abstractions over exactly these concepts), and I nearly took them. They're wrong here for a structural reason worth writing down: Silk.NET.Windowing owns a window and its event loop, backed by a native GLFW/SDL binary loaded at runtime. UIXrender.dll is a library P/Invoked into an already-running host process that already owns its window and pump (Zune's own shell). A library that spins up a second windowing backend to answer "what's the system DPI" would be both heavier and less correct than asking the OS. So these stay platform-gated OS calls (#if WINDOWS + // TODO for other platforms), which is what CLAUDE.md's platform rule prescribes anyway. Note these are OS APIs (DPI, registry-change notification, IME), not graphics APIs — the user's constraint doesn't bite here.

Decision 3 — image decode uses StbImageSharp, not System.Drawing.Common. This one does add a dependency, and it's the case where the instruction changed the answer for the better. The earlier plan (entry below) had SpBitmapLoadFile/LoadBuffer/ LoadResource decoding via System.Drawing.Common under #if WINDOWS with cross-platform decode deferred — i.e. GDI+, a specific graphics API, Windows-only since .NET 6, and a stub everywhere else. StbImageSharp is pure managed (no native dependency), cross-platform, NativeAOT-safe (matters: this project is PublishAot/NativeLib=Shared), and is the decoder Silk.NET's own tutorials pair with its APIs. Net effect: PNG/JPEG/BMP/TGA decode becomes real logic on every platform instead of Windows-only-plus-a-stub. Strictly better against both the project's cross-platform goal and CLAUDE.md's "real logic preferred over stubs".

Unchanged by this: everything non-graphical (memory, tracing, transport, remote channel, reflection/schema, UIXList, data-binding, XmlLite, sound) was never going to touch a graphics API and keeps the real implementations described in the plan entry below.


2026-07-22 — Plan: complete the entire native export surface this session

Task, per explicit user direction: implement stages 1 & 2 for the entire remaining UIXrender.dll public surface (not just the EngineApi.cs transport methods worked on so far), aiming for a complete library by the end of this session, the same bar applied to ZuneDBApi. Real implementations are preferred over stubs (matching CLAUDE.md's stage 2 convention), with Silk.NET preferred over hand-rolled P/Invoke where a fitting binding package exists (mainly Direct3D9 compile-effect). Existing UIXsup/UIXrender code may be rewritten as needed as long as the public surface/signatures stay unchanged. Logging before/while writing, not after, per this repo's established convention.

Scope, counted from a full grep of every [DllImport("UIXRender.dll")] across the four managed consumer files (UIX/Microsoft/Iris/OS/NativeApi.cs, UIX.RenderApi/Microsoft/Iris/Render/Extensions/ExtensionsApi.cs, UIX.RenderApi/Microsoft/Iris/Render/Internal/FormApi.cs, UIX.RenderApi/Microsoft/Iris/Render/Protocol/EngineApi.cs): every export that has a concrete managed-side signature in this repo gets a matching [UnmanagedCallersOnly] counterpart in UIXrender, organized into UIXrender/Subsystems/<Group>/ folders mirroring Architecture.md's subsystem grouping. Exports flagged in Architecture.md as "export-only" (no managed caller found in this repo, e.g. SpAttachWndProc, SpCreateObject, SpGetMessageA/W) are not implemented here — there is no known signature to mirror, and CLAUDE.md forbids guessing; logged as still-open, not silently dropped.

Subsystem plan and treatment (real logic vs. documented stub), decided before writing any code:

  1. Memory (SpMemAlloc/SpMemFree/SpFreeDib) — real, via NativeMemory.
  2. Tracing (SpUpdateTraceSettings/SpLogTrace, extending the existing SpInitializeTracing/SpUninitializeTracing) — real, Console/optional file sink.
  3. Engine transport remainder (SpPeekMessage, SpWaitMessage, SpInvoke, the 4 SpRemote* remote-channel entry points, SpDx9CompileEffect, SpObjectRelease) — real where the existing managed-direct EngineService/ContextRegistry design extends naturally (peek/wait/invoke against the per-context registry); SpRemote* implemented for real using System.Net.Sockets/named-pipe-equivalent streams since .NET's own stream types are a genuine, working transport, not a stub.
  4. Native reflection / type-schema system (~50 SpQuery*/SpGet*/SpInvoke* exports) — real, backed by a new managed SchemaRegistry that projects real System.Reflection (Type/PropertyInfo/MethodInfo/ConstructorInfo/EventInfo) over registered CLR types through GCHandle-wrapped opaque IntPtr handles (the same opaque-handle convention already established for RenderThread). This is a genuine design substitution (real C++ types no longer exist to reflect over), not a stub — flagged here as a deliberate architecture decision, not an assumption to second-guess later.
  5. UIXList (SpUIXList*, ~15 exports) — real, a List<UIXVariant>-backed store.
  6. Data binding / query (SpData*, ~15 exports) — real, a managed registry mirroring the query lifecycle (construct/refresh/get-set property/notify).
  7. XmlLite (SpXmlLite*, ~11 exports) — real, backed by System.Xml.XmlReader (cross-platform, not Windows-only XmlLite COM, but drop-in behaviorally).
  8. Asset loading (SpBitmap*/SpSound*) — real where feasible: raw/buffer bitmap paths use direct pixel copy (no decode needed); file/resource decode paths use System.Drawing.Common on Windows (#if WINDOWS) since that's an in-box decoder, cross-platform decode deferred (stub + TODO, logged as an open question — no cross-platform image codec is referenced anywhere else in this repo yet). Sound buffer load is real (WAV header parse, no codec needed for PCM).
  9. Graphics (SpGdiplusInit/Uninit, SpGetStateCache/SpSetStateCache, SpDx9CompileEffect) — GDI+ init/uninit are real (documented as legitimate no-ops: managed GDI+ has no manual startup step). SpDx9CompileEffect real via Silk.NET.Direct3D.Compilers (D3DCompiler), Windows-only (#if WINDOWS), since D3D9 effect compilation is inherently a Windows/Direct3D concept — cross-platform has no equivalent, not guessed at.
  10. Misc OS/Win32 (SpGetDpi, SpGetMouseCursorInfo, SpExtractDroppedFileNames, SpRegNotifyChangeKey/Revoke, SpCreateNotifyWindow/Destroy, SpRegisterImeCallbacks/Unregister, SpPostDeferredImeMessage) — real on Windows (#if WINDOWS, direct Win32 P/Invoke, consistent with CLAUDE.md's platform-gating rule), stub + // TODO elsewhere since these are inherently Win32-shaped concepts.
  11. Rich text / Simple text (~30 exports) — real editing model (content buffer, cut/copy/paste/delete/undo/redo, selection, timer/scroll/wrap/readonly/scale state) backed by an actual StringBuilder + undo stack; measurement real via System.Drawing.Graphics.MeasureString on Windows (#if WINDOWS), a documented fixed-width-estimate fallback elsewhere (logged assumption, not silently guessed); rasterization (SpRichTextRasterize) real ARGB bitmap production on Windows via System.Drawing, stub + TODO cross-platform (no font-shaping library referenced elsewhere in this repo to build on without adding a large new, unrequested dependency — flagged as an open question rather than guessed).

Interface-typed callback parameters (IUIXListCallbacks, IRawUIXServices, IRichTextCallbacks, IImeCallbacks) can't be received as [MarshalAs(UnmanagedType. Interface)] COM parameters in an [UnmanagedCallersOnly] method (not blittable) — native callers pass a raw vtable pointer regardless of what the managed P/Invoke declares, so UIXrender's side receives void*/IntPtr and invokes through the vtable directly at each interface's declared method slots (offset by the standard IUnknown 3: QueryInterface/AddRef/Release), the same "reconstruct the interface, don't invent raw pointer types" approach CLAUDE.md's COM objects section describes.

Dependent-type mirrors needed (independent UIXrender/Interop/*.cs copies, since UIXrender cannot reference UIX/UIX.RenderApi — circular dependency, same reasoning as the existing ContextID/RENDERHANDLE/HRESULT/BufferInfo/InitArgs mirrors): UIXVariant, Color/Size/Rectangle/RectangleF/ColorF, RawImageFormat, SurfaceFormat, ImageRequirements/ImageInformation/ImageHeader/ImageData/ HSpBitmap, TextStyle.MarshalledData, TextMeasureParams.MarshalledData (+ FormattedRange), DataProviderQueryStatus, ShutdownReason, TransportProtocol, NativeXmlNodeType, Win32Api.HANDLE/MSG/LOGFONTW_STRUCT (the Microsoft.Iris.OS variant, since that's what NativeApi.cs itself resolves to for its own P/Invokes). Full field layouts confirmed via an Explore pass over the real decompiled sources before writing any of these — not guessed.

Not attempted, logged as genuinely out of scope rather than silently skipped: UIXsup.dll's own surface beyond what's already there (separate DLL, separate stage-1 target — logs/UIXsup.md presumably covers it, not duplicated here); the ~26 "export-only" natives with no managed call site in this repo (no signature to mirror).

Proceeding subsystem-by-subsystem in the order above; each subsystem's own progress gets logged as it lands, same as EngineCore.md's existing entries.