24 KiB
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-existingSimpleDebugClient/SimpleIrisApperrors already documented inEngineCore.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,WaveParseragainst a hand-built RIFF/WAVE buffer,XmlLiteReaderwalking elements/ attributes/text/EOF,RichTextObjectcopy-paste/undo/redo/read-only/max-length,TextMetricswrapping,SchemaRegistrationreflecting over a probe type, andUIXVariantround-trips. Reached via a newInternalsVisibleTo, since the exports themselves are[UnmanagedCallersOnly]and uncallable from C#.
AOT correctness fixes made while publishing (these were real, not cosmetic):
Enum.GetValues(Type)inEnumSchemawas an IL3050 — it constructs aT[]of the enum type at runtime, which can genuinely fail under NativeAOT, and this project publishes AOT. Replaced withEnum.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; bindingd3dcompiler_47.dllwould 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.mdopen 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 areMicrosoft.Iris.Data.UIListContentsChangeType, confirmed fromDllProxyList.ListChanged's direct cast — not invented.SpXmlLiteRead/MoveToFirstAttribute/MoveToNextAttributemust return a failing HRESULT at end-of-stream, becauseNativeXmlReadergates its loops onSUCCEEDED(...)— returningS_FALSE(the natural XmlLite idiom) would have produced an infinite loop.SpXmlLiteCreateXmlReader'slengthis a byte count (content.Length * 2over a pinned string), not a character count.- Every
bool/out boolin the originalDllImports carries no[MarshalAs]override, so the CLR marshals it as a 4-byte Win32BOOL— these areinton the export side, notbyte. 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):
- 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.
TransportProtocol.VC(value 1) — still unidentified.- 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. - Whether the schema subsystem's CLR-type projection matches what real
.uixmarkup 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 opaqueIntPtrhandles are minted. Every export that hands back a "pointer" (schema objects, bitmaps, XML readers, rich-text objects, data queries) returns aGCHandle, never a real object address. Safe because nothing outsideUIXrenderis permitted to dereference these — all four managed consumers type them as opaqueIntPtr/HANDLE. Generalises the conventionSpRenderThreadInitalready used for its thread handle.UIXInterop/NativeString.csextended withInternUni/AllocUni. Non-obvious detail worth recording: exports likeSpQueryTypeName/SpQueryPropertyNamehand out achar*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,ToStringoutput) use the non-internedAllocUniinstead.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 asIntPtrand are called through their vtable. Slot numbering starts at 3 because all four areComInterfaceType.InterfaceIsIUnknown— verified from each interface's own attributes, not assumed.
Subsystems landed:
- Memory —
SpMemAlloc/SpMemFreereal viaNativeMemory. Correction made while writing:SpFreeDibinitially routed to GDI'sDeleteObject; 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. - Tracing —
SpUpdateTraceSettings/SpLogTracenow real (prefix/timestamp/category formatting, debugger + optional file sink), joining the existing init/uninit pair. - Engine transport remainder —
SpInvoke(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 andLocalChannelnever calls it),SpObjectRelease(realIUnknown::Releasethrough the object's own vtable), and the fourSpRemote*entry points. - Remote channel (
Subsystems/Remote/RemoteServerConnection.cs) — genuinely working out-of-process transport overTcpListener/NamedPipeServerStream/UdpClient, with a length-prefixedBufferInfo+ payload framing and a reader thread that dispatches into the sameEngineService/ContextRegistrythe local channel uses — so a remote context is indistinguishable from a local one to the rest of the engine.TransportProtocol.VCreturnsE_NOTIMPL: its meaning was never recovered (open question #2 inArchitecture.md) and CLAUDE.md forbids guessing. - 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 0–15 are reserved for the primitivesUIXVariantcarries directly. - 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. TheListChangedtype codes are not guessed:DllProxyList.ListChanged(UIX/Microsoft/Iris/CodeModel/Cpp/DllProxyList.cs) casts the argument straight toMicrosoft.Iris.Data.UIListContentsChangeType, so the numbering was read off that decompiled enum. - Assets —
SpBitmapLoadFile/LoadBufferreal cross-platform decode via StbImageSharp;SpBitmapLoadRawreal (pixel copy, no decoder involved);SpSoundLoadBufferreal (RIFF/WAVE chunk parser — no codec needed since the managed side's only declared format isWAVE_FORMAT_PCM). Non-obvious detail: stb decodes to RGBA byte order but Iris'sSurfaceFormat.ARGB32is0xAARRGGBBas a little-endian uint, i.e. BGRA in memory — the channel swap inBitmapStoreis required, not incidental.SpBitmapLoadResourceisE_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:
- Memory (
SpMemAlloc/SpMemFree/SpFreeDib) — real, viaNativeMemory. - Tracing (
SpUpdateTraceSettings/SpLogTrace, extending the existingSpInitializeTracing/SpUninitializeTracing) — real,Console/optional file sink. - Engine transport remainder (
SpPeekMessage,SpWaitMessage,SpInvoke, the 4SpRemote*remote-channel entry points,SpDx9CompileEffect,SpObjectRelease) — real where the existing managed-directEngineService/ContextRegistrydesign extends naturally (peek/wait/invoke against the per-context registry);SpRemote*implemented for real usingSystem.Net.Sockets/named-pipe-equivalent streams since .NET's own stream types are a genuine, working transport, not a stub. - Native reflection / type-schema system (~50
SpQuery*/SpGet*/SpInvoke*exports) — real, backed by a new managedSchemaRegistrythat projects realSystem.Reflection(Type/PropertyInfo/MethodInfo/ConstructorInfo/EventInfo) over registered CLR types throughGCHandle-wrapped opaqueIntPtrhandles (the same opaque-handle convention already established forRenderThread). 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. - UIXList (
SpUIXList*, ~15 exports) — real, aList<UIXVariant>-backed store. - Data binding / query (
SpData*, ~15 exports) — real, a managed registry mirroring the query lifecycle (construct/refresh/get-set property/notify). - XmlLite (
SpXmlLite*, ~11 exports) — real, backed bySystem.Xml.XmlReader(cross-platform, not Windows-only XmlLite COM, but drop-in behaviorally). - Asset loading (
SpBitmap*/SpSound*) — real where feasible: raw/buffer bitmap paths use direct pixel copy (no decode needed); file/resource decode paths useSystem.Drawing.Commonon 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). - Graphics (
SpGdiplusInit/Uninit,SpGetStateCache/SpSetStateCache,SpDx9CompileEffect) — GDI+ init/uninit are real (documented as legitimate no-ops: managed GDI+ has no manual startup step).SpDx9CompileEffectreal viaSilk.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. - 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 +// TODOelsewhere since these are inherently Win32-shaped concepts. - 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 viaSystem.Drawing.Graphics.MeasureStringon Windows (#if WINDOWS), a documented fixed-width-estimate fallback elsewhere (logged assumption, not silently guessed); rasterization (SpRichTextRasterize) real ARGB bitmap production on Windows viaSystem.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.