Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling the references of COM objects #2472

Closed
mominul opened this issue Apr 24, 2023 · 9 comments
Closed

Handling the references of COM objects #2472

mominul opened this issue Apr 24, 2023 · 9 comments
Labels
question Further information is requested

Comments

@mominul
Copy link
Contributor

mominul commented Apr 24, 2023

Currently the reference handling functions(AddRef() and Release()) are hidden and are implemented by the implement macro for custom COM objects, which is a good approach that reference counting is made implicit. But it creates an issue when we need to know the number of references of an object is still alive, currently this is not exposed by any of the APIs. When implementing an inproc COM server, there is a requirement to implement the DllCanUnloadNow() function to indicate if the DLL is ready to be unloaded. If there are references alive it returns S_FALSE otherwise S_TRUE.

We can somewhat at least keep the track of how many references of the objects are being created. But how do we track the Release() of the references? Do you have a solution or workaround in mind?

@kennykerr
Copy link
Collaborator

Unloading is generally very unreliable, so I usually recommend not implementing DllCanUnloadNow. If you must implement this for some reason, you don't actually need the COM reference count. You just need the count of outstanding objects. A module lock is usually implemented by incrementing a count whenever an object is created and decrementing it whenever an object is dropped. This is more efficient than counting each reference. #1094 will likely also include such a built-in counter but you can implement that yourself for the time being.

@kennykerr kennykerr added the question Further information is requested label Apr 24, 2023
@mominul
Copy link
Contributor Author

mominul commented Apr 24, 2023

A module lock is usually implemented by incrementing a count whenever an object is created and decrementing it whenever an object is dropped.

How can I track dropping of objects? Can you kindly provide an example please?

@kennykerr
Copy link
Collaborator

You can implement the Drop trait on your implementation.

@mominul
Copy link
Contributor Author

mominul commented Apr 24, 2023

Thanks!

@mominul mominul closed this as completed Apr 24, 2023
@tim-weis
Copy link
Contributor

A while back I found myself confronted with the same questions, so here's a bit more information:

A COM server only needs to track the actual object count to implement DllCanUnloadNow. It is not required to track each and every QueryInterface/AddRef and Release call on the interfaces a client holds or requests. The only interesting call is the final Release on any given interface, that dials back home and eventually destroys the COM object.

Since a COM server also implements the class factory, which every external instantiation request necessarily has to go through, it is now in control over both the start and the end of a COM object's life. This allows to, in theory, implement the counter representing the DllCanUnloadNow predicate, spread across the class factory and Drop implementation(s).

I said "in theory", since (as I discovered) it is very fragile in practice. While not impossible to get right, it's very challenging to keep it that way. All it takes is a single interface method that instantiates a COM object, without updating the object counter (say, a Clone method).

I ultimately settled for always returning S_FALSE from my DllCanUnloadNow implementation; the OS already knows how to evict pages of code that hasn't executed in a while from physical memory if required. Which boils down to wasting a bit of address space, and possibly the inability to overwrite/delete the executable image. Neither one was an issue in my case.

@mominul
Copy link
Contributor Author

mominul commented Apr 24, 2023

Thanks a lot, @tim-weis! ❤️

mominul added a commit to mominul/tsf-example that referenced this issue Apr 24, 2023
Unloading is generally very unreliable, so we don't take the hassle  of keep tracking when the DLL is ready to be unloaded.

microsoft/windows-rs#2472 (comment)
@kennykerr
Copy link
Collaborator

kennykerr commented Apr 24, 2023

Since a COM server also implements the class factory, which every external instantiation request necessarily has to go through, it is now in control over both the start and the end of a COM object's life.

There's nothing that prevents instances from outliving factories. C++/WinRT for example simply keeps track of all outstanding objects, factory or not.

@tim-weis
Copy link
Contributor

@kennykerr I realize that that was worded in an unfortunate way. I didn't mean to imply that a live class factory were the indicator for outstanding object references. Indeed, the factory is commonly the first thing to go once a client has obtained their object of interest.

What I meant to say is that the class factory implementation is under the (COM server) author's control, and with all object instantiation requests going through it, it is a convenient place to increment the object counter for each successfully serviced request.

Hope that makes more sense.

@kennykerr
Copy link
Collaborator

Yep, best advice is still to avoid exporting DllCanUnloadNow altogether. 🙃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants