One of the responses to my discussion of how to give away a COM reference to yourself at destruction was roughly “The crazy convolutions required to accomplish this demonstrate how COM is a disaster.”
Well, some people may feel that COM is a disaster, but at least this specific piece of COM isn’t a disaster.
In a non-disastrous language, say, C++ with the standard library,¹ if somebody asked you, “How do I run some code when my shared_ptr
reference count drops to 1?”, the answer is simple: You can’t do it! C++’s shared_ptr
does not give you that level of access to the internal reference count. You can peek at the reference count by asking for the use_count()
, but that is necessarily just an approximation due to multithreading. Even knowing that the use count is 1 doesn’t prove that you have the last shared_ptr
. The reference count might go back up to 2 due to a race against a weak_ptr::lock()
, and your attempt to detect when the reference count has dropped to 1 has failed.
In C# and Java, you are allowed to rescue an object in its finalizer, known as object resurrection. Java weak references expire before the object is submitted for finalization, so even if you rescue the object in its finalizer, it’s too late to preserve the weak references. C# weak references default to Java style, but you can ask for a “long” weak reference which retains its connection to the object even past finalization. Unfortunately, the object cannot control the types of weak references that people create, and if somebody creates a long weak reference, then they can access your object while it is finalizing! That is likely to result in unhappiness if the finalizer cleaned up external resources (that being the primary purpose of finalizers), since the object no longer has its external resources and consequently is unable to perform any useful operations.
Maybe the real problem is the design that required an object to hand out a reference to itself at destruction. That’s what forced us into the weird contortions. But at least it’s possible with COM. That’s more than can be said for other frameworks.
Bonus chatter: I perhaps did not note clearly enough that the intended design of the Closed
event is that it is raised only the first time the last application reference is released. If the application rescues the object in its Closed
event handler, and then subsequently releases the rescued object, the Closed
event does not get raised again. The rules for the Windows Runtime is that the IClosable.
method tells the object, “I have no further intention of using this object,” and further operations² will fail with RO_
.
The “did I already raise the Closed
event?” flag is atomic because knowing that the reference count has dropped to 1 does not prove that only one thread can be using the object: A weak reference might increment the reference count back up, and then down to 1 a second time. C++/WinRT and C++/WRL objects support weak references by default, so this is something to worry about. If your framework doesn’t support weak references (or if you’ve disabled them), then the flag doesn’t need to be atomic because you know that there are no competing threads.
¹ Some people feel that C++ itself is also a disaster.
² With the exception of event handler unregistration and calling the IClosable.
itself.
After reading that ”C++ with the standard library is a non-disastrous language” I was going to write that at least 7 billion people would argue that and Raymond sometimes has not only social skills of a thermonuclear device (as we all now from his blog posts) but sense of humour of the same kind as well.
And then I saw the footnote.
COM is a disaster.
The only part of COM that should ever have existed was the fake COM created for Windows 95 while they were playing COM chicken; and the only reason that needs to exist is for shell plugins.
The entire rest of COM is dead weight; and any time a developer encounters it it means Microsoft designed the API surface wrong. These random COM APIs for windowing functions (application icon/prevent pinning/multi-desktop support) should be replaced...
COM has been used to publish undocumented features as interfaces that have to be discovered using other means than simply dumping DLL exports. Two examples that come to mind are IPolicyConfig and IApplicationView.
While I mostly agree with you, your statement is very much unsubstantiated opinion. Giving some actual examples of why COM is the worst way to provide functionality would make your point a lot more compelling and debatable.
Continuing the opinion based attacks on COM for a second*, the reason I don't like it is how much work it is to call from vanilla C/C++ or even MFC as compared to the dead** simple...
C++/CX was a sweet spot, finally Microsoft had something that could rival C++ Builder in productivity for Windows desktop applications in C++, including COM authoring.
It was killed by a group of folks clamouring language extensions are bad, for a technology that isn’t portable to other platforms to start with, and strangely the same group doesn’t have any issues using language extensions on other C++ compilers.
What really sucks is how COM is called from C#. On the one hand it’s nice that every typecast is an implicit QueryInterface call. On the other hand, the rules for calling Marshal.ReleaseComObject() are shady and usually result in crashes. And worse, you’re not allowed to use interface inheritance, so every time you define a derived interface you have to re-declare all the inherited methods with it.
It's the memory model constraints. I've had too many cases of having to launch another thread or even another process because something was only exposed as a COM API. And then I have to link in a COM support library or a reference library that I simply don't need but trying to get rid of is too much work. And now I'm calling APIs that don't exist on server core so I have to deal...
The examples you give (application icon, prevent pinning, multi-desktop) aren’t features of user32, so why should user32 export them?
1) Because accessing them shouldn't impose a thread model that persists after the call finishes.
2) Because it ought not to import shell32 into the calling process that wouldn't otherwise have it, and definitely shouldn't start importing any shell plugin dlls.
3) Because the shell ought to be a replaceable component. It's not the rock solid shell we used to have fifteen years ago. I should be able to actually use the registry key for replace shell...
The shell functionality should be a per-user service. Right now there are a bunch of things you can’t do unless explorer.exe is running as “the shell”. Particularly, UWP apps stop working and PIDL update events (SHChangeNotify) don’t get broadcast. Plus there’s something internal and weird about the way explorer cloaks windows to make “virtual desktops” work that can’t really be reproduced. Cheesy.
Rust has , which decreases the refcount but, if the refcount drops to 0, moves the value out of the heap (returning it to the caller) instead of destroying it. You can then do whatever you want with the value you've got. The object identity has changed (it has been moved out of where it was stored, and of course weakrefs have been disconnected), but in 99% of the cases you don't care about object...
I can agree that both COM and C++ are a disaster 😜, at least in the sense that the parts of C++ that necessitated the creation of COM are a disaster (such a lack of a binary standard). COMs main problem to me is that it tries to solve way too many problems with one giant solution (common binary interface, object lifetime, cross-process communication, cross-computer communication, threading models, etc). This makes it complicated, difficult to...
COM is a specification for component development. It isn't a big specification either; the book 'Inside COM' from Dale Rogerson covers the most important aspects in a reader friendly way. Getting the reference counting correct used to be a challenge but with smart pointers (e.g. CComPtr) one can hardly go wrong these days. Apartments o.t.h. are still misty and counterproductive amplified by conflicting or confusing documentation.
For me C++is not a disaster though I am not...
What makes COM a disaster is Microsoft lack of willigness to provide good tooling for using it, similar to how C++ Builder extensions. There was a timid effort with C++/CX that was killed due to internal politics in name of "portability" for what is and will remain a Windows only technology.
That after 30 years COM tooling still lags behing similar technologies on other platforms (XPC, AIDL, D-BUS), despite its surface exposure in Windows APIs, is...
...the parts of C++ that necessitated the creation of COM are a disaster (such a lack of a binary standard)
Many consider not constantly breaking the ABI to be one of C++'s original sins (search: ), and many of them admire Rust's complete lack of ABI stability outside of designated FFI types.
Actually, most languages don't have a stable ABI for non-FFI types; for those languages, the impact of the lack of a stable ABI is usually...
Seems to me the issue is that object resurrection is in the weird middle ground of being unsupported and (almost always) broken, but not actually impossible - and in particular, all too possible to do accidentally.
If there were a way for an object to explicitly tell the framework/runtime/infrastructure something to the effect of "hey, I know I was about to destruct, but actually I changed my mind, resurrect me", then the infrastructure could also provide...