.agents/skills/performance-fixer/references/bcl-patterns/interop-and-marshalling.md
The techniques specific to a P/Invoke binding — reducing the marshalling the managed wrapper adds
around a native call. The generated SkiaApi P/Invoke declarations in *.generated.cs are
off-limits to hand-edit; the lever you control is the managed wrapper. Prove byte-for-byte
parity for anything that reaches native (../measuring.md).
bool/char marshalling.bool/char marshalling without checking the native layout — C++
bool is 1 byte; default .NET bool marshals as 4.encoding.GetByteCount(ReadOnlySpan<char>) then encode into a
stackalloc/pooled Span<byte>.byte[] per
call (Util.GetEncodedText).byte[] path as old-TFM
fallback · ABI: internal (don't change the return type unless all callers are updated).GetEncodedText is not UTF-8-only — it switches on SKTextEncoding
(Utf8→UTF8, Utf16→Unicode, Utf32→UTF32) with an addNull variant. Prove parity for every
encoding AND both addNull cases — the NUL terminator is 1/2/4 bytes.static delegate for a native callback, or use delegate* unmanaged /
[UnmanagedCallersOnly] on modern TFMs (retain the delegate fallback for old ones).LibraryImport source-gen marshalling (net7)USE_LIBRARY_IMPORT). It reduces stub overhead and helps
AOT/trimming, but the declarations live in generated SkiaApi — never hand-edit them.
Your lever is keeping the managed wrapper signatures blittable.[SuppressGCTransition] (net5, advanced — usually not actionable here)