Remix.run Logo
jlarocco 3 days ago

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.

asveikau 3 days ago | parent | next [-]

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 3 days ago | parent | next [-]

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.

pjmlp a day ago | parent | next [-]

Registration free COM has existed since Windows XP, if I get my timeline right without bothering to look it up.

All modern Windows APIs introduced since Vista have been COM, classical Win32 C APIs are seldom introduced nowadays.

Certainly current Windows 11 performance problems have nothing to do with using COM all over the place, rather Webwidgets instead of native code, hiring people that apparently never did Windows programming, that apparently do AI driven coding.

Ah, macOS and iDevices driver model is equally based in COM like design, one would expect drivers to be something where performance matters.

Then there is XPC, Android IPC, and one could consider D-BUS as well, if it was more widely adopted across the GNU/Linux world.

snuxoll 12 hours ago | parent [-]

You are absolutely right on all counts, although XPC/Binder/D-Bus aren't really something to compare against the core of COM (the ABI model), and I think many Windows developers would have some unkind things to say about DCOM.

dleary 3 days ago | parent | prev | next [-]

> 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 3 days ago | parent [-]

> 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 3 days ago | parent | prev [-]

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.

pjmlp 2 days ago | parent [-]

Pity that the great tooling that came with it is now gone, alongside UWP.

WinRT tooling on Win32 side is a bad joke.

I almost lost count of how many COM frameworks have come and gone since OLE 1.0 days.

bri3d 3 days ago | parent | prev | next [-]

> 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.

pjc50 2 days ago | parent [-]

Even the UUIDs aren't bad, they're a reasonable solution to Zooko's triangle. You can't globally assign names.

bri3d a day ago | parent [-]

Yeah, I've often thought about what I'd do instead and there's no legitimate alternative. It might help developers feel better if they had some kind of "friendly name" functionality (ie - if registrations in the Registry had a package-identifier style string alongside), but that also wouldn't have flown when COM was invented and resources overall were much more scarce than they are today.

snuxoll 12 hours ago | parent [-]

While they're not "the same", classic COM (or OLE? the whole history is a mess) did actually have ProgIDs, and WinRT introduces proper "classes" and namespaces (having given up global registration for everything but system provided API's) with proper "names" (you can even query them at runtime with IInspectable::GetRuntimeClassName).

Microsoft tried to do a lot with COM when they first released it, it wasn't just a solution for having a stable cross-language ABI, it was a way to share component libraries across multiple applications on a system, and a whole lot more.

> but that also wouldn't have flown when COM was invented and resources overall were much more scarce than they are today.

And this ultimately is the paradox of COM. There were good ideas, but given Microsoft's (mostly kept) promise of keeping old software working the bad ones have remained baked in.

recursive 3 days ago | parent | prev | next [-]

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 3 days ago | parent | next [-]

> 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 3 days ago | parent [-]

> 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.

snuxoll 11 hours ago | parent | next [-]

> where allocating using malloc(3) in one DLL then freeing it with free(3) in another being a crash.

This can still happen all the time on UNIX systems. glibc's malloc implementation is a fine general purpose allocator, but there's plenty of times where you want to bring in tcmalloc, jemalloc, etc. Of course, you hope that various libraries will resolve to your implementation of choice when the linker wires everything up, but they can opt not to just as easily.

asveikau 9 hours ago | parent [-]

No actually, this doesn't happen the same way on modern Unix. The way symbol resolution works is just not the same. A library asking for an extern called "malloc" will get the same malloc. To use those other allocators, you would typically give them a different symbol name, or make the whole process use the new one.

A dll import on Windows explicitly calls for the DLL by name. You could have some DLLs explicitly ask for a different version of the Visual Studio runtime, or with different threading settings, release vs debug etc., and a C extern asking for simply the name "malloc", no other details, will resolve to that, possibly incompatible with another DLL in the same process despite the compiler's perspective of it just being extern void *malloc(size_t) and no other detail, no other decoration, rename of the symbol etc.. there might be a rarely used symbol versioning pragma to accomplish similar on a modern gcc/clang/elf setup but it's not the way anybody does this.

I would argue that the modern Unix way, with these limitations, is better, by the way. Maybe some older Unix in the early days of shared libraries, early 90s or so, tried what Windows does, I don't know. But it's not common today.

snuxoll 7 hours ago | parent [-]

> No actually, this doesn't happen the same way on modern Unix. The way symbol resolution works is just not the same. A library asking for an extern called "malloc" will get the same malloc. To use those other allocators, you would typically give them a different symbol name, or make the whole process use the new one.

This is, yes, the behavior of both the ELF specification as well as the GNU linker.

I'm not here to get into semantics of symbol namespaces and resolution though, I can just as easily link a static jemalloc into an arbitrary ELF shared object and use it inside for every allocation and not give a damn about what the global malloc() symbol points to. There's a half dozen other ways I can have a local malloc() symbol as well instead of having the linker bind the global one.

Which, is the entire point I'm trying to make. Is this a bigger problem on Windows versus UNIX-like platforms due to the way runtime linker support is handled? Yes. Is it entirely possible to have the same issue, however? Yes, absolutely.

pjmlp 2 days ago | parent | prev [-]

Because C standard library isn't part of the OS.

Outside UNIX, the C standard library is a responsibility of the C compiler vendor, not the OS.

Nowadays Windows might seem the odd one, however 30 years ago the operating system was more diverse.

You will also find similar issues on dynamic libraries in mainframes/micros from IBM and Unisys, still being sold.

asveikau 2 days ago | parent [-]

Yeah I know the reasons for this, I'm just saying it's not usual coming from currently dominant unix-like systems.

asveikau 2 days ago | parent | prev [-]

> I couldn't figure out how to use COM or what it's purpose was.

A shorter version than the other reply:

COM allows you to have a reference counted object with callable virtual methods. You can also ask for different virtual methods at runtime (QueryInterface).

Some of the use cases include: maybe those methods are implemented in a completely different programming language that you are using, for example I think one of the historical ones is JavaScript or vbscript interacting with C++. It standardizes the virtual calls in such a way that you can throw in such an abstraction. And since reference counting happens via a virtual call, memory allocation is also up to the callee. Another historical use case is to have the calls be handled in a different process.

jstimpfle 3 days ago | parent | prev [-]

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 3 days ago | parent [-]

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.

pjmlp 3 days ago | parent | prev | next [-]

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 3 days ago | parent | prev [-]

Using COM in Perl was pretty seamless back in its heyday.