[HN Gopher] COM Like a Bomb: Rust Outlook Add-in
       ___________________________________________________________________
        
       COM Like a Bomb: Rust Outlook Add-in
        
       Author : piker
       Score  : 75 points
       Date   : 2025-12-10 15:10 UTC (7 hours ago)
        
 (HTM) web link (tritium.legal)
 (TXT) w3m dump (tritium.legal)
        
       | meibo wrote:
       | I will say that I'm surprised no other LLM picked this up, since
       | the issue should be somewhat evident to people familiar with C++
       | and how COM works. COM APIs cannot represent "owned" strings.
       | 
       | Still better than whatever JS rats nest they came up with for the
       | new Outlook.
        
         | snuxoll wrote:
         | What do you mean by "owned" strings?
         | 
         | WinRT, which is ultimately just an evolution of COM, has
         | HSTRING which _can_ own the data inside it (as well as contain
         | a reference to an existing chunk of memory with fast-pass
         | strings).
        
       | CrimsonCape wrote:
       | > But using C# required us to contemplate whether and which
       | dotnet runtime our client supported. Or did we need to ship our
       | own? Isn't this just a small launcher stub? This was just too
       | much complexity outside of our wheelhouse to put between our
       | product and the user. This is not to say that the C# approach
       | isn't valid. It is just that our limited understanding of that
       | ecosystem and its requirements counseled against shipping it as a
       | primary entry point into our application.
       | 
       | You should be able to compile a relatively small, trimmed,
       | standalone, AOT compiled library that uses native interop.
       | (Correct me if i'm wrong, dotnet users). Then there would be no
       | dependency on the framework.
        
         | pjmlp wrote:
         | Yes, provided you are using modern COM bindings introduced in
         | .NET Core, alongside code generators.
        
         | sedatk wrote:
         | Or you could target .NET Framework 4.8 which is supported by
         | all Windows OSes out of the box albeit quite outdated.
        
           | Kwpolska wrote:
           | Their add-in seems quite simple, I imagine there would be no
           | meaningful difference between using the classic .NET
           | Framework 4.8 and .NET 10.
        
         | merb wrote:
         | You can only use .net 4.8 when you create an outlook add-in.
         | 
         | I mean yes you can build it with native interop and aot. But
         | then you would loose the .net benefits as well.
        
       | jlarocco wrote:
       | I don't like Windows, but I've always thought COM was pretty
       | cool. It's a nightmare using it directly from low level languages
       | like C++ and Rust, though. It's a perfect place to use code
       | generation or metaprogramming.
       | 
       | In Python, Ruby and the Microsoft languages COM objects integrate
       | seamlessly into the language as instances of the built-in class
       | types.
       | 
       | Also, there's a fairly straightfoward conversion from C# to C++
       | signatures, which becomes apparent after you see a few of them.
       | It might be explicitly spelled out in the docs somewhere.
        
         | pjmlp wrote:
         | Not if using Delphi or C++ Builder.
         | 
         | For whatever reason all attempts to make COM easier to use in
         | Visual C++, keep being sabotaged by internal teams.
         | 
         | It is like Windows team feels like it is a manhood test to use
         | such low level tooling.
        
         | ok123456 wrote:
         | Using COM in Perl was pretty seamless back in its heyday.
        
         | asveikau wrote:
         | COM is basically just reference counting and interfaces. Also,
         | the HRESULT type tries to give some structure to 32 bit error
         | codes.
         | 
         | I remember a few years back hearing hate about COM and I didn't
         | feel like they understood what it was.
         | 
         | I think the legit criticisms include:
         | 
         | * It relies heavily on function pointers (virtual calls) so
         | this has performance costs. Also constantly checking those
         | HRESULTs for errors, I guess, gives you a lot more branching
         | than exceptions.
         | 
         | * The idea of registration, polluting the Windows registry.
         | These days this part is pretty optional.
        
           | snuxoll wrote:
           | As somebody who's been, for whatever reason, toying around
           | with writing a COM-style ABI layer in Rust, there's a lot of
           | good ideas in there and I think a lot of the hatred comes
           | from the DLL hell that was spawned by _registration_ ; along
           | with the, unfortunately necessary, boilerplate.
           | 
           | Virtual dispatch absolutely has an overhead, but absolutely
           | nobody in their right mind should be using COM interfaces in
           | a critical section of code. When we're talking things like UI
           | elements, HTTP clients, whatever, the overhead of an indirect
           | call is negligible compared to the time spent inside a
           | function.
           | 
           | The one thing I'm personally trying to see if there's _any_
           | room for improvement on in a clean slate design, is error
           | handling  / HRESULT values. Exceptions get abused for flow
           | control and stack unwinding is expensive, so even if there
           | was a sane way to implement cross-language exception handling
           | it's a non starter. But HRESULT leads to IErrorInfo,
           | ISupportErrorInfo, thread local state
           | SetErrorInfo/GetErrorInfo, which is a whole extra bunch of
           | _fun_ to deal with.
           | 
           | There's the option of going the GObject and AppKit route,
           | using an out parameter for an Error type - but you have to
           | worry about freeing/releasing this in your language bindings
           | or risk leaking memory.
        
             | dleary wrote:
             | > Virtual dispatch absolutely has an overhead, but
             | absolutely nobody in their right mind should be using COM
             | interfaces in a critical section of code.
             | 
             | I could definitely be wrong, but I think C++ style "virtual
             | dispatch" (ie, following two pointers instead of one to get
             | to your function) doesn't really cost anything anymore,
             | except for the extra pointers taking up cache space.
             | 
             | Don't all of the Windows DirectX gaming interfaces use COM?
             | And isn't AAA gaming performance critical?
        
               | snuxoll wrote:
               | > Don't all of the Windows DirectX gaming interfaces use
               | COM? And isn't AAA gaming performance critical?
               | 
               | Yes, on both counts. You will also, on average, be making
               | fewer calls to ID3D12CommandQueue methods than one would
               | think - you'd submit an entire vertex buffer for a model
               | (or specific components of it that need the same pipeline
               | state, at least) at once, allocate larger pools of memory
               | on the GPU and directly write textures to it, etc.
               | 
               | This is the entire design behind D3D12, Vulkan, and Metal
               | - more direct interaction with the GPU, batching
               | submission, and caching command buffers for reuse.
               | 
               | When I'm talking about "critical sections" of code, I
               | mean anything with a tight loop where you can reasonably
               | expect to pin a CPU core with work. For a game, this
               | would be things like _creating_ vertex buffers, which is
               | why all three major API 's take these as bare pointers to
               | data structures in memory instead of requiring discrete
               | calls to create and populate them.
        
             | WorldMaker wrote:
             | WinRT is certainly not a "clean slate design", but still a
             | useful comparison to see where Microsoft themselves
             | iterated on the COM design with decades of hindsight.
        
           | jstimpfle wrote:
           | I'd say COM is also run-time type safe casting, and
           | importantly the reference counting is uniform which might
           | help writing wrappers for dynamic and garbage collected
           | languages.
           | 
           | I'm still not sure that it brings a lot to the table for
           | ordinary application development.
        
             | asveikau wrote:
             | It's been a while since I've written it professionally, but
             | I felt the fact that it has consistent idioms and
             | conventions helped me be somewhat more productive writing
             | C++. In the vast landscape of C++ features it winds up
             | making some decisions for you. You can use whatever you
             | want within your component but the COM interfaces dictate
             | how you talk to outside.
        
           | bri3d wrote:
           | > COM is basically just reference counting and interfaces. >
           | I remember a few years back hearing hate about COM and I
           | didn't feel like they understood what it was.
           | 
           | Even in "core" COM there's also marshaling, the whole
           | client/server IPC model, and apartments.
           | 
           | And, I think most people encounter COM with one of its
           | friends attached (like in this case, OLE/Automation in the
           | form of IDispatch), which adds an additional layer of
           | complexity on top.
           | 
           | Honestly I think that COM is really nice, though. If they'd
           | come up with some kind of user-friendly naming scheme instead
           | of UUIDs, I don't even think it would get that much hate. It
           | feels to me that 90% of the dislike for COM is the mental
           | overhead of seeing and dealing with UUIDs when getting
           | started.
           | 
           | Once you get past that part, it's really fast to do pretty
           | complex stuff in; compared to the other things people have
           | come up with like dbus or local gRPC and so on, it works
           | really well for coordinating extensibility and lots of
           | independent processes that need to work together.
        
           | recursive wrote:
           | You might have been hearing some of that hate from me. I
           | definitely don't understand COM, but I've had to use it once
           | or twice. It's pretty far outside what I normally work on,
           | which is all high-level garbage collected languages. I don't
           | know if that's even the right dimension to distinguish it. I
           | couldn't figure out how to use COM or what it's purpose was.
           | 
           | The task was some automated jobs doing MS word automation.
           | This all happened about 20 years ago. I never did figure out
           | how to get it to stop leaking memory after a couple days of
           | searching. I think I just had the process restart
           | periodically.
           | 
           | Compared to what I was accustomed to COM seemed weird and
           | just unnecessarily difficult to work with. I was a lot less
           | experienced then, but I haven't touched COM since. I still
           | don't know what the intent of COM is or where it's
           | documented, and nor have I tried to figure it out. But it's
           | colored my impression of COM ever since.
           | 
           | I think there may be a lot of people like me. They had to do
           | some COM thing because it was the only way to accomplish a
           | task, and just didn't understand. They randomly poked it
           | until it kind of worked, and swore never to touch it again.
        
             | duped wrote:
             | > I still don't know what the intent of COM is
             | 
             | COM is an ABI (application binary interface). You have two
             | programs, compiled in different languages with different
             | memory management strategies, potentially years apart. You
             | want them to communicate. You either
             | 
             | -1 use a Foreign Function Interface (FFI) provided to those
             | languages -2 serialize/deserialize data and send it over
             | some channel like a socket
             | 
             | (2) is how the internet works so we've taken to doing it
             | that way for many different systems, even if they don't
             | need it. (1) is how operating systems work and how the
             | kernel and other subsystems are exposed to user space.
             | 
             | The problem with FFI is that it's pretty barebones. You can
             | move bytes and call functions, but there's no standard way
             | of composing those bytes and function calls into higher
             | level constructs like you use in OOP languages.
             | 
             | COM is a standard for defining that FFI layer using OOP
             | patterns. Programs export objects which have well defined
             | interfaces. There's a root interface all objects implement
             | called "Unknown", and you can find out if an object
             | supports another interface by calling `queryInterface()`
             | with the id of a desired interface (all interfaces have a
             | globally unique ID). You can make sure the object doesn't
             | lose its data out of nowhere by calling `addRef()` to bump
             | its reference count, and `release()` to decrement it (thus
             | removing any ambiguity over memory management, for the most
             | part - see TFA for an example where that fails).
             | 
             | > where it's documented
             | 
             | https://learn.microsoft.com/en-us/windows/win32/com/the-
             | comp...
        
               | asveikau wrote:
               | > You have two programs, compiled in different languages
               | with different memory management strategies, potentially
               | years ap
               | 
               | Sometimes they are even the same language. Windows has a
               | few problems that I haven't seen in the Unix world, such
               | as: each DLL potentially having an incompatible
               | implementation of malloc, where allocating using
               | malloc(3) in one DLL then freeing it with free(3) in
               | another being a crash.
        
       | ptx wrote:
       | Couldn't the correct function signatures be generated from the
       | COM type library? Using an LLM for this is clearly not a good
       | fit, as the article demonstrates.
        
         | Kwpolska wrote:
         | They would need to know what a COM type library is in the first
         | place.
        
       | bri3d wrote:
       | This is quite interesting: it's easy to blame the use of LLM to
       | find the interface, but really this is a matter of needing to
       | understand the COM calling conventions in order to interact with
       | it.
       | 
       | I found the interface and a C++ sample in about two minutes of
       | GitHub searching:
       | 
       | https://github.com/microsoft/SampleNativeCOMAddin/blob/5512e...
       | 
       | https://github.com/microsoft/SampleNativeCOMAddin/blob/5512e...
       | 
       | but I don't actually think this would have helped the Rust
       | implementation; the authors already knew they wanted a BSTR and a
       | BSTR*, they just didn't understand the COM conventions for BSTR
       | ownership.
        
         | bigstrat2003 wrote:
         | > it's easy to blame the use of LLM to find the interface, but
         | really this is a matter of needing to understand the COM
         | calling conventions in order to interact with it.
         | 
         | Sure, but I think that this perfectly illustrates why LLMs are
         | not good at programming (and may well never get good): they
         | don't actually _understand_ anything. An LLM is fundamentally
         | incapable of going  "this is COM so let me make sure that the
         | function signature matches the calling conventions", it just
         | generates something based on the code it has seen before.
         | 
         | I don't blame the authors for reaching for an LLM given that
         | Microsoft has removed the C++ example code (seriously, what's
         | up with that nonsense?). But it does very nicely highlight why
         | LLMs are such a bad tool.
        
           | piker wrote:
           | In defense of the LLM here: learning COM from scratch given
           | its lack of accessible documentation would have forced us to
           | reach for C# for this minor project.
           | 
           | The LLM gave us an initial boost of productivity and (false)
           | confidence that enabled us to get at the problem with Rust.
           | While the LLM's _output_ was flawed, using it did actually
           | cause us to learn a lot about COM by allowing us to even
           | getting started. That somewhat flies in the face of a lot of
           | the  "tech debt" criticisms levied at LLMs (including by me).
           | Yes, we accumulated a bit of debt while working on the
           | project, but were in this case able to pay it off before
           | shipping and it gave us the leverage we needed to approach
           | this problem using pure Rust.
        
         | vintagedave wrote:
         | Every time I read an article on someone understanding COM from
         | interfaces and dispatching, I think: reinventing Delphi, badly.
        
       | LegionMammal978 wrote:
       | A lot of these automatic marshalling systems (in this case,
       | windows-rs) can be annoyingly unintuitive or opaque in how they
       | handle subtler details of memory ownership, character sets, how
       | to allocate and free objects, etc. And then it's made worse by
       | documentation that only gives the output of one marshalling
       | system (in this case, .NET) that's different from the one you're
       | using, so you have to translate it both backwards and forwards. I
       | guess this is mainly a consequence of COM trying to be all things
       | to all people, being used by both unmanaged and managed code.
        
       | rconti wrote:
       | Reference: Rage Against the Machine song "Calm Like a Bomb"
       | 
       | https://www.youtube.com/watch?v=h2TLwwrLKbY
        
       | JanneVee wrote:
       | Fun fact about BSTR, it uses memory before the string pointer to
       | store the length.
       | 
       | From the CComBSTR documentation from microsoft: "The CComBSTR
       | class is a wrapper for BSTRs, which are length-prefixed strings.
       | The length is stored as an integer at the memory location
       | preceding the data in the string. A BSTR is null-terminated after
       | the last counted character but may also contain null characters
       | embedded within the string. The string length is determined by
       | the character count, not the first null character."
       | https://learn.microsoft.com/en-us/cpp/atl/reference/ccombstr...
       | 
       | From the book ATL internals that I read about 24 years ago.
       | 
       | "Minor Rant on BSTRs, Embedded NUL Characters in Strings, and
       | Life in General From the book ATL internals that i read about 24
       | years ago.
       | 
       | The compiler considers the types BSTR and OLECHAR* to be
       | synonymous. In fact, the BSTR symbol is simply a typedef for
       | OLECHAR _. For example, from wtypes.h: typedef /_ [wire_marshal]
       | _/ OLECHAR __RPC_FAR _ BSTR;
       | 
       | This is more than somewhat brain damaged. An arbitrary BSTR is
       | not an OLECHAR _, and an arbitrary OLECHAR_ is not a BSTR. One is
       | often misled on this regard because frequently a BSTR works just
       | fine as an OLECHAR *.
       | 
       | STDMETHODIMP SomeClass::put_Name (LPCOLESTR pName) ; BSTR
       | bstrInput = ... pObj->put_Name (bstrInput) ; // This works just
       | fine... usually SysFreeString (bstrInput) ;
       | 
       | In the previous example, because the bstrInput argument is
       | defined to be a BSTR, it can contain embedded NUL characters
       | within the string. The put_Name method, which expects a LPCOLESTR
       | (a NUL-character-terminated string), will probably save only the
       | characters preceding the first embedded NUL character. In other
       | words, it will cut the string short."
       | 
       | I wont link to the pirated edition which is never than the one I
       | read.
       | 
       | So if there is code in outlook that relies on the preceding bytes
       | being the string length it can be the cause of the memory
       | corruption. It would require a sesssion in the debugger to figure
       | it out.
        
       ___________________________________________________________________
       (page generated 2025-12-10 23:01 UTC)