xllify’s native XLL runtime was originally built in C++. It has been replaced with one written in Zig. This post explains why.
zigxll is a Zig framework for building native Excel XLL add-ins. It wraps the C SDK so you never have to touch it directly. zigxll handles registration, type conversions, memory management and all the ceremony that the raw SDK demands. zig build produces a .xll file. Drop it into Excel and your functions are there.
xllify users write Lua with annotations:
--- Does something useful
-- @param input number The input value
-- @category Demo
-- @test(21) == 42
function MY_FUNCTION(input)
return input * 2
end
xllify compiles Lua to bytecode and embeds it into a zigxll-powered XLL. The annotations populate Excel’s Function Wizard automatically and the tests run at build time.
Why
I started building xllify and needed a solid foundation for the native XLL runtime. The existing options were C++ (painful), C# with Excel-DNA (great, but brings the CLR), or Python with PyXLL (similar trade-off). I wanted something that could produce small, self-contained binaries with no runtime dependencies, and that could cross-compile from macOS or Linux to Windows.
Wrapping the C SDK in C++ was the first attempt. It worked but was slow to build, hard to test, rested too heavily on CMake and bash, and every change felt like pulling teeth. I had been curious about Zig for a while and it turned out to be an excellent fit.
Why Zig
Zig can consume C headers directly. No bindings, no FFI layer, no code generation step. You just point it at the SDK headers and call the functions. This alone eliminated a huge amount of glue code.
Beyond that:
- Comptime lets you generate the boilerplate that the SDK demands (registration, type marshalling) at compile time from your function definitions. What would be macros or code generation in other languages is just normal Zig code that runs during compilation.
- Cross-compilation works out of the box. Develop on a Mac.
zig buildtargetsx86_64-windows-msvcand produces a working.xll. No Windows machine, no Visual Studio, no CI gymnastics required. The xwin tool provides the Windows SDK and CRT libraries. - Allocator model gives explicit control over memory. Arena allocators are used heavily, particularly in hot paths where per-call allocations would be wasteful.
- Small binaries, no runtime. The output is a compact DLL with zero dependencies beyond what Windows and Excel already provide.
- Testing without Windows. Unit tests for function logic run natively on whatever platform you’re developing on. You don’t need Excel or even Windows to validate your code.
How it works, briefly
The SDK communicates through XLOPER12, a tagged union type that represents every value Excel can pass to or receive from an add-in. zigxll provides type-safe conversions between Zig types and XLOPER12, handling the UTF-8 to UTF-16 conversion that Excel requires.
When Excel loads the XLL, zigxll registers your functions with the host using metadata derived at comptime from your function definitions. When Excel calls a function, zigxll unmarshals the arguments, calls your handler, and marshals the return value back.
For async operations, a thread pool executes work off the main Excel thread. Results are cached and Excel is notified to recalculate. There is also support for building COM RTD servers in pure Zig for pushing live data into cells.
Beyond demos: zigxll-nats
To prove zigxll could do something real, I built zigxll-nats, a NATS connector for Excel.
I have written about NATS before and had cause to play around with this kind of thing in Excel professionally. I know where it can break.
The original version used Office.js and nats.ws which is a bit quirky, but straightforward enough. In simple scenarios not a bad shout, but in sheets with thousands of moving prices, it does not work at all. The only way to really do this properly is natively.
zigxll-nats lets you subscribe to NATS subjects and stream data directly into Excel cells:
=NATS.SUB("prices.AAPL")
An RTD server manages subscriptions and pushes updates as messages arrive. The result is a ~370KB binary with no dependencies on .NET, VSTO, or any COM infrastructure beyond what zigxll itself provides. It auto-registers on load without needing admin privileges.
The interesting bit from an implementation perspective is how memory is managed on the hot path. Arena allocators handle UTF-16 conversions during refresh cycles, so there are no per-message allocations on the critical rendering path. The handoff between nats.c’s thread pool and Excel’s RTD polling mechanism is lock-free.
Problems solved
Building this taught me a few things worth sharing:
- The Excel C SDK is a hostile antique. Microsoft’s documentation indicates the investment required to understand XLL development “make[s] this a technology impractical for most users.” Behaviours differ between Excel versions, documentation is sparse. zigxll absorbs this pain so you don’t have to.
- COM in Zig is possible. Implementing a COM RTD server without C++ or .NET sounded mad. It is mad. But when you think about it, ATL and friends are a similar black box and, magic values aside, the Zig version is very readable. Zig’s comptime is powerful enough to generate vtables and IUnknown implementations from interface definitions.
- Cross-compilation changes the workflow. Being able to develop, test, and build on a Mac and then copy the
.xllto a Windows machine for integration testing is a huge quality of life improvement. CI is cheap too, as Linux runners handle the cross-compile. - Thread safety needs thought. Excel’s recalculation engine is multi-threaded. Getting the concurrency model right, particularly around async functions and RTD servers, required careful design. Zig’s explicit approach to memory and lack of hidden control flow made this easier to reason about than it would have been in C++.
What’s next
zigxll is the foundation of xllify’s native runtime. If you are building Excel add-ins and are comfortable with Zig, it might save you from a lot of pain.
For zigxll-nats, the roadmap includes authentication, TLS, configurable server addresses, JetStream integration, message transformation, and publish support. If you have data flowing through NATS and people who live in Excel, this could be a useful bridge between the two worlds.
Both projects are MIT licensed.
If you have any questions, get in touch or find us on X or LinkedIn.